Rewrote Special:Upload to allow easier extension. Mostly backwards compatible towards...
authorBryan Tong Minh <btongminh@users.mediawiki.org>
Sun, 18 Oct 2009 19:41:01 +0000 (19:41 +0000)
committerBryan Tong Minh <btongminh@users.mediawiki.org>
Sun, 18 Oct 2009 19:41:01 +0000 (19:41 +0000)
* Special:Upload now uses HTMLForm for form generation
* Upload errors that can be solved by changing the filename now do not require reuploading.

12 files changed:
RELEASE-NOTES
includes/AutoLoader.php
includes/Licenses.php
includes/Setup.php
includes/SpecialPage.php
includes/api/ApiUpload.php
includes/specials/SpecialUpload.php
includes/upload/UploadBase.php
includes/upload/UploadFromStash.php
languages/messages/MessagesEn.php
skins/common/upload.js
skins/common/wikibits.js

index 58bfc33..e58ff0b 100644 (file)
@@ -249,6 +249,10 @@ it from source control: http://www.mediawiki.org/wiki/Download_from_SVN
   limit exceeded, __NOINDEX__ tracking, etc) can now be disabled by setting the 
   system message ([[MediaWiki:expensive-parserfunction-category]] etc) to "-".
 * Added maintenance script sqlite.php for SQLite-specific maintenance tasks.
+* Rewrote Special:Upload to allow easier extension. 
+* Upload errors that can be solved by changing the filename now do not require
+  reuploading.
+
 
 === Bug fixes in 1.16 ===
 
index 8ca1a4d..b24163d 100644 (file)
@@ -550,6 +550,7 @@ $wgAutoloadLocalClasses = array(
        'SpecialSearch' => 'includes/specials/SpecialSearch.php',
        'SpecialStatistics' => 'includes/specials/SpecialStatistics.php',
        'SpecialTags' => 'includes/specials/SpecialTags.php',
+       'SpecialUpload' => 'includes/specials/SpecialUpload.php',
        'SpecialVersion' => 'includes/specials/SpecialVersion.php',
        'SpecialWhatlinkshere' => 'includes/specials/SpecialWhatlinkshere.php',
        'UncategorizedCategoriesPage' => 'includes/specials/SpecialUncategorizedcategories.php',
index 6398c88..4b81bfa 100644 (file)
@@ -9,46 +9,39 @@
  * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
  */
 
-class Licenses {
-       /**#@+
-        * @private
-        */
+class Licenses extends HTMLFormField {
        /**
         * @var string
         */
-       var $msg;
+       protected $msg;
 
        /**
         * @var array
         */
-       var $licenses = array();
+       protected $licenses = array();
 
        /**
         * @var string
         */
-       var $html;
+       protected $html;
        /**#@-*/
 
        /**
         * Constructor
-        *
-        * @param $str String: the string to build the licenses member from, will use
-        *                    wfMsgForContent( 'licenses' ) if null (default: null)
         */
-       function __construct( $str = null ) {
-               // PHP sucks, this should be possible in the constructor
-               $this->msg = is_null( $str ) ? wfMsgForContent( 'licenses' ) : $str;
-               $this->html = '';
+       public function __construct( $params ) {
+               parent::__construct( $params );
+               
+               $this->msg = empty( $params['licenses'] ) ? wfMsgForContent( 'licenses' ) : $params['licenses'];
+               $this->selected = null;
 
                $this->makeLicenses();
-               $tmp = $this->getLicenses();
-               $this->makeHtml( $tmp );
        }
 
        /**#@+
         * @private
         */
-       function makeLicenses() {
+       protected function makeLicenses() {
                $levels = array();
                $lines = explode( "\n", $this->msg );
 
@@ -75,18 +68,17 @@ class Licenses {
                }
        }
 
-       function trimStars( $str ) {
+       protected static function trimStars( $str ) {
                $i = $count = 0;
 
-               wfSuppressWarnings();
-               while ($str[$i++] == '*')
-                       ++$count;
-               wfRestoreWarnings();
-
-               return array( $count, ltrim( $str, '* ' ) );
+               $length = strlen( $str );
+               for ( $i = 0; $i < $length; $i++ ) {
+                       if ( $str[$i] != '*' )
+                               return array( $i, ltrim( $str, '* ' ) );
+               }
        }
 
-       function stackItem( &$list, $path, $item ) {
+       protected function stackItem( &$list, $path, $item ) {
                $position =& $list;
                if ( $path )
                        foreach( $path as $key )
@@ -94,13 +86,12 @@ class Licenses {
                $position[] = $item;
        }
 
-       function makeHtml( &$tagset, $depth = 0 ) {
+       protected function makeHtml( &$tagset, $depth = 0 ) {
                foreach ( $tagset as $key => $val )
                        if ( is_array( $val ) ) {
                                $this->html .= $this->outputOption(
-                                       $this->msg( $key ),
+                                       $this->msg( $key ), '',
                                        array(
-                                               'value' => '',
                                                'disabled' => 'disabled',
                                                'style' => 'color: GrayText', // for MSIE
                                        ),
@@ -109,22 +100,22 @@ class Licenses {
                                $this->makeHtml( $val, $depth + 1 );
                        } else {
                                $this->html .= $this->outputOption(
-                                       $this->msg( $val->text ),
-                                       array(
-                                               'value' => $val->template,
-                                               'title' => '{{' . $val->template . '}}'
-                                       ),
+                                       $this->msg( $val->text ), $val->template,
+                                       array( 'title' => '{{' . $val->template . '}}' ),
                                        $depth
                                );
                        }
        }
 
-       function outputOption( $val, $attribs = null, $depth ) {
-               $val = str_repeat( /* &nbsp */ "\xc2\xa0", $depth * 2 ) . $val;
+       protected function outputOption( $text, $value, $attribs = null, $depth = 0 ) {
+               $attribs['value'] = $value;
+               if ( $value === $this->selected )
+                       $attribs['selected'] = 'selected';              
+               $val = str_repeat( /* &nbsp */ "\xc2\xa0", $depth * 2 ) . $text;
                return str_repeat( "\t", $depth ) . Xml::element( 'option', $attribs, $val ) . "\n";
        }
 
-       function msg( $str ) {
+       protected function msg( $str ) {
                $out = wfMsg( $str );
                return wfEmptyMsg( $str, $out ) ? $str : $out;
        }
@@ -136,14 +127,29 @@ class Licenses {
         *
         * @return array
         */
-       function getLicenses() { return $this->licenses; }
+       public function getLicenses() { return $this->licenses; }
 
        /**
         * Accessor for $this->html
         *
         * @return string
         */
-       function getHtml() { return $this->html; }
+       public function getInputHTML( $value ) {
+               $this->selected = $value;
+               
+               $this->html = $this->outputOption( wfMsg( 'nolicense' ), '',
+                       (bool)$this->selected ? null : array( 'selected' => 'selected' ) );
+               $this->makeHtml( $this->getLicenses() );
+               
+               $attribs = array(
+                       'name' => $this->mName,
+                       'id' => $this->mID
+               );
+               if ( !empty( $this->mParams['disabled'] ) )
+                       $attibs['disabled'] = 'disabled';
+               
+               return Html::rawElement( 'select', $attribs, $this->html );
+       }
 }
 
 /**
index 2f16ed4..04b9b47 100644 (file)
@@ -306,9 +306,9 @@ $wgDeferredUpdateList = array();
 $wgPostCommitUpdateList = array();
 
 if ( $wgAjaxWatch ) $wgAjaxExportList[] = 'wfAjaxWatch';
-if ( $wgAjaxUploadDestCheck ) $wgAjaxExportList[] = 'UploadForm::ajaxGetExistsWarning';
+if ( $wgAjaxUploadDestCheck ) $wgAjaxExportList[] = 'SpecialUpload::ajaxGetExistsWarning';
 if( $wgAjaxLicensePreview )
-       $wgAjaxExportList[] = 'UploadForm::ajaxGetLicensePreview';
+       $wgAjaxExportList[] = 'SpecialUpload::ajaxGetLicensePreview';
 
 # Placeholders in case of DB error
 $wgTitle = null;
index dbf0974..47ba993 100644 (file)
@@ -141,7 +141,7 @@ class SpecialPage {
                'Filepath'                  => array( 'SpecialPage', 'Filepath' ),
                'MIMEsearch'                => array( 'SpecialPage', 'MIMEsearch' ),
                'FileDuplicateSearch'       => array( 'SpecialPage', 'FileDuplicateSearch' ),
-               'Upload'                    => 'UploadForm',
+               'Upload'                    => 'SpecialUpload',
 
                # Wiki data and tools
                'Statistics'                            => 'SpecialStatistics',
index d37e54f..c421e47 100644 (file)
@@ -230,7 +230,7 @@ class ApiUpload extends ApiBase {
                                        $this->dieUsage( 'This file did not pass file verification', 'verification-error',
                                                        0, array( 'details' => $verification['details'] ) );
                                        break;
-                               case UploadBase::UPLOAD_VERIFICATION_ERROR:
+                               case UploadBase::HOOK_ABORTED:
                                        $this->dieUsage( "The modification you tried to make was aborted by an extension hook",
                                                        'hookaborted', 0, array( 'error' => $verification['error'] ) );
                                        break;
index 9025fc2..713a6ff 100644 (file)
@@ -2,82 +2,80 @@
 /**
  * @file
  * @ingroup SpecialPage
+ * @ingroup Upload
+ * 
+ * Form for handling uploads and special page.
+ * 
  */
 
-/**
- * implements Special:Upload
- * @ingroup SpecialPage
- */
-class UploadForm extends SpecialPage {
-       /**#@+
-        * @access private
-        */
-       var $mComment, $mLicense, $mIgnoreWarning;
-       var $mCopyrightStatus, $mCopyrightSource, $mReUpload, $mAction, $mUploadClicked;
-       var $mDestWarningAck;
-       var $mLocalFile;
-
-       var $mUpload; // Instance of UploadBase or derivative
-
-       # Placeholders for text injection by hooks (must be HTML)
-       # extensions should take care to _append_ to the present value
-       var $uploadFormTextTop;
-       var $uploadFormTextAfterSummary;
-       var $mTokenOk = false;
-       var $mForReUpload = false;
-       /**#@-*/
-
+class SpecialUpload extends SpecialPage {
        /**
         * Constructor : initialise object
         * Get data POSTed through the form and assign them to the object
-        * @param $request Data posted.
+        * @param WebRequest $request Data posted.
         */
-       function __construct( $request = null ) {
+       public function __construct( $request = null ) {
+               global $wgRequest;
+               
                parent::__construct( 'Upload', 'upload' );
-               $this->mRequest = $request;
+               
+               $this->loadRequest( is_null( $request ) ? $wgRequest : $request );
        }
+       
+       /** Misc variables **/
+       protected $mRequest;                    // The WebRequest or FauxRequest this form is supposed to handle
+       protected $mSourceType;
+       protected $mUpload;
+       protected $mLocalFile;
+       protected $mUploadClicked;
+       
+       /** User input variables from the "description" section **/
+       protected $mDesiredDestName;    // The requested target file name
+       protected $mComment;
+       protected $mLicense;
+       
+       /** User input variables from the root section **/
+       protected $mIgnoreWarning;
+       protected $mWatchThis;
+       protected $mCopyrightStatus;
+       protected $mCopyrightSource;
+       
+       /** Hidden variables **/
+       protected $mForReUpload;                // The user followed an "overwrite this file" link
+       protected $mCancelUpload;               // The user clicked "Cancel and return to upload form" button
+       protected $mTokenOk;
+       
+       /**
+        * Initialize instance variables from request and create an Upload handler
+        * 
+        * @param WebRequest $request The request to extract variables from
+        */
+       protected function loadRequest( $request ) {
+               global $wgUser;
 
-       protected function initForm() {
-               global $wgRequest, $wgUser;
-
-               if ( is_null( $this->mRequest ) ) {
-                       $request = $wgRequest;
-               } else {
-                       $request = $this->mRequest;
-               }
+               $this->mRequest = $request;
+               $this->mSourceType        = $request->getVal( 'wpSourceType', 'file' );
+               $this->mUpload            = UploadBase::createFromRequest( $request );
+               $this->mUploadClicked     = $request->getCheck( 'wpUpload' ) && $request->wasPosted();
+               
                // Guess the desired name from the filename if not provided
                $this->mDesiredDestName   = $request->getText( 'wpDestFile' );
                if( !$this->mDesiredDestName )
                        $this->mDesiredDestName = $request->getText( 'wpUploadFile' );
-
-               $this->mForReUpload       = $request->getBool( 'wpForReUpload' ); // updating a file
-               $this->mIgnoreWarning     = $request->getCheck( 'wpIgnoreWarning' );
                $this->mComment           = $request->getText( 'wpUploadDescription' );
-
-               if( !$request->wasPosted() ) {
-                       # GET requests just give the main form; no data except destination
-                       # filename and description
-                       return;
-               }
-
-               # Placeholders for text injection by hooks (empty per default)
-               $this->uploadFormTextTop = "";
-               $this->uploadFormTextAfterSummary = "";
-
-               $this->mUploadClicked     = $request->getCheck( 'wpUpload' );
-
-               $this->mLicense           = $request->getText( 'wpLicense' );
+               $this->mLicense           = $request->getText( 'wpLicense' );           
+               
+               
+               $this->mIgnoreWarning     = $request->getCheck( 'wpIgnoreWarning' );
+               $this->mWatchthis         = $request->getBool( 'wpWatchthis' );
                $this->mCopyrightStatus   = $request->getText( 'wpUploadCopyStatus' );
                $this->mCopyrightSource   = $request->getText( 'wpUploadSource' );
-               $this->mWatchthis         = $request->getBool( 'wpWatchthis' );
-               $this->mSourceType        = $request->getVal( 'wpSourceType', 'file' );
-               $this->mDestWarningAck    = $request->getText( 'wpDestFileWarningAck' );
 
-               $this->mReUpload          = $request->getCheck( 'wpReUpload' ); // retrying upload
 
-               $this->mAction            = $request->getVal( 'action' );
-               $this->mUpload            = UploadBase::createFromRequest( $request );
-               
+               $this->mForReUpload       = $request->getBool( 'wpForReUpload' ); // updating a file            
+               $this->mCancelUpload      = $request->getCheck( 'wpCancelUpload' )
+                                        || $request->getCheck( 'wpReUpload' ); // b/w compat
+
                // If it was posted check for the token (no remote POST'ing with user credentials)
                $token = $request->getVal( 'wpEditToken' );
                if( $this->mSourceType == 'file' && $token == null ) {
@@ -89,23 +87,28 @@ class UploadForm extends SpecialPage {
                        $this->mTokenOk = $wgUser->matchEditToken( $token );
                }
        }
-
+       
+       /**
+        * This page can be shown if uploading is enabled.
+        * Handle permission checking elsewhere in order to be able to show 
+        * custom error messages.
+        * 
+        * @param User $user
+        * @return bool
+        */
        public function userCanExecute( $user ) {
                return UploadBase::isEnabled() && parent::userCanExecute( $user );
        }
-
+       
        /**
-        * Start doing stuff
-        * @access public
+        * Special page entry point
         */
-       function execute( $par ) {
+       public function execute() {
                global $wgUser, $wgOut, $wgRequest;
-
+               
                $this->setHeaders();
                $this->outputHeader();
-
-               $this->initForm();
-
+               
                # Check uploading enabled
                if( !UploadBase::isEnabled() ) {
                        $wgOut->showErrorPage( 'uploaddisabled', 'uploaddisabledtext' );
@@ -131,91 +134,302 @@ class UploadForm extends SpecialPage {
                        return;
                }
 
+               # Check whether we actually want to allow changing stuff
                if( wfReadOnly() ) {
                        $wgOut->readOnlyPage();
                        return;
                }
-               //check token if uploading or reUploading
-               if( !$this->mTokenOk && !$this->mReUpload && ($this->mUpload && (
-                                               'submit' == $this->mAction || $this->mUploadClicked ) ) )
-               {
-                       $this->mainUploadForm ( wfMsgExt( 'session_fail_preview', 'parseinline' ) );
-                       return ;
-               }
-
-
-               if( $this->mReUpload &&  $this->mUpload) {
-                       // User choose to cancel upload
-                       if( !$this->mUpload->unsaveUploadedFile() ) {
+               
+               # Unsave the temporary file in case this was a cancelled upload
+               if ( $this->mCancelUpload ) {
+                       if ( !$this->unsaveUploadedFile() )
+                               # Something went wrong, so unsaveUploadedFile showed a warning
                                return;
-                       }
-                       # Because it is probably checked and shouldn't be
-                       $this->mIgnoreWarning = false;
-                       $this->mainUploadForm();
-               } elseif( $this->mUpload && (
-                                       'submit' == $this->mAction ||
-                                       $this->mUploadClicked
-                               ) ) {
+               }
+               
+               # Process upload or show a form
+               if ( $this->mTokenOk && !$this->mCancelUpload 
+                               && ( $this->mUpload && $this->mUploadClicked ) ) { 
                        $this->processUpload();
                } else {
-                       $this->mainUploadForm();
+                       $this->showUploadForm( $this->getUploadForm() );
                }
-
-               if( $this->mUpload )
+               
+               # Cleanup
+               if ( $this->mUpload )
                        $this->mUpload->cleanupTempFile();
        }
+       
+       /**
+        * Show the main upload form and optionally add the session key to the 
+        * output. This hides the source selection.
+        * 
+        * @param string $message HTML message to be shown at top of form
+        * @param string $sessionKey Session key of the stashed upload
+        */
+       protected function showUploadForm( $form ) {
+               # Add links if file was previously deleted
+               if ( !$this->mDesiredDestName )
+                       $this->showViewDeletedLinks();
+               
+               $form->show();
+       }
+       
+       /**
+        * Get an UploadForm instance with title and text properly set.
+        * 
+        * @param string $message HTML string to add to the form
+        * @param string $sessionKey Session key in case this is a stashed upload
+        * @return UploadForm
+        */
+       protected function getUploadForm( $message = '', $sessionKey = '' ) {
+               # Initialize form
+               $form = new UploadForm( $this->watchCheck(), $this->mForReUpload, $sessionKey );
+               $form->setTitle( $this->getTitle() );
+               
+               # Check the token, but only if necessary
+               if( !$this->mTokenOk && !$this->mCancelUpload 
+                               && ( $this->mUpload && $this->mUploadClicked ) ) 
+                       $form->addPreText( wfMsgExt( 'session_fail_preview', 'parseinline' ) );
+
+               # Add text to form
+               $form->addPreText( '<div id="uploadtext">' . wfMsgExt( 'uploadtext', 'parse' ) . '</div>');
+               # Add upload error message
+               $form->addPreText( $message );          
+               
+               return $form;           
+       }
+       
+       /**
+        * TODO: DOCUMENT
+        */
+       protected function showViewDeletedLinks() {
+               global $wgOut, $wgUser;
+               
+               $title = Title::makeTitleSafe( NS_FILE, $this->mDesiredDestName );
+               // Show a subtitle link to deleted revisions (to sysops et al only)
+               if( $title instanceof Title && ( $count = $title->isDeleted() ) > 0 && $wgUser->isAllowed( 'deletedhistory' ) ) {
+                       $link = wfMsgExt(
+                               $wgUser->isAllowed( 'delete' ) ? 'thisisdeleted' : 'viewdeleted',
+                               array( 'parse', 'replaceafter' ),
+                               $wgUser->getSkin()->linkKnown(
+                                       SpecialPage::getTitleFor( 'Undelete', $title->getPrefixedText() ),
+                                       wfMsgExt( 'restorelink', array( 'parsemag', 'escape' ), $count )
+                               )
+                       );
+                       $wgOut->addHTML( "<div id=\"contentSub2\">{$link}</div>" );
+               }
 
+               // Show the relevant lines from deletion log (for still deleted files only)
+               if( $title instanceof Title && $title->isDeletedQuick() && !$title->exists() ) {
+                       $this->showDeletionLog( $wgOut, $title->getPrefixedText() );
+               }
+       }
+       
        /**
-        * Do the upload
+        * Stashes the upload and shows the main upload form.
+        * 
+        * Note: only errors that can be handled by changing the name or 
+        * description should be redirected here. It should be assumed that the
+        * file itself is sane and has passed UploadBase::verifyFile. This 
+        * essentially means that UploadBase::VERIFICATION_ERROR and 
+        * UploadBase::EMPTY_FILE should not be passed here.
+        * 
+        * @param string $message HTML message to be passed to mainUploadForm
+        */
+       protected function recoverableUploadError( $message ) {
+               $sessionKey = $this->mUpload->stashSession();
+               $message = '<h2>' . wfMsgHtml( 'uploadwarning' ) . "</h2>\n" .
+                       '<div class="error">' . $message . "</div>\n";
+               $this->showUploadForm( $this->getUploadForm( $message, $sessionKey ) );
+       }
+       /** 
+        * Stashes the upload, shows the main form, but adds an "continue anyway button"
+        * 
+        * @param array $warnings
+        */
+       protected function uploadWarning( $warnings ) {
+               global $wgUser;
+               
+               $sessionKey = $this->mUpload->stashSession();
+               
+               $sk = $wgUser->getSkin();
+
+               $warningHtml = '<h2>' . wfMsgHtml( 'uploadwarning' ) . "</h2>\n" 
+                       . '<ul class="warning">';
+               foreach( $warnings as $warning => $args ) {
+                               $msg = '';
+                               if( $warning == 'exists' ) {
+                                       $msg = self::getExistsWarning( $args );
+                               } elseif( $warning == 'duplicate' ) {
+                                       $msg = self::getDupeWarning( $args );
+                               } elseif( $warning == 'duplicate-archive' ) {
+                                       $msg = "\t<li>" . wfMsgExt( 'file-deleted-duplicate', 'parseinline', 
+                                                       array( Title::makeTitle( NS_FILE, $args )->getPrefixedText() ) ) 
+                                               . "</li>\n";
+                               } else {
+                                       if ( is_bool( $args ) )
+                                               $args = array();
+                                       elseif ( !is_array( $args ) )
+                                               $args = array( $args );
+                                       $msg = "\t<li>" . wfMsgExt( $warning, 'parseinline', $args ) . "</li>\n";
+                               }
+                               $warningHtml .= $msg;
+               }
+               
+               $form = $this->getUploadForm( $warningHtml, $sessionKey );
+               $form->setSubmitText( wfMsg( 'ignorewarning' ) );
+               $form->addButton( 'wpCancelUpload', wfMsg( 'reuploaddesc' ) );
+               
+               $this->showUploadForm( $form );
+       }
+       
+       /**
+        * Show the upload form with error message, but do not stash the file.
+        * 
+        * @param string $message
+        */
+       protected function uploadError( $message ) {
+               $message = '<h2>' . wfMsgHtml( 'uploadwarning' ) . "</h2>\n" .
+                       '<div class="error">' . $message . "</div>\n";
+               $this->showUploadForm( $this->getUploadForm( $message ) );
+       }
+       
+       /**
+        * Do the upload.
         * Checks are made in SpecialUpload::execute()
+        */
+       protected function processUpload() {
+               global $wgUser, $wgOut;
+               
+               // Verify permissions
+               $permErrors = $this->mUpload->verifyPermissions( $wgUser );
+               if( $permErrors !== true )
+                       return $wgOut->showPermissionsErrorPage( $permErrors );
+
+               // Fetch the file if required
+               $status = $this->mUpload->fetchFile();
+               if( !$status->isOK() )
+                       return $this->mainUploadForm( $wgOut->parse( $status->getWikiText() ) );
+               
+               // Upload verification
+               $details = $this->mUpload->verifyUpload();
+               if ( $details['status'] != UploadBase::OK )
+                       return $this->processVerificationError( $details );
+               
+               $this->mLocalFile = $this->mUpload->getLocalFile();
+
+               // Check warnings if necessary
+               if( !$this->mIgnoreWarning ) {
+                       $warnings = $this->mUpload->checkWarnings();
+                       if( count( $warnings ) )
+                               return $this->uploadWarning( $warnings );
+               }
+
+               // Get the page text if this is not a reupload
+               if( !$this->mForReUpload ) {
+                       $pageText = self::getInitialPageText( $this->mComment, $this->mLicense,
+                               $this->mCopyrightStatus, $this->mCopyrightSource );
+               } else {
+                       $pageText = false;
+               }
+               $status = $this->mUpload->performUpload( $this->mComment, $pageText, $this->mWatchthis, $wgUser );
+               if ( !$status->isGood() )
+                       return $this->uploadError( $wgOut->parse( $status->getWikiText() ) );
+               
+               // Success, redirect to description page
+               wfRunHooks( 'SpecialUploadComplete', array( &$this ) );
+               $wgOut->redirect( $this->mLocalFile->getTitle()->getFullURL() );
+
+       }
+       
+       /**
+        * Get the initial image page text based on a comment and optional file status information
+        */
+       public static function getInitialPageText( $comment = '', $license = '', $copyStatus = '', $source = '' ) {
+               global $wgUseCopyrightUpload;
+               if ( $wgUseCopyrightUpload ) {
+                       $licensetxt = '';
+                       if ( $license != '' ) {
+                               $licensetxt = '== ' . wfMsgForContent( 'license-header' ) . " ==\n" . '{{' . $license . '}}' . "\n";
+                       }
+                       $pageText = '== ' . wfMsgForContent ( 'filedesc' ) . " ==\n" . $comment . "\n" .
+                         '== ' . wfMsgForContent ( 'filestatus' ) . " ==\n" . $copyStatus . "\n" .
+                         "$licensetxt" .
+                         '== ' . wfMsgForContent ( 'filesource' ) . " ==\n" . $source ;
+               } else {
+                       if ( $license != '' ) {
+                               $filedesc = $comment == '' ? '' : '== ' . wfMsgForContent ( 'filedesc' ) . " ==\n" . $comment . "\n";
+                                $pageText = $filedesc .
+                                        '== ' . wfMsgForContent ( 'license-header' ) . " ==\n" . '{{' . $license . '}}' . "\n";
+                       } else {
+                               $pageText = $comment;
+                       }
+               }
+               return $pageText;
+       }
+       
+       /**
+        * See if we should check the 'watch this page' checkbox on the form
+        * based on the user's preferences and whether we're being asked
+        * to create a new file or update an existing one.
         *
-        * FIXME this should really use the standard Status class (instead of associative array)
-        * FIXME would be nice if we refactored this into the upload api.
-        *  (the special upload page is not the only response point that needs clean localized error msgs)
+        * In the case where 'watch edits' is off but 'watch creations' is on,
+        * we'll leave the box unchecked.
         *
-        * @access private
+        * Note that the page target can be changed *on the form*, so our check
+        * state can get out of sync.
         */
-       function processUpload() {
-               global $wgOut, $wgFileExtensions, $wgLang;
-               $details = $this->internalProcessUpload();
-               switch( $details['status'] ) {
-                       case UploadBase::SUCCESS:
-                               $wgOut->redirect( $this->mLocalFile->getTitle()->getFullURL() );
-                               break;
-
-                       case UploadBase::BEFORE_PROCESSING:
-                               $this->uploadError( $details['error'] );
-                               break;
-                       case UploadBase::LARGE_FILE_SERVER:
-                               $this->mainUploadForm( wfMsgHtml( 'largefileserver' ) );
-                               break;
+       protected function watchCheck() {
+               global $wgUser;
+               if( $wgUser->getOption( 'watchdefault' ) ) {
+                       // Watch all edits!
+                       return true;
+               }
 
-                       case UploadBase::EMPTY_FILE:
-                               $this->mainUploadForm( wfMsgHtml( 'emptyfile' ) );
-                               break;
+               $local = wfLocalFile( $this->mDesiredDestName );
+               if( $local && $local->exists() ) {
+                       // We're uploading a new version of an existing file.
+                       // No creation, so don't watch it if we're not already.
+                       return $local->getTitle()->userIsWatching();
+               } else {
+                       // New page should get watched if that's our option.
+                       return $wgUser->getOption( 'watchcreations' );
+               }
+       }
+       
+       
+       /**
+        * Provides output to the user for a result of UploadBase::verifyUpload
+        * 
+        * @param array $details Result of UploadBase::verifyUpload
+        */
+       protected function processVerificationError( $details ) {
+               global $wgFileExtensions;
+               
+               switch( $details['status'] ) {
 
+                       /** Statuses that only require name changing **/
                        case UploadBase::MIN_LENGTH_PARTNAME:
-                               $this->mainUploadForm( wfMsgHtml( 'minlength1' ) );
+                               $this->recoverableUploadError( wfMsgHtml( 'minlength1' ) );
                                break;
-
                        case UploadBase::ILLEGAL_FILENAME:
-                               $this->uploadError( wfMsgExt( 'illegalfilename',
+                               $this->recoverableUploadError( wfMsgExt( 'illegalfilename',
                                        'parseinline', $details['filtered'] ) );
                                break;
-
-                       case UploadBase::PROTECTED_PAGE:
-                               $wgOut->showPermissionsErrorPage( $details['permissionserrors'] );
-                               break;
-
                        case UploadBase::OVERWRITE_EXISTING_FILE:
-                               $this->uploadError( wfMsgExt( $details['overwrite'],
+                               $this->recoverableUploadError( wfMsgExt( $details['overwrite'],
                                        'parseinline' ) );
                                break;
-
                        case UploadBase::FILETYPE_MISSING:
-                               $this->uploadError( wfMsgExt( 'filetype-missing', array ( 'parseinline' ) ) );
+                               $this->recoverableUploadError( wfMsgExt( 'filetype-missing', 
+                                       'parseinline' ) );
                                break;
 
+                       /** Statuses that require reuploading **/
+                       case UploadBase::EMPTY_FILE:
+                               $this->mainUploadForm( wfMsgHtml( 'emptyfile' ) );
+                               break;
                        case UploadBase::FILETYPE_BADTYPE:
                                $finalExt = $details['finalExt'];
                                $this->uploadError(
@@ -230,103 +444,40 @@ class UploadForm extends SpecialPage {
                                        )
                                );
                                break;
-
                        case UploadBase::VERIFICATION_ERROR:
                                unset( $details['status'] );
                                $code = array_shift( $details['details'] );
                                $this->uploadError( wfMsgExt( $code, 'parseinline', $details['details'] ) );
                                break;
-
-                       case UploadBase::UPLOAD_VERIFICATION_ERROR:
+                       case UploadBase::HOOK_ABORTED:
                                $error = $details['error'];
                                $this->uploadError( wfMsgExt( $error, 'parseinline' ) );
                                break;
-
-                       case UploadBase::UPLOAD_WARNING:
-                               unset( $details['status'] );
-                               $this->uploadWarning( $details );
-                               break;
-
-                       case UploadBase::INTERNAL_ERROR:
-                               $status = $details['internal'];
-                               $this->showError( $wgOut->parse( $status->getWikiText() ) );
-                               break;
-
                        default:
                                throw new MWException( __METHOD__ . ": Unknown value `{$details['status']}`" );
                }
        }
-
+       
        /**
-        * Really do the upload
-        * Checks are made in SpecialUpload::execute()
-        *
-        * @param array $resultDetails contains result-specific dict of additional values
-        *
+        * Remove a temporarily kept file stashed by saveTempUploadedFile().
         * @access private
+        * @return success
         */
-       function internalProcessUpload() {
-               global $wgUser;
-
-               if( !wfRunHooks( 'UploadForm:BeforeProcessing', array( &$this ) ) )
-               {
-                       wfDebug( "Hook 'UploadForm:BeforeProcessing' broke processing the file.\n" );
-                       return array( 'status' => UploadBase::BEFORE_PROCESSING );
-               }
-
-               /**
-                * If the image is protected, non-sysop users won't be able
-                * to modify it by uploading a new revision.
-                */
-               $permErrors = $this->mUpload->verifyPermissions( $wgUser );
-               if( $permErrors !== true ) {
-                       return array( 'status' => UploadBase::PROTECTED_PAGE, 'permissionserrors' => $permErrors );
-               }
-
-               // Fetch the file if required
-               $status = $this->mUpload->fetchFile();
-               if( !$status->isOK() ) {
-                       return array( 'status' => UploadBase::BEFORE_PROCESSING, 'error'=> $status->getWikiText() );
-               }
-
-               // Check whether this is a sane upload
-               $result = $this->mUpload->verifyUpload();
-               if( $result['status'] != UploadBase::OK )
-                       return $result;
-
-               $this->mLocalFile = $this->mUpload->getLocalFile();
-
-               if( !$this->mIgnoreWarning ) {
-                       $warnings = $this->mUpload->checkWarnings();
-
-                       if( count( $warnings ) ) {
-                               $warnings['status'] = UploadBase::UPLOAD_WARNING;
-                               return $warnings;
-                       }
-               }
-
-
-               /**
-                * Try actually saving the thing...
-                * It will show an error form on failure. No it will not.
-                */
-               if( !$this->mForReUpload ) {
-                       $pageText = self::getInitialPageText( $this->mComment, $this->mLicense,
-                               $this->mCopyrightStatus, $this->mCopyrightSource );
-               } else {
-                       $pageText = false;
-               }
-               $status = $this->mUpload->performUpload( $this->mComment, $pageText, $this->mWatchthis, $wgUser );
-
-               if ( !$status->isGood() ) {
-                       return array( 'status' => UploadBase::INTERNAL_ERROR, 'internal' => $status );
+       protected function unsaveUploadedFile() {
+               global $wgOut;
+               if ( !( $this->mUpload instanceof UploadFromStash ) )
+                       return true;
+               $success = $this->mUpload->unsaveUploadedFile();
+               if ( ! $success ) {
+                       $wgOut->showFileDeleteError( $this->mUpload->getTempPath() );
+                       return false;
                } else {
-                       // Success, redirect to description page
-                       wfRunHooks( 'SpecialUploadComplete', array( &$this ) );
-                       return UploadBase::SUCCESS;
+                       return true;
                }
        }
-
+       
+       /*** Functions for formatting warnings ***/
+       
        /**
         * Formats a result of UploadBase::getExistsWarning as HTML
         * This check is static and can be done pre-upload via AJAX
@@ -361,7 +512,7 @@ class UploadForm extends SpecialPage {
                        $warning[] = '<li>' . wfMsgExt( 'fileexists-thumbnail-yes', 'parseinline', 
                                $exists['thumbFile']->getTitle()->getPrefixedText(), $filename ) . '</li>';
                } elseif ( $exists['warning'] == 'thumb-name' ) {
-                       # Image w/o '180px-' does not exists, but we do not like these filenames
+                       // Image w/o '180px-' does not exists, but we do not like these filenames
                        $name = $file->getName();
                        $badPart = substr( $name, 0, strpos( $name, '-' ) + 1 );
                        $warning[] = '<li>' . wfMsgExt( 'file-thumbnail-no', 'parseinline', $badPart ) . '</li>';
@@ -391,7 +542,7 @@ class UploadForm extends SpecialPage {
         * @param string local filename, e.g. 'file exists', 'non-descriptive filename'
         * @return array list of warning messages
         */
-       static function ajaxGetExistsWarning( $filename ) {
+       public static function ajaxGetExistsWarning( $filename ) {
                $file = wfFindFile( $filename );
                if( !$file ) {
                        // Force local file so we have an object to do further checks against
@@ -402,7 +553,6 @@ class UploadForm extends SpecialPage {
                if ( $file ) {
                        $exists = UploadBase::getExistsWarning( $file );
                        $warning = self::getExistsWarning( $exists );
-                       // FIXME: We probably also want the prefix blacklist and the wasdeleted check here
                        if ( $warning !== '' ) {
                                $s = "<ul>$warning</ul>";
                        }
@@ -430,7 +580,7 @@ class UploadForm extends SpecialPage {
        }
 
        /**
-        * Construct the human readable warning message from an array of duplicate files
+        * Construct a warning and a gallery from an array of duplicate files.
         */
        public static function getDupeWarning( $dupes ) {
                if( $dupes ) {
@@ -450,200 +600,122 @@ class UploadForm extends SpecialPage {
                        return '';
                }
        }
+       
+}
 
-
-       /**
-        * Remove a temporarily kept file stashed by saveTempUploadedFile().
-        * @access private
-        * @return success
-        */
-       function unsaveUploadedFile() {
-               global $wgOut;
-               $success = $this->mUpload->unsaveUploadedFile();
-               if ( ! $success ) {
-                       $wgOut->showFileDeleteError( $this->mUpload->getTempPath() );
-                       return false;
-               } else {
-                       return true;
+/**
+ * Sub class of HTMLForm that provides the form section of SpecialUpload 
+ */
+class UploadForm extends HTMLForm {
+       protected $mWatch;
+       protected $mForReUpload;
+       protected $mSessionKey;
+       protected $mSourceIds;
+               
+       public function __construct( $watch, $forReUpload = false, $sessionKey = '' ) {
+               global $wgLang;
+               
+               $this->mWatch = $watch;
+               $this->mForReUpload = $forReUpload;
+               $this->mSessionKey = $sessionKey;
+               
+               $sourceDescriptor = $this->getSourceSection(); 
+               $descriptor = $sourceDescriptor
+                       + $this->getDescriptionSection()
+                       + $this->getOptionsSection();
+               
+               wfRunHooks( 'UploadFormInitDescriptor', array( $descriptor ) );
+               parent::__construct( $descriptor, 'upload' );
+               
+               # Set some form properties
+               $this->setSubmitText( wfMsg( 'uploadbtn' ) );
+               $this->setSubmitName( 'wpUpload' );
+               $this->setSubmitTooltip( 'upload' );
+               $this->setId( 'mw-upload-form' );
+               
+               # Build a list of IDs for javascript insertion
+               $this->mSourceIds = array();
+               foreach ( $sourceDescriptor as $key => $field ) {
+                       if ( !empty( $field['id'] ) )
+                               $this->mSourceIds[] = $field['id'];
                }
+               
        }
-
-       /*              Interface code starts below this line             *
-        * -------------------------------------------------------------- */
-
-
-       /**
-        * @param string $error as HTML
-        * @access private
-        */
-       function uploadError( $error ) {
-               global $wgOut;
-               $wgOut->addHTML( '<h2>' . wfMsgHtml( 'uploadwarning' ) . "</h2>\n" );
-               $wgOut->addHTML( '<span class="error">' . $error . '</span>' );
-       }
-
+       
        /**
-        * There's something wrong with this file, not enough to reject it
-        * totally but we require manual intervention to save it for real.
-        * Stash it away, then present a form asking to confirm or cancel.
-        *
-        * @param string $warning as HTML
-        * @access private
+        * 
         */
-       function uploadWarning( $warnings ) {
-               global $wgOut, $wgUser;
-               global $wgUseCopyrightUpload;
-
-               $this->mSessionKey = $this->mUpload->stashSession();
-
-               if( $this->mSessionKey === false ) {
-                       # Couldn't save file; an error has been displayed so let's go.
-                       return;
-               }
-
-               $sk = $wgUser->getSkin();
-
-               $wgOut->addHTML( '<h2>' . wfMsgHtml( 'uploadwarning' ) . "</h2>\n" );
-               $wgOut->addHTML( '<ul class="warning">' );
-               foreach( $warnings as $warning => $args ) {
-                               $msg = null;
-                               if( $warning == 'exists' ) {
-
-                                       //we should not have produced this warning if the user already acknowledged the destination warning
-                                       //at any rate I don't see why we should hid this warning if mDestWarningAck has been checked
-                                       //(it produces an empty warning page when no other warnings are fired)
-                                       //if ( !$this->mDestWarningAck )
-                                               $msg = self::getExistsWarning( $args );
-
-                               } elseif( $warning == 'duplicate' ) {
-                                       $msg = $this->getDupeWarning( $args );
-                               } elseif( $warning == 'duplicate-archive' ) {
-                                       $titleText = Title::makeTitle( NS_FILE, $args )->getPrefixedText();
-                                       $msg = Xml::tags( 'li', null, wfMsgExt( 'file-deleted-duplicate', array( 'parseinline' ), array( $titleText ) ) );
-                               } else {
-                                       if( is_bool( $args ) )
-                                               $args = array();
-                                       elseif( !is_array( $args ) )
-                                               $args = array( $args );
-                                       $msg = "\t<li>" . wfMsgExt( $warning, 'parseinline', $args ) . "</li>\n";
-                               }
-                               if( $msg )
-                                       $wgOut->addHTML( $msg );
+       protected function getSourceSection() {
+               global $wgLang, $wgUser, $wgRequest;
+               
+               if ( $this->mSessionKey ) {
+                       return array(
+                               'wpSessionKey' => array(
+                                       'type' => 'hidden',
+                                       'default' => $this->mSessionKey,
+                               ),
+                               'wpSourceType' => array(
+                                       'type' => 'hidden',
+                                       'default' => 'Stash',
+                               ),
+                       );
                }
-
-               $titleObj = SpecialPage::getTitleFor( 'Upload' );
-
-               if ( $wgUseCopyrightUpload ) {
-                       $copyright = Xml::hidden( 'wpUploadCopyStatus', $this->mCopyrightStatus ) . "\n" .
-                                       Xml::hidden( 'wpUploadSource', $this->mCopyrightSource ) . "\n";
-               } else {
-                       $copyright = '';
+               
+               $canUploadByUrl = UploadFromUrl::isEnabled() && $wgUser->isAllowed( 'upload_by_url' );
+               $radio = $canUploadByUrl;
+               $selectedSourceType = strtolower( $wgRequest->getText( 'wpSourceType', 'File' ) );
+               
+               $descriptor = array();
+               $descriptor['UploadFile'] = array(
+                               'class' => 'UploadSourceField',
+                               'section' => 'source',
+                               'type' => 'file',
+                               'id' => 'wpUploadFile',
+                               'label-message' => 'sourcefilename',
+                               'upload-type' => 'File',
+                               'radio' => &$radio,
+                               'help' => wfMsgExt( 'upload-maxfilesize', 
+                                               array( 'parseinline', 'escapenoentities' ),
+                                               $wgLang->formatSize( 
+                                                       wfShorthandToInteger( ini_get( 'upload_max_filesize' ) ) 
+                                               )
+                                       ) . ' ' . wfMsgHtml( 'upload_source_file' ),
+                               'checked' => $selectedSourceType == 'file',
+               );
+               if ( $canUploadByUrl ) {
+                       global $wgMaxUploadSize;
+                       $descriptor['UploadFileURL'] = array(
+                               'class' => 'UploadSourceField',
+                               'section' => 'source',
+                               'id' => 'wpUploadFileURL',
+                               'label-message' => 'sourceurl',
+                               'upload-type' => 'Url',
+                               'radio' => &$radio,
+                               'help' => wfMsgExt( 'upload-maxfilesize', 
+                                               array( 'parseinline', 'escapenoentities' ),
+                                               $wgLang->formatSize( $wgMaxUploadSize )
+                                       ) . ' ' . wfMsgHtml( 'upload_source_url' ),
+                               'checked' => $selectedSourceType == 'url',
+                       );
                }
-               $wgOut->addHTML(
-                       Xml::openElement( 'form', array( 'method' => 'post', 'action' => $titleObj->getLocalURL( 'action=submit' ),
-                                'enctype' => 'multipart/form-data', 'id' => 'uploadwarning' ) ) . "\n" .
-                       Xml::hidden('wpEditToken', $wgUser->editToken(), array("id" => 'wpEditToken')) .
-                       Xml::hidden( 'wpIgnoreWarning', '1' ) . "\n" .
-                       Xml::hidden( 'wpSourceType', 'stash' ) . "\n" .
-                       Xml::hidden( 'wpSessionKey', $this->mSessionKey ) . "\n" .
-                       Xml::hidden( 'wpUploadDescription', $this->mComment ) . "\n" .
-                       Xml::hidden( 'wpLicense', $this->mLicense ) . "\n" .
-                       Xml::hidden( 'wpDestFile', $this->mDesiredDestName ) . "\n" .
-                       Xml::hidden( 'wpWatchthis', $this->mWatchthis ) . "\n" .
-                       "{$copyright}<br />" .
-                       Xml::submitButton( wfMsg( 'ignorewarning' ), array ( 'name' => 'wpUpload', 'id' => 'wpUpload', 'checked' => 'checked' ) ) . ' ' .
-                       Xml::submitButton( wfMsg( 'reuploaddesc' ), array ( 'name' => 'wpReUpload', 'id' => 'wpReUpload' ) ) .
-                       Xml::closeElement( 'form' ) . "\n"
+               wfRunHooks( 'UploadFormSourceDescriptors', array( $descriptor, &$radio, $selectedSourceType ) );
+               
+               $descriptor['Extensions'] = array(
+                       'type' => 'info',
+                       'section' => 'source',
+                       'default' => $this->getExtensionsMessage(),
+                       'raw' => true,
                );
+               return $descriptor;             
        }
-
+       
        /**
-        * Displays the main upload form, optionally with a highlighted
-        * error message up at the top.
-        *
-        * @param string $msg as HTML
-        * @access private
+        * 
         */
-       function mainUploadForm( $msg='' ) {
-               global $wgOut, $wgUser, $wgLang, $wgMaxUploadSize, $wgEnableFirefogg;
-               global $wgUseCopyrightUpload, $wgUseAjax, $wgAjaxUploadDestCheck, $wgAjaxLicensePreview;
-               global $wgRequest;
-               global $wgStylePath, $wgStyleVersion;
-               global $wgEnableJS2system;
-
-               $useAjaxDestCheck = $wgUseAjax && $wgAjaxUploadDestCheck;
-               $useAjaxLicensePreview = $wgUseAjax && $wgAjaxLicensePreview;
-
-               $adc = wfBoolToStr( $useAjaxDestCheck );
-               $alp = wfBoolToStr( $useAjaxLicensePreview );
-               $uef = wfBoolToStr( $wgEnableFirefogg );
-               $autofill = wfBoolToStr( $this->mDesiredDestName == '' );
-
-
-               $wgOut->addScript( "<script type=\"text/javascript\">
-wgAjaxUploadDestCheck = {$adc};
-wgAjaxLicensePreview = {$alp};
-wgEnableFirefogg = {$uef};
-wgUploadAutoFill = {$autofill};
-</script>" );
-
-               if( $wgEnableJS2system ) {
-                       //js2version of upload page:
-                       $wgOut->addScriptClass( 'uploadPage' );
-               }else{
-                       //legacy upload code:
-                       $wgOut->addScriptFile( 'upload.js' );
-                       $wgOut->addScriptFile( 'edit.js' ); // For <charinsert> support
-               }
-
-               if( !wfRunHooks( 'UploadForm:initial', array( &$this ) ) )
-               {
-                       wfDebug( "Hook 'UploadForm:initial' broke output of the upload form" );
-                       return false;
-               }
-
-               if( $this->mDesiredDestName != '' ) {
-                       $title = Title::makeTitleSafe( NS_FILE, $this->mDesiredDestName );
-                       // Show a subtitle link to deleted revisions (to sysops et al only)
-                       if( $title instanceof Title && ( $count = $title->isDeleted() ) > 0 && $wgUser->isAllowed( 'deletedhistory' ) ) {
-                               $link = wfMsgExt(
-                                       $wgUser->isAllowed( 'delete' ) ? 'thisisdeleted' : 'viewdeleted',
-                                       array( 'parse', 'replaceafter' ),
-                                       $wgUser->getSkin()->linkKnown(
-                                               SpecialPage::getTitleFor( 'Undelete', $title->getPrefixedText() ),
-                                               wfMsgExt( 'restorelink', array( 'parsemag', 'escape' ), $count )
-                                       )
-                               );
-                               $wgOut->addHTML( "<div id=\"contentSub2\">{$link}</div>" );
-                       }
-
-                       // Show the relevant lines from deletion log (for still deleted files only)
-                       if( $title instanceof Title && $title->isDeletedQuick() && !$title->exists() ) {
-                               $this->showDeletionLog( $wgOut, $title->getPrefixedText() );
-                       }
-               }
-
-               $cols = intval($wgUser->getOption( 'cols' ));
-
-               if( $wgUser->getOption( 'editwidth' ) ) {
-                       $width = " style=\"width:100%\"";
-               } else {
-                       $width = '';
-               }
-
-               if ( '' != $msg ) {
-                       $sub = wfMsgHtml( 'uploaderror' );
-                       $wgOut->addHTML( "<h2>{$sub}</h2>\n" .
-                         "<span class='error'>{$msg}</span>\n" );
-               }
-
-               $wgOut->addHTML( '<div id="uploadtext">' );
-               $wgOut->addWikiMsg( 'uploadtext', $this->mDesiredDestName );
-               $wgOut->addHTML( "</div>\n" );
-
+       protected function getExtensionsMessage() {
                # Print a list of allowed file extensions, if so configured.  We ignore
                # MIME type here, it's incomprehensible to most people and too long.
-               global $wgCheckFileExtensions, $wgStrictFileExtensions,
+               global $wgLang, $wgCheckFileExtensions, $wgStrictFileExtensions,
                $wgFileExtensions, $wgFileBlacklist;
 
                $allowedExtensions = '';
@@ -661,387 +733,202 @@ wgUploadAutoFill = {$autofill};
                                        wfMsgWikiHtml( 'upload-preferred', $wgLang->commaList( $wgFileExtensions ) ) .
                                        "</div>\n" .
                                        '<div id="mw-upload-prohibited">' .
-                                       wfMsgWikiHtml( 'upload-prohibited', $wgLang->commaList( $wgFileExtensions ) ) .
+                                       wfMsgWikiHtml( 'upload-prohibited', $wgLang->commaList( $wgFileBlacklist ) ) .
                                        "</div>\n";
                        }
                } else {
                        # Everything is permitted.
                        $extensionsList = '';
                }
-
-               # Get the maximum file size from php.ini as $wgMaxUploadSize works for uploads from URL via CURL only
-               # See http://www.php.net/manual/en/ini.core.php#ini.upload-max-filesize for possible values of upload_max_filesize
-               $val = wfShorthandToInteger( ini_get( 'upload_max_filesize' ) );
-               $maxUploadSize = '<div id="mw-upload-maxfilesize">' .
-                       wfMsgExt( 'upload-maxfilesize', array( 'parseinline', 'escapenoentities' ),
-                               $wgLang->formatSize( $val ) ) .
-                               "</div>\n";
-               //add a hidden filed for upload by url (uses the $wgMaxUploadSize var)
-               if( UploadFromUrl::isEnabled() ) {
-                       $maxUploadSize.='<div id="mw-upload-maxfilesize-url" style="display:none">' .
-                       wfMsgExt( 'upload-maxfilesize', array( 'parseinline', 'escapenoentities' ),
-                               $wgLang->formatSize( $wgMaxUploadSize ) ) .
-                               "</div>\n";
-               }
-
-               $sourcefilename = wfMsgExt( 'sourcefilename', array( 'parseinline', 'escapenoentities' ) );
-               $destfilename = wfMsgExt( 'destfilename', array( 'parseinline', 'escapenoentities' ) );
-
-               $msg = ( $this->mForReUpload )  ? 'filereuploadsummary' : 'fileuploadsummary';
-               $summary = wfMsgExt( $msg, 'parseinline' );
-
-               $licenses = new Licenses();
-               $license = wfMsgExt( 'license', array( 'parseinline' ) );
-               $nolicense = wfMsgHtml( 'nolicense' );
-               $licenseshtml = $licenses->getHtml();
-
-               $ulb = wfMsgHtml( 'uploadbtn' );
-
-
-               $titleObj = SpecialPage::getTitleFor( 'Upload' );
-
-               $encDestName = htmlspecialchars( $this->mDesiredDestName );
-
-               $watchChecked = $this->watchCheck() ? 'checked="checked"' : '';
-               # Re-uploads should not need "file exist already" warnings
-               $warningChecked = ($this->mIgnoreWarning || $this->mForReUpload) ? 'checked="checked"' : '';
-
-               // Prepare form for upload or upload/copy
-               //javascript moved from inline calls to setup:
-               if( UploadFromUrl::isEnabled() && $wgUser->isAllowed( 'upload_by_url' ) ) {
-                       if( $wgEnableJS2system ) {
-                               $filename_form =
-                                       Xml::input( 'wpSourceType', false, 'file', 
-                                               array( 'id' => 'wpSourceTypeFile', 'type' => 'radio', 'checked' => 'checked' ) ) .
-                                       Xml::input( 'wpUploadFile', 60, false, 
-                                               array( 'id' => 'wpUploadFile', 'type' => 'file', 'tabindex' => '1' ) ) .
-                                       wfMsgHTML( 'upload_source_file' ) . "<br/>" .
-                                       Xml::input( 'wpSourceType', false, 'Url', 
-                                               array( 'id' => 'wpSourceTypeURL', 'type' => 'radio' ) ) .
-                                       Xml::input( 'wpUploadFileURL', 60, false, 
-                                               array( 'id' => 'wpUploadFileURL', 'type' => 'text', 'tabindex' => '1' ) ) .
-                               wfMsgHtml( 'upload_source_url' ) ;
-                       } else {
-                               //@@todo deprecate (not needed once $wgEnableJS2system is turned on)
-                               $filename_form =
-                               "<input type='radio' id='wpSourceTypeFile' name='wpSourceType' value='file' " .
-                                  "onchange='toggle_element_activation(\"wpUploadFileURL\",\"wpUploadFile\")' checked='checked' />" .
-                                "<input tabindex='1' type='file' name='wpUploadFile' id='wpUploadFile' " .
-                                  " onfocus='" .
-                                    "toggle_element_activation(\"wpUploadFileURL\",\"wpUploadFile\");" .
-                                    "toggle_element_check(\"wpSourceTypeFile\",\"wpSourceTypeURL\")' " .
-                                    "onchange='fillDestFilename(\"wpUploadFile\")' size='60' />" .
-                               wfMsgHTML( 'upload_source_file' ) . "<br/>" .
-                               "<input type='radio' id='wpSourceTypeURL' name='wpSourceType' value='Url' " .
-                                 "onchange='toggle_element_activation(\"wpUploadFile\",\"wpUploadFileURL\")' />" .
-                               "<input tabindex='1' type='text' name='wpUploadFileURL' id='wpUploadFileURL' " .
-                                 "onfocus='" .
-                                   "toggle_element_activation(\"wpUploadFile\",\"wpUploadFileURL\");" .
-                                   "toggle_element_check(\"wpSourceTypeURL\",\"wpSourceTypeFile\")' " .
-                                   "onchange='fillDestFilename(\"wpUploadFileURL\")' size='60' />" .
-                               wfMsgHtml( 'upload_source_url' ) ;
-
-                       }
-               } else {
-                       if( $wgEnableJS2system ) {
-                               $filename_form =
-                                       Xml::input( 'wpUploadFile', 60, false, 
-                                               array( 'id' => 'wpUploadFile', 'type' => 'file', 'tabindex' => '1' ) ) .
-                                       Xml::hidden( 'wpSourceType', 'file');
-                       } else {
-                               $filename_form =
-                               "<input tabindex='1' type='file' name='wpUploadFile' id='wpUploadFile' size='60' ".
-                               "onchange='fillDestFilename(\"wpUploadFile\")' />" .
-                               "<input type='hidden' name='wpSourceType' value='file' />" ;
-                       }
-               }
-               $warningRow = '';
-               $destOnkeyup = '';
-               if( $wgEnableJS2system ) {
-                       $warningRow = "<tr><td colspan='2' id='wpDestFile-warning'>&nbsp;</td></tr>";
-               } else {
-                       if ( $useAjaxDestCheck ) {
-                               $warningRow = "<tr><td colspan='2' id='wpDestFile-warning'>&nbsp;</td></tr>";
-                               $destOnkeyup = 'onchange=\'wgUploadWarningObj.checkNow(this.value);\'';
-                       }
+               return $extensionsList;
+       }
+       
+       /**
+        * 
+        */
+       protected function getDescriptionSection() {
+               global $wgUser, $wgOut;
+                               
+               $cols = intval( $wgUser->getOption( 'cols' ) );
+               if( $wgUser->getOption( 'editwidth' ) ) {
+                       $wgOut->addInlineStyle( '#mw-htmlform-description { width: 100%; }' );
                }
-               # Uploading a new version? If so, the name is fixed.
-               $on = $this->mForReUpload ? "readonly='readonly'" : "";
-
-               $encComment = htmlspecialchars( $this->mComment );
-
-               //add the wpEditToken
-               $wgOut->addHTML(
-                       Xml::openElement( 'form', 
-                       array( 'method' => 'post', 'action' => $titleObj->getLocalURL( 'action=submit' ),
-                                'enctype' => 'multipart/form-data', 'id' => 'mw-upload-form' ) ) .
-                       Xml::hidden( 'wpEditToken', $wgUser->editToken(), array( 'id' => 'wpEditToken' ) ) .
-                       Xml::openElement( 'fieldset' ) .
-                       Xml::element( 'legend', null, wfMsg( 'upload' ) ) .
-                       Xml::openElement( 'table', array( 'border' => '0', 'id' => 'mw-upload-table' ) ) .
-                       "<tr>
-                               {$this->uploadFormTextTop}
-                               <td class='mw-label'>
-                                       <label for='wpUploadFile'>{$sourcefilename}</label>
-                               </td>
-                               <td class='mw-input'>
-                                       {$filename_form}
-                               </td>
-                       </tr>
-                       <tr>
-                               <td></td>
-                               <td>
-                                       {$maxUploadSize}
-                                       {$extensionsList}
-                               </td>
-                       </tr>
-                       <tr>
-                               <td class='mw-label'>
-                                       <label for='wpDestFile'>{$destfilename}</label>
-                               </td>
-                               <td class='mw-input'>"
+               
+               $descriptor = array(
+                       'DestFile' => array(
+                               'type' => 'text',
+                               'section' => 'description',
+                               'id' => 'wpDestFile',
+                               'label-message' => 'destfilename',
+                               'size' => 60,
+                       ),
+                       'UploadDescription' => array(
+                               'type' => 'textarea',
+                               'section' => 'description',
+                               'id' => 'wpUploadDescription',
+                               'label-message' => $this->mForReUpload
+                                       ? 'filereuploadsummary' 
+                                       : 'fileuploadsummary',
+                               'cols' => $cols,
+                               'rows' => 8,
+                       ),
+                       'License' => array(
+                               'type' => 'select',
+                               'class' => 'Licenses',
+                               'section' => 'description',
+                               'id' => 'wpLicense',
+                               'label-message' => 'license',
+                       ),
                );
-               if( $this->mForReUpload ) {
-                       $wgOut->addHTML(
-                               Xml::hidden( 'wpDestFile', $this->mDesiredDestName, array('id'=>'wpDestFile','tabindex'=>2) ) .
-                               "<tt>" .
-                               $encDestName .
-                               "</tt>"
-                       );
-               }
-               else {
-                       $wgOut->addHTML(
-                               "<input tabindex='2' type='text' name='wpDestFile' id='wpDestFile' size='60'
-                                               value=\"{$encDestName}\" $destOnkeyup />"
+               if ( $this->mForReUpload )
+                       $descriptor['DestFile']['readonly'] = true;
+               
+               global $wgUseAjax, $wgAjaxLicensePreview;
+               if ( $wgUseAjax && $wgAjaxLicensePreview )
+                       $descriptor['AjaxLicensePreview'] = array( 
+                               'class' => 'UploadAjaxLicensePreview', 
+                               'section' => 'description' 
                        );
-               }
-
-
-               $wgOut->addHTML(
-                               "</td>
-                       </tr>
-                       <tr>
-                               <td class='mw-label'>
-                                       <label for='wpUploadDescription'>{$summary}</label>
-                               </td>
-                               <td class='mw-input'>
-                                       <textarea tabindex='3' name='wpUploadDescription' id='wpUploadDescription' rows='6'
-                                               cols='{$cols}'{$width}>$encComment</textarea>
-                                       {$this->uploadFormTextAfterSummary}
-                               </td>
-                       </tr>
-                       <tr>"
-               );
-
-               # Re-uploads should not need license info
-               if ( !$this->mForReUpload && $licenseshtml != '' ) {
-                       global $wgStylePath;
-                       $wgOut->addHTML( "
-                                       <td class='mw-label'>
-                                               <label for='wpLicense'>$license</label>
-                                       </td>
-                                       <td class='mw-input'>
-                                               <select name='wpLicense' id='wpLicense' tabindex='4'
-                                                       onchange='licenseSelectorCheck()'>
-                                                       <option value=''>$nolicense</option>
-                                                       $licenseshtml
-                                               </select>
-                                       </td>
-                               </tr>
-                               <tr>"
+                       
+               global $wgUseCopyrightUpload;
+               if ( $wgUseCopyrightUpload ) {
+                       $descriptor['UploadCopyStatus'] = array(
+                               'type' => 'text',
+                               'section' => 'description',
+                               'id' => 'wpUploadCopyStatus',
+                               'label-message' => 'filestatus',
                        );
-                       if( $useAjaxLicensePreview ) {
-                               $wgOut->addHTML( "
-                                               <td></td>
-                                               <td id=\"mw-license-preview\"></td>
-                                       </tr>
-                                       <tr>"
-                               );
-                       }
-               }
-
-               if ( !$this->mForReUpload && $wgUseCopyrightUpload ) {
-                       $filestatus = wfMsgExt( 'filestatus', 'escapenoentities' );
-                       $copystatus =  htmlspecialchars( $this->mCopyrightStatus );
-                       $filesource = wfMsgExt( 'filesource', 'escapenoentities' );
-                       $uploadsource = htmlspecialchars( $this->mCopyrightSource );
-
-                       $wgOut->addHTML( "
-                                       <td class='mw-label' style='white-space: nowrap;'>
-                                               <label for='wpUploadCopyStatus'>$filestatus</label></td>
-                                       <td class='mw-input'>
-                                               <input tabindex='5' type='text' name='wpUploadCopyStatus' id='wpUploadCopyStatus'
-                                                       value=\"$copystatus\" size='60' />
-                                       </td>
-                               </tr>
-                               <tr>
-                                       <td class='mw-label'>
-                                               <label for='wpUploadCopyStatus'>$filesource</label>
-                                       </td>
-                                       <td class='mw-input'>
-                                               <input tabindex='6' type='text' name='wpUploadSource' id='wpUploadCopyStatus'
-                                                       value=\"$uploadsource\" size='60' />
-                                       </td>
-                               </tr>
-                               <tr>"
+                       $descriptor['UploadSource'] = array(
+                               'type' => 'text',
+                               'section' => 'description',
+                               'id' => 'wpUploadSource',
+                               'label-message' => 'filesource',
                        );
                }
-
-               $wgOut->addHTML( "
-                               <td></td>
-                               <td>
-                                       <input tabindex='7' type='checkbox' name='wpWatchthis' id='wpWatchthis' $watchChecked value='true' />
-                                       <label for='wpWatchthis'>" . wfMsgHtml( 'watchthisupload' ) . "</label>
-                                       <input tabindex='8' type='checkbox' name='wpIgnoreWarning' id='wpIgnoreWarning' value='true' $warningChecked />
-                                       <label for='wpIgnoreWarning'>" . wfMsgHtml( 'ignorewarnings' ) . "</label>
-                               </td>
-                       </tr>
-                       $warningRow
-                       <tr>
-                               <td></td>
-                                       <td class='mw-input'>
-                                               <input tabindex='9' type='submit' name='wpUpload' value=\"{$ulb}\"" .
-                                                       $wgUser->getSkin()->tooltipAndAccesskey( 'upload' ) . " />
-                                       </td>
-                       </tr>
-                       <tr>
-                               <td></td>
-                               <td class='mw-input'>"
-               );
-               $wgOut->addHTML( '<div class="mw-editTools">' );
-               $wgOut->addWikiMsgArray( 'edittools', array(), array( 'content' ) );
-               $wgOut->addHTML( '</div>' );
-               $wgOut->addHTML( "
-                               </td>
-                       </tr>" .
-                       Xml::closeElement( 'table' ) .
-                       Xml::hidden( 'wpDestFileWarningAck', '', array( 'id' => 'wpDestFileWarningAck' ) ) .
-                       Xml::hidden( 'wpForReUpload', $this->mForReUpload, array( 'id' => 'wpForReUpload' ) ) .
-                       Xml::closeElement( 'fieldset' ) .
-                       Xml::closeElement( 'form' )
-               );
-               $uploadfooter = wfMsgNoTrans( 'uploadfooter' );
-               if( $uploadfooter != '-' && !wfEmptyMsg( 'uploadfooter', $uploadfooter ) ){
-                       $wgOut->addWikiText( '<div id="mw-upload-footer-message">' . $uploadfooter . '</div>' );
-               }
+               
+               return $descriptor;
        }
-
-       /* -------------------------------------------------------------- */
-
+       
        /**
-        * See if we should check the 'watch this page' checkbox on the form
-        * based on the user's preferences and whether we're being asked
-        * to create a new file or update an existing one.
-        *
-        * In the case where 'watch edits' is off but 'watch creations' is on,
-        * we'll leave the box unchecked.
-        *
-        * Note that the page target can be changed *on the form*, so our check
-        * state can get out of sync.
+        * 
         */
-       function watchCheck() {
-               global $wgUser;
-               if( $wgUser->getOption( 'watchdefault' ) ) {
-                       // Watch all edits!
-                       return true;
-               }
-
-               $local = wfLocalFile( $this->mDesiredDestName );
-               if( $local && $local->exists() ) {
-                       // We're uploading a new version of an existing file.
-                       // No creation, so don't watch it if we're not already.
-                       return $local->getTitle()->userIsWatching();
-               } else {
-                       // New page should get watched if that's our option.
-                       return $wgUser->getOption( 'watchcreations' );
-               }
+       protected function getOptionsSection() {
+               global $wgOut;
+               
+               $descriptor = array(
+                       'Watchthis' => array(
+                               'type' => 'check',
+                               'id' => 'wpWatchthis',
+                               'label-message' => 'watchthisupload',
+                               'section' => 'options',
+                       ),
+                       'IgnoreWarning' => array(
+                               'type' => 'check',
+                               'id' => 'wpIgnoreWarning',
+                               'label-message' => 'ignorewarnings',
+                               'section' => 'options',
+                       ),
+                       'EditTools' => array(
+                               'type' => 'edittools',
+                               'section' => 'options',
+                       ),
+               );
+               
+               $uploadFooter = wfMsgNoTrans( 'uploadfooter' );
+               if ( $uploadFooter != '-' && !wfEmptyMsg( 'uploadfooter', $uploadFooter ) )
+                       $descriptor['UploadFooter'] = array(
+                               'type' => 'info',
+                               'id' => 'mw-upload-footer-message',
+                               'default' => $wgOut->parse( $uploadFooter ),
+                       );
+               
+               return $descriptor;
+               
        }
-
+       
        /**
-        * Check if a user is the last uploader
-        *
-        * @param User $user
-        * @param string $img, image name
-        * @return bool
-        * @deprecated Use UploadBase::userCanReUpload
+        * 
         */
-       public static function userCanReUpload( User $user, $img ) {
-               wfDeprecated( __METHOD__ );
-
-               if( $user->isAllowed( 'reupload' ) )
-                       return true; // non-conditional
-               if( !$user->isAllowed( 'reupload-own' ) )
-                       return false;
-
-               $dbr = wfGetDB( DB_SLAVE );
-               $row = $dbr->selectRow('image',
-               /* SELECT */ 'img_user',
-               /* WHERE */ array( 'img_name' => $img )
-               );
-               if ( !$row )
-                       return false;
-
-               return $user->getId() == $row->img_user;
+       public function show() {
+               $this->addUploadJS();
+               parent::show();
        }
-
+       
        /**
-        * Display an error with a wikitext description
+        * 
         */
-       function showError( $description ) {
+       protected function addUploadJS( $autofill = true ) {
+               global $wgUseAjax, $wgAjaxUploadDestCheck, $wgAjaxLicensePreview;
+               global $wgEnableFirefogg, $wgEnableJS2system;
                global $wgOut;
-               $wgOut->setPageTitle( wfMsg( "upload-file-error" ) );
-               $wgOut->setRobotPolicy( "noindex,nofollow" );
-               $wgOut->setArticleRelated( false );
-               $wgOut->enableClientCache( false );
-               $wgOut->addWikiText( $description );
-       }
+               
+               $useAjaxDestCheck = $wgUseAjax && $wgAjaxUploadDestCheck;
+               $useAjaxLicensePreview = $wgUseAjax && $wgAjaxLicensePreview;
+               
+               $scriptVars = array(
+                       'wgAjaxUploadDestCheck' => $wgUseAjax && $wgAjaxUploadDestCheck,
+                       'wgAjaxLicensePreview' => $wgUseAjax && $wgAjaxLicensePreview,
+                       'wgEnableFirefogg' => (bool)$wgEnableFirefogg,
+                       'wgUploadAutoFill' => (bool)$autofill,
+                       'wgUploadSourceIds' => $this->mSourceIds,
+               );
 
-       /**
-        * Get the initial image page text based on a comment and optional file status information
-        */
-       static function getInitialPageText( $comment = '', $license = '', $copyStatus = '', $source = '' ) {
-               global $wgUseCopyrightUpload;
-               if ( $wgUseCopyrightUpload ) {
-                       $licensetxt = '';
-                       if ( $license != '' ) {
-                               $licensetxt = '== ' . wfMsgForContent( 'license-header' ) . " ==\n" . '{{' . $license . '}}' . "\n";
-                       }
-                       $pageText = '== ' . wfMsgForContent ( 'filedesc' ) . " ==\n" . $comment . "\n" .
-                         '== ' . wfMsgForContent ( 'filestatus' ) . " ==\n" . $copyStatus . "\n" .
-                         "$licensetxt" .
-                         '== ' . wfMsgForContent ( 'filesource' ) . " ==\n" . $source ;
+               $wgOut->addScript( Skin::makeVariablesScript( $scriptVars ) );
+
+               if ( $wgEnableJS2system ) {
+                       //js2version of upload page:
+                       $wgOut->addScriptClass( 'uploadPage' );
                } else {
-                       if ( $license != '' ) {
-                               $filedesc = $comment == '' ? '' : '== ' . wfMsgForContent ( 'filedesc' ) . " ==\n" . $comment . "\n";
-                                $pageText = $filedesc .
-                                        '== ' . wfMsgForContent ( 'license-header' ) . " ==\n" . '{{' . $license . '}}' . "\n";
-                       } else {
-                               $pageText = $comment;
-                       }
+                       //legacy upload code:
+                       $wgOut->addScriptFile( 'upload.js' );
+                       $wgOut->addScriptFile( 'edit.js' ); // For <charinsert> support
                }
-               return $pageText;
+       }
+       
+       function trySubmit() {
+               return false;
        }
 
-       /**
-        * If there are rows in the deletion log for this file, show them,
-        * along with a nice little note for the user
-        *
-        * @param OutputPage $out
-        * @param string filename
-        */
-       private function showDeletionLog( $out, $filename ) {
-               global $wgUser;
-               $loglist = new LogEventsList( $wgUser->getSkin(), $out );
-               $pager = new LogPager( $loglist, 'delete', false, $filename );
-               if( $pager->getNumRows() > 0 ) {
-                       $out->addHTML( '<div class="mw-warning-with-logexcerpt">' );
-                       $out->addWikiMsg( 'upload-wasdeleted' );
-                       $out->addHTML(
-                               $loglist->beginLogEventsList() .
-                               $pager->getBody() .
-                               $loglist->endLogEventsList()
+}
+
+/**
+ * TODO: DOCUMENT
+ */
+class UploadSourceField extends HTMLTextField {
+       function getLabelHtml() {
+               $id = "wpSourceType{$this->mParams['upload-type']}";            
+               $label = Html::rawElement( 'label', array( 'for' => $id ), $this->mLabel  );
+                               
+               if ( !empty( $this->mParams['radio'] ) ) {
+                       $attribs = array( 
+                               'name' => 'wpSourceType',
+                               'type' => 'radio',
+                               'id' => $id,
+                               'value' => $this->mParams['upload-type'],
                        );
-                       $out->addHTML( '</div>' );
+                       if ( !empty( $this->mParams['checked'] ) )
+                               $attribs['checked'] = 'checked';
+                       $label .= Html::element( 'input', $attribs );
                }
+               
+               return Html::rawElement( 'td', array( 'class' => 'mw-label' ), $label );
+       }
+       function getSize() {
+               return isset( $this->mParams['size'] ) 
+                       ? $this->mParams['size'] 
+                       : 60;
        }
 }
+
+/**
+ * TODO: Document
+ * TODO: This can be migrated to JS only
+ */
+class UploadAjaxLicensePreview extends HTMLFormField {
+       public function getTableRow( $value ) {
+               return "<tr><td></td><td id=\"mw-license-preview\"></td></tr>\n";
+       }
+       public function getInputHTML( $value ) {
+               return '';
+       }
+}
\ No newline at end of file
index 3f8b1d2..5c2929f 100644 (file)
@@ -1,18 +1,18 @@
 <?php
 /**
- * @file
+ * @file 
  * @ingroup upload
- *
+ * 
  * UploadBase and subclasses are the backend of MediaWiki's file uploads.
  * The frontends are formed by ApiUpload and SpecialUpload.
- *
+ * 
  * See also includes/docs/upload.txt
- *
+ * 
  * @author Brion Vibber
  * @author Bryan Tong Minh
  * @author Michael Dale
  */
-
 abstract class UploadBase {
        protected $mTempPath;
        protected $mDesiredDestName, $mDestName, $mRemoveTempFile, $mSourceType;
@@ -22,19 +22,15 @@ abstract class UploadBase {
 
        const SUCCESS = 0;
        const OK = 0;
-       const BEFORE_PROCESSING = 1;
-       const LARGE_FILE_SERVER = 2;
        const EMPTY_FILE = 3;
        const MIN_LENGTH_PARTNAME = 4;
        const ILLEGAL_FILENAME = 5;
-       const PROTECTED_PAGE = 6;
        const OVERWRITE_EXISTING_FILE = 7;
        const FILETYPE_MISSING = 8;
        const FILETYPE_BADTYPE = 9;
        const VERIFICATION_ERROR = 10;
        const UPLOAD_VERIFICATION_ERROR = 11;
-       const UPLOAD_WARNING = 12;
-       const INTERNAL_ERROR = 13;
+       const HOOK_ABORTED = 11;
        const MIN_LENGHT_PARTNAME = 14;
 
        const SESSION_VERSION = 2;
@@ -117,7 +113,7 @@ abstract class UploadBase {
                $this->mFileSize = $fileSize;
                $this->mRemoveTempFile = $removeTempFile;
        }
-
+       
        /**
         * Initialize from a WebRequest. Override this in a subclass.
         */
@@ -136,12 +132,12 @@ abstract class UploadBase {
        public function isEmptyFile(){
                return empty( $this->mFileSize );
        }
-
-       /*
-        * getRealPath
-        * @param string $srcPath the source path
-        * @returns the real path if it was a virtual url
-        */
+       
+       /**
+     * getRealPath
+     * @param string $srcPath the source path
+     * @returns the real path if it was a virtual url
+     */
        function getRealPath( $srcPath ){
                $repo = RepoGroup::singleton()->getLocalRepo();
                if ( $repo->isVirtualUrl( $srcPath ) ) {
@@ -160,7 +156,21 @@ abstract class UploadBase {
                 */
                if( $this->isEmptyFile() )
                        return array( 'status' => self::EMPTY_FILE );
+               
+               /**
+                * Look at the contents of the file; if we can recognize the
+                * type but it's corrupt or data of the wrong type, we should
+                * probably not accept it.
+                */
+               $verification = $this->verifyFile();
+               if( $verification !== true ) {
+                       if( !is_array( $verification ) )
+                               $verification = array( $verification );
+                       return array( 'status' => self::VERIFICATION_ERROR,
+                                       'details' => $verification );
 
+               }
+               
                $nt = $this->getTitle();
                if( is_null( $nt ) ) {
                        $result = array( 'status' => $this->mTitleError );
@@ -179,25 +189,11 @@ abstract class UploadBase {
                if( $overwrite !== true )
                        return array( 'status' => self::OVERWRITE_EXISTING_FILE, 'overwrite' => $overwrite );
 
-               /**
-                * Look at the contents of the file; if we can recognize the
-                * type but it's corrupt or data of the wrong type, we should
-                * probably not accept it.
-                */
-               $verification = $this->verifyFile();
-
-               if( $verification !== true ) {
-                       if( !is_array( $verification ) )
-                               $verification = array( $verification );
-                       return array( 'status' => self::VERIFICATION_ERROR,
-                                       'details' => $verification );
-               }
-
                $error = '';
                if( !wfRunHooks( 'UploadVerification',
                                array( $this->mDestName, $this->mTempPath, &$error ) ) ) {
                        // This status needs another name...
-                       return array( 'status' => self::UPLOAD_VERIFICATION_ERROR, 'error' => $error );
+                       return array( 'status' => self::HOOK_ABORTED, 'error' => $error );
                }
 
                return array( 'status' => self::OK );
@@ -221,7 +217,7 @@ abstract class UploadBase {
 
                #check mime type, if desired
                global $wgVerifyMimeType;
-               if ( $wgVerifyMimeType ) {
+               if ( $wgVerifyMimeType ) {              
                        global $wgMimeTypeBlacklist;
                        if ( $this->checkFileExtension( $mime, $wgMimeTypeBlacklist ) )
                                return array( 'filetype-badmime', $mime );
@@ -262,7 +258,7 @@ abstract class UploadBase {
 
        /**
         * Check whether the user can edit, upload and create the image.
-        *
+        * 
         * @param User $user the user to verify the permissions against
         * @return mixed An array as returned by getUserPermissionsErrors or true
         *               in case the user has proper permissions.
@@ -288,7 +284,7 @@ abstract class UploadBase {
 
        /**
         * Check for non fatal problems with the file
-        *
+        * 
         * @return array Array of warnings
         */
        public function checkWarnings() {
@@ -304,7 +300,10 @@ abstract class UploadBase {
                 * but ignore things like ucfirst() and spaces/underscore things
                 */
                $comparableName = str_replace( ' ', '_', $this->mDesiredDestName );
-               $comparableName = Title::capitalize( $comparableName, NS_FILE );
+               global $wgCapitalLinks, $wgContLang;
+               if ( $wgCapitalLinks ) {
+                       $comparableName = $wgContLang->ucfirst( $comparableName );
+               }
                if( $this->mDesiredDestName != $filename && $comparableName != $filename )
                        $warnings['badfilename'] = $filename;
 
@@ -348,9 +347,9 @@ abstract class UploadBase {
        }
 
        /**
-        * Really perform the upload. Stores the file in the local repo, watches
+        * Really perform the upload. Stores the file in the local repo, watches 
         * if necessary and runs the UploadComplete hook.
-        *
+        * 
         * @return mixed Status indicating the whether the upload succeeded.
         */
        public function performUpload( $comment, $pageText, $watch, $user ) {
@@ -358,13 +357,8 @@ abstract class UploadBase {
                $status = $this->getLocalFile()->upload( $this->mTempPath, $comment, $pageText,
                        File::DELETE_SOURCE, $this->mFileProps, false, $user );
 
-               if( $status->isGood() && $watch ){
-                       //make sure the watch commit happens inline
-                       $dbw = wfGetDB(DB_MASTER);
-                       $dbw->begin();
-                               $user->addWatch( $this->getLocalFile()->getTitle() );
-                       $dbw->commit();
-               }
+               if( $status->isGood() && $watch )
+                       $user->addWatch( $this->getLocalFile()->getTitle() );
 
                if( $status->isGood() )
                        wfRunHooks( 'UploadComplete', array( &$this ) );
@@ -375,7 +369,7 @@ abstract class UploadBase {
        /**
         * Returns the title of the file to be uploaded. Sets mTitleError in case
         * the name was illegal.
-        *
+        * 
         * @return Title The title of the file or null in case the name was illegal
         */
        public function getTitle() {
@@ -444,7 +438,7 @@ abstract class UploadBase {
        }
 
        /**
-        * Return the local file and initializes if necessary.
+        * Return the local file and initializes if necessary. 
         */
        public function getLocalFile() {
                if( is_null( $this->mLocalFile ) ) {
@@ -472,9 +466,9 @@ abstract class UploadBase {
                return $status;
        }
 
-       /**
+       /** 
         * Append a file to a stashed file.
-        *
+        * 
         * @param string $srcPath Path to file to append from
         * @param string $toAppendPath Path to file to append to
         * @return Status Status
@@ -507,7 +501,7 @@ abstract class UploadBase {
                        'mFileSize'       => $this->mFileSize,
                        'mFileProps'      => $this->mFileProps,
                        'version'         => self::SESSION_VERSION,
-               );
+               );              
                return $key;
        }
 
@@ -520,15 +514,6 @@ abstract class UploadBase {
                return $key;
        }
 
-       /**
-        * Remove a temporarily kept file stashed by saveTempUploadedFile().
-        * @return success
-        */
-       public function unsaveUploadedFile() {
-               $repo = RepoGroup::singleton()->getLocalRepo();
-               $success = $repo->freeTemp( $this->mTempPath );
-               return $success;
-       }
 
        /**
         * If we've modified the upload file we need to manually remove it
@@ -817,8 +802,11 @@ abstract class UploadBase {
                #      that does not seem to be worth the pain.
                #      Ask me (Duesentrieb) about it if it's ever needed.
                $output = array();
-               $output = wfShellExec("$command 2>&1", $exitCode);
-
+               if ( wfIsWindows() ) {
+                       exec( "$command", $output, $exitCode );
+               } else {
+                       exec( "$command 2>&1", $output, $exitCode );
+               }
 
                # map exit code to AV_xxx constants.
                $mappedCode = $exitCode;
@@ -830,7 +818,6 @@ abstract class UploadBase {
                        }
                }
 
-
                if ( $mappedCode === AV_SCAN_FAILED ) {
                        # scan failed (code was mapped to false by $exitCodeMap)
                        wfDebug( __METHOD__ . ": failed to scan $file (code $exitCode).\n" );
@@ -912,9 +899,9 @@ abstract class UploadBase {
                                return true;
                }
 
-               /* Check shared conflicts: if the local file does not exist, but
-                * wfFindFile finds a file, it exists in a shared repository.
-                */
+               /* Check shared conflicts: if the local file does not exist, but 
+                * wfFindFile finds a file, it exists in a shared repository. 
+                */ 
                $file = wfFindFile( $this->getTitle() );
                if ( $file && !$wgUser->isAllowed( 'reupload-shared' ) )
                        return 'fileexists-shared-forbidden';
@@ -944,13 +931,13 @@ abstract class UploadBase {
 
        /**
         * Helper function that does various existence checks for a file.
-        * The following checks are performed:
+        * The following checks are performed: 
         * - The file exists
         * - Article with the same name as the file exists
         * - File exists with normalized extension
         * - The file looks like a thumbnail and the original exists
-        *
-        * @param File $file The file to check
+        * 
+        * @param File $file The file to check 
         * @return mixed False if the file does not exists, else an array
         */
        public static function getExistsWarning( $file ) {
@@ -959,10 +946,10 @@ abstract class UploadBase {
 
                if( $file->getTitle()->getArticleID() )
                        return array( 'warning' => 'page-exists', 'file' => $file );
-
+               
                if ( $file->wasDeleted() && !$file->exists() )
-                       return array( 'warning' => 'was-deleted', 'file' => $file );
-
+                       return array( 'warning' => 'was-deleted', 'file' => $file );            
+                       
                if( strpos( $file->getName(), '.' ) == false ) {
                        $partname = $file->getName();
                        $extension = '';
@@ -996,13 +983,13 @@ abstract class UploadBase {
                                // File does not exist, but we just don't like the name
                                return array( 'warning' => 'thumb-name', 'file' => $file, 'thumbFile' => $file_thb );
                }
-
+               
 
                foreach( self::getFilenamePrefixBlacklist() as $prefix ) {
                        if ( substr( $partname, 0, strlen( $prefix ) ) == $prefix )
                                return array( 'warning' => 'bad-prefix', 'file' => $file, 'prefix' => $prefix );
                }
-
+               
 
 
                return false;
index 6fbeb51..cb5926f 100644 (file)
@@ -41,6 +41,7 @@ class UploadFromStash extends UploadBase {
                                false
                        );
 
+                       $this->mVirtualTempPath = $sessionData['mTempPath'];
                        $this->mFileProps = $sessionData['mFileProps'];
        }
 
@@ -68,4 +69,14 @@ class UploadFromStash extends UploadBase {
                return array();
        }
 
+       /**
+        * Remove a temporarily kept file stashed by saveTempUploadedFile().
+        * @return success
+        */
+       public function unsaveUploadedFile() {
+               $repo = RepoGroup::singleton()->getLocalRepo();
+               $success = $repo->freeTemp( $this->mVirtualTempPath );
+               return $success;
+       }
+
 }
\ No newline at end of file
index 77220a2..c288e70 100644 (file)
@@ -2038,8 +2038,7 @@ Pages on [[Special:Watchlist|your watchlist]] are '''bold'''.",
 # Upload
 'upload'                      => 'Upload file',
 'uploadbtn'                   => 'Upload file',
-'reupload'                    => 'Re-upload',
-'reuploaddesc'                => 'Cancel upload and return to the upload form',
+'reuploaddesc'                => 'Cancel upload and return to the upload form ',
 'uploadnologin'               => 'Not logged in',
 'uploadnologintext'           => 'You must be [[Special:UserLogin|logged in]] to upload files.',
 'upload_directory_missing'    => 'The upload directory ($1) is missing and could not be created by the webserver.',
@@ -2126,9 +2125,13 @@ Please check the file_uploads setting.',
 'uploadcorrupt'               => 'The file is corrupt or has an incorrect extension.
 Please check the file and upload again.',
 'uploadvirus'                 => 'The file contains a virus! Details: $1',
+'upload-source'               => 'Source file',
 'sourcefilename'              => 'Source filename:',
+'sourceurl'                   => 'Source URL:',
 'destfilename'                => 'Destination filename:',
 'upload-maxfilesize'          => 'Maximum file size: $1',
+'upload-description'          => 'File description',
+'upload-options'              => '',
 'watchthisupload'             => 'Watch this file',
 'filewasdeleted'              => 'A file of this name has been previously uploaded and subsequently deleted.
 You should check the $1 before proceeding to upload it again.',
index 3e91e06..3d79209 100644 (file)
@@ -34,6 +34,75 @@ function wgUploadSetup() {
                        }
                }
        }
+       
+       // Toggle source type
+       var sourceTypeCheckboxes = document.getElementsByName( 'wpSourceType' );
+       for ( var i = 0; i < sourceTypeCheckboxes.length; i++ ) {
+               sourceTypeCheckboxes[i].onchange = toggleUploadInputs;
+       }
+       
+       // AJAX wpDestFile warnings
+       if ( wgAjaxUploadDestCheck ) {
+               document.getElementById( 'wpDestFile' ).onchange = function ( e ) { 
+                       wgUploadWarningObj.checkNow(this.value);
+               };
+               var optionsTable = document.getElementById( 'mw-htmlform-options' ).tBodies[0];
+               var row = document.createElement( 'tr' );
+               var td = document.createElement( 'td' );
+               td.id = 'wpDestFile-warning';
+               td.colSpan = 2;
+               row.appendChild( td );
+               optionsTable.appendChild( row );
+       }
+       
+       // License selector check
+       document.getElementById( 'wpLicense' ).onchange = licenseSelectorCheck;
+       
+       // fillDestFile setup
+       for ( var i = 0; i < wgUploadSourceIds.length; i++ )
+               document.getElementById( wgUploadSourceIds[i] ).onchange = function (e) {
+                       fillDestFilename( this.id );
+               };
+}
+
+/**
+ * Iterate over all upload source fields and disable all except the selected one.
+ * 
+ * @param enabledId The id of the selected radio button 
+ * @return emptiness
+ */
+function toggleUploadInputs() {
+       // Iterate over all rows with UploadSourceField
+       var rows;
+       if ( document.getElementsByClassName ) {
+               rows = document.getElementsByClassName( 'mw-htmlform-field-UploadSourceField' );
+       } else {
+               // Older browsers don't support getElementsByClassName
+               rows = new Array();
+               
+               var allRows = document.getElementsByTagName( 'tr' );
+               for ( var i = 0; i < allRows.length; i++ ) {
+                       if ( allRows[i].className == 'mw-htmlform-field-UploadSourceField' )
+                               rows.push( allRows[i] );
+               }
+       }
+       
+       for ( var i = 0; i < rows.length; i++ ) {
+               var inputs = rows[i].getElementsByTagName( 'input' );
+               
+               // Check if this row is selected
+               var isChecked = true; // Default true in case wpSourceType is not found
+               for ( var j = 0; j < inputs.length; j++ ) {
+                       if ( inputs[j].name == 'wpSourceType' )
+                               isChecked = inputs[j].checked;
+               }
+               
+               // Disable all unselected rows
+               for ( var j = 0; j < inputs.length; j++ ) {
+                       if ( inputs[j].type != 'radio')
+                               inputs[j].disabled = !isChecked;
+               }
+       }
 }
 
 var wgUploadWarningObj = {
@@ -86,7 +155,7 @@ var wgUploadWarningObj = {
                // ajax requests can be supported.
                var obj = this;
                var fileName = this.nameToCheck;
-               sajax_do_call( 'UploadForm::ajaxGetExistsWarning', [this.nameToCheck],
+               sajax_do_call( 'SpecialUpload::ajaxGetExistsWarning', [this.nameToCheck],
                        function (result) {
                                obj.processResult(result, fileName)
                        }
@@ -101,16 +170,7 @@ var wgUploadWarningObj = {
 
        'setWarning' : function (warning) {
                var warningElt = document.getElementById( 'wpDestFile-warning' );
-               var ackElt = document.getElementById( 'wpDestFileWarningAck' );
                this.setInnerHTML(warningElt, warning);
-
-               // Set a value in the form indicating that the warning is acknowledged and
-               // doesn't need to be redisplayed post-upload
-               if ( warning == '' || warning == '&nbsp;' ) {
-                       ackElt.value = '';
-               } else {
-                       ackElt.value = '1';
-               }
        },
        'setInnerHTML' : function (element, text) {
                // Check for no change to avoid flicker in IE 7
@@ -180,6 +240,7 @@ function fillDestFilename(id) {
        }
 
        // Capitalise first letter and replace spaces by underscores
+       // FIXME: $wgCapitalizedNamespaces
        fname = fname.charAt(0).toUpperCase().concat(fname.substring(1,10000)).replace(/ /g, '_');
 
        // Output result
@@ -214,7 +275,7 @@ var wgUploadLicenseObj = {
                        }
                }
                injectSpinner( document.getElementById( 'wpLicense' ), 'license' );
-               sajax_do_call( 'UploadForm::ajaxGetLicensePreview', [license],
+               sajax_do_call( 'SpecialUpload::ajaxGetLicensePreview', [license],
                        function( result ) {
                                wgUploadLicenseObj.processResult( result, license );
                        }
index 06bbff3..922d4e1 100644 (file)
@@ -469,36 +469,6 @@ function checkboxClickHandler(e) {
        return true;
 }
 
-function toggle_element_activation(ida,idb) {
-       if ( !document.getElementById ) {
-               return;
-       }
-       // Show the appropriate upload size limit message
-       if( idb == 'wpUploadFileURL' ) {
-               var e = document.getElementById( 'mw-upload-maxfilesize' );
-               if( e ) e.style.display = "none";
-
-               var e = document.getElementById( 'mw-upload-maxfilesize-url' );
-               if( e ) e.style.display = "block";
-       }
-       if( idb == 'wpUploadFile' ) {
-               var e = document.getElementById( 'mw-upload-maxfilesize-url' );
-               if( e ) e.style.display =  "none";
-
-               var e = document.getElementById( 'mw-upload-maxfilesize' );
-               if( e ) e.style.display =  "block";
-       }
-       document.getElementById( ida ).disabled = true;
-       document.getElementById( idb ).disabled = false;
-}
-
-function toggle_element_check(ida,idb) {
-       if (!document.getElementById) {
-               return;
-       }
-       document.getElementById(ida).checked=true;
-       document.getElementById(idb).checked=false;
-}
 
 /*
        Written by Jonathan Snook, http://www.snook.ca/jonathan