]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blobdiff - includes/specials/SpecialUpload.php
MediaWiki 1.30.2
[autoinstallsdev/mediawiki.git] / includes / specials / SpecialUpload.php
index 96696ab3d229cce50ca02a37e437b5007d51687d..024034a6284f2b2e6c9668c7739e4a96ba36cbcc 100644 (file)
@@ -22,6 +22,8 @@
  * @ingroup Upload
  */
 
+use MediaWiki\MediaWikiServices;
+
 /**
  * Form for handling uploads and special page.
  *
  */
 class SpecialUpload extends SpecialPage {
        /**
-        * Constructor : initialise object
         * Get data POSTed through the form and assign them to the object
-        * @param $request WebRequest : data posted.
+        * @param WebRequest $request Data posted.
         */
        public function __construct( $request = null ) {
-               global $wgRequest;
-
                parent::__construct( 'Upload', 'upload' );
+       }
 
-               $this->loadRequest( is_null( $request ) ? $wgRequest : $request );
+       public function doesWrites() {
+               return true;
        }
 
        /** Misc variables **/
-       public $mRequest;                       // The WebRequest or FauxRequest this form is supposed to handle
+
+       /** @var WebRequest|FauxRequest The request this form is supposed to handle */
+       public $mRequest;
        public $mSourceType;
+
+       /** @var UploadBase */
        public $mUpload;
+
+       /** @var LocalFile */
        public $mLocalFile;
        public $mUploadClicked;
 
        /** User input variables from the "description" section **/
-       public $mDesiredDestName;       // The requested target file name
+
+       /** @var string The requested target file name */
+       public $mDesiredDestName;
        public $mComment;
        public $mLicense;
 
        /** User input variables from the root section **/
+
        public $mIgnoreWarning;
-       public $mWatchThis;
+       public $mWatchthis;
        public $mCopyrightStatus;
        public $mCopyrightSource;
 
        /** Hidden variables **/
+
        public $mDestWarningAck;
-       public $mForReUpload;           // The user followed an "overwrite this file" link
-       public $mCancelUpload;          // The user clicked "Cancel and return to upload form" button
+
+       /** @var bool The user followed an "overwrite this file" link */
+       public $mForReUpload;
+
+       /** @var bool The user clicked "Cancel and return to upload form" button */
+       public $mCancelUpload;
        public $mTokenOk;
-       public $mUploadSuccessful = false;      // Subclasses can use this to determine whether a file was uploaded
+
+       /** @var bool Subclasses can use this to determine whether a file was uploaded */
+       public $mUploadSuccessful = false;
 
        /** Text injection points for hooks not using HTMLForm **/
        public $uploadFormTextTop;
@@ -73,43 +90,44 @@ class SpecialUpload extends SpecialPage {
 
        /**
         * Initialize instance variables from request and create an Upload handler
-        *
-        * @param $request WebRequest: the request to extract variables from
         */
-       protected function loadRequest( $request ) {
-               global $wgUser;
-
-               $this->mRequest = $request;
-               $this->mSourceType        = $request->getVal( 'wpSourceType', 'file' );
-               $this->mUpload            = UploadBase::createFromRequest( $request );
-               $this->mUploadClicked     = $request->wasPosted()
+       protected function loadRequest() {
+               $this->mRequest = $request = $this->getRequest();
+               $this->mSourceType = $request->getVal( 'wpSourceType', 'file' );
+               $this->mUpload = UploadBase::createFromRequest( $request );
+               $this->mUploadClicked = $request->wasPosted()
                        && ( $request->getCheck( 'wpUpload' )
                                || $request->getCheck( 'wpUploadIgnoreWarning' ) );
 
                // Guess the desired name from the filename if not provided
-               $this->mDesiredDestName   = $request->getText( 'wpDestFile' );
-               if( !$this->mDesiredDestName && $request->getFileName( 'wpUploadFile' ) !== null ) {
+               $this->mDesiredDestName = $request->getText( 'wpDestFile' );
+               if ( !$this->mDesiredDestName && $request->getFileName( 'wpUploadFile' ) !== null ) {
                        $this->mDesiredDestName = $request->getFileName( 'wpUploadFile' );
                }
-               $this->mComment           = $request->getText( 'wpUploadDescription' );
-               $this->mLicense           = $request->getText( 'wpLicense' );
+               $this->mLicense = $request->getText( 'wpLicense' );
 
-
-               $this->mDestWarningAck    = $request->getText( 'wpDestFileWarningAck' );
-               $this->mIgnoreWarning     = $request->getCheck( 'wpIgnoreWarning' )
+               $this->mDestWarningAck = $request->getText( 'wpDestFileWarningAck' );
+               $this->mIgnoreWarning = $request->getCheck( 'wpIgnoreWarning' )
                        || $request->getCheck( 'wpUploadIgnoreWarning' );
-               $this->mWatchthis         = $request->getBool( 'wpWatchthis' ) && $wgUser->isLoggedIn();
-               $this->mCopyrightStatus   = $request->getText( 'wpUploadCopyStatus' );
-               $this->mCopyrightSource   = $request->getText( 'wpUploadSource' );
+               $this->mWatchthis = $request->getBool( 'wpWatchthis' ) && $this->getUser()->isLoggedIn();
+               $this->mCopyrightStatus = $request->getText( 'wpUploadCopyStatus' );
+               $this->mCopyrightSource = $request->getText( 'wpUploadSource' );
+
+               $this->mForReUpload = $request->getBool( 'wpForReUpload' ); // updating a file
 
+               $commentDefault = '';
+               $commentMsg = wfMessage( 'upload-default-description' )->inContentLanguage();
+               if ( !$this->mForReUpload && !$commentMsg->isDisabled() ) {
+                       $commentDefault = $commentMsg->plain();
+               }
+               $this->mComment = $request->getText( 'wpUploadDescription', $commentDefault );
 
-               $this->mForReUpload       = $request->getBool( 'wpForReUpload' ); // updating a file
-               $this->mCancelUpload      = $request->getCheck( 'wpCancelUpload' )
-                                        || $request->getCheck( 'wpReUpload' ); // b/w compat
+               $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' );
-               $this->mTokenOk = $wgUser->matchEditToken( $token );
+               $this->mTokenOk = $this->getUser()->matchEditToken( $token );
 
                $this->uploadFormTextTop = '';
                $this->uploadFormTextAfterSummary = '';
@@ -120,54 +138,59 @@ class SpecialUpload extends SpecialPage {
         * Handle permission checking elsewhere in order to be able to show
         * custom error messages.
         *
-        * @param $user User object
-        * @return Boolean
+        * @param User $user
+        * @return bool
         */
-       public function userCanExecute( $user ) {
+       public function userCanExecute( User $user ) {
                return UploadBase::isEnabled() && parent::userCanExecute( $user );
        }
 
        /**
         * Special page entry point
+        * @param string $par
+        * @throws ErrorPageError
+        * @throws Exception
+        * @throws FatalError
+        * @throws MWException
+        * @throws PermissionsError
+        * @throws ReadOnlyError
+        * @throws UserBlockedError
         */
        public function execute( $par ) {
-               global $wgUser, $wgOut;
+               $this->useTransactionalTimeLimit();
 
                $this->setHeaders();
                $this->outputHeader();
 
                # Check uploading enabled
-               if( !UploadBase::isEnabled() ) {
-                       $wgOut->showErrorPage( 'uploaddisabled', 'uploaddisabledtext' );
-                       return;
+               if ( !UploadBase::isEnabled() ) {
+                       throw new ErrorPageError( 'uploaddisabled', 'uploaddisabledtext' );
                }
 
+               $this->addHelpLink( 'Help:Managing files' );
+
                # Check permissions
-               global $wgGroupPermissions;
-               $permissionRequired = UploadBase::isAllowed( $wgUser );
-               if( $permissionRequired !== true ) {
-                       if( !$wgUser->isLoggedIn() && ( $wgGroupPermissions['user']['upload']
-                               || $wgGroupPermissions['autoconfirmed']['upload'] ) ) {
-                               // Custom message if logged-in users without any special rights can upload
-                               $wgOut->showErrorPage( 'uploadnologin', 'uploadnologintext' );
-                       } else {
-                               $wgOut->permissionRequired( $permissionRequired );
-                       }
-                       return;
+               $user = $this->getUser();
+               $permissionRequired = UploadBase::isAllowed( $user );
+               if ( $permissionRequired !== true ) {
+                       throw new PermissionsError( $permissionRequired );
                }
 
                # Check blocks
-               if( $wgUser->isBlocked() ) {
-                       $wgOut->blockedPage();
-                       return;
+               if ( $user->isBlocked() ) {
+                       throw new UserBlockedError( $user->getBlock() );
                }
 
-               # Check whether we actually want to allow changing stuff
-               if( wfReadOnly() ) {
-                       $wgOut->readOnlyPage();
-                       return;
+               // Global blocks
+               if ( $user->isBlockedGlobally() ) {
+                       throw new UserBlockedError( $user->getGlobalBlock() );
                }
 
+               # Check whether we actually want to allow changing stuff
+               $this->checkReadOnly();
+
+               $this->loadRequest();
+
                # Unsave the temporary file in case this was a cancelled upload
                if ( $this->mCancelUpload ) {
                        if ( !$this->unsaveUploadedFile() ) {
@@ -180,17 +203,17 @@ class SpecialUpload extends SpecialPage {
                if (
                        $this->mTokenOk && !$this->mCancelUpload &&
                        ( $this->mUpload && $this->mUploadClicked )
-               )
-               {
+               ) {
                        $this->processUpload();
                } else {
                        # Backwards compatibility hook
-                       if( !wfRunHooks( 'UploadForm:initial', array( &$this ) ) ) {
-                               wfDebug( "Hook 'UploadForm:initial' broke output of the upload form" );
+                       // Avoid PHP 7.1 warning of passing $this by reference
+                       $upload = $this;
+                       if ( !Hooks::run( 'UploadForm:initial', [ &$upload ] ) ) {
+                               wfDebug( "Hook 'UploadForm:initial' broke output of the upload form\n" );
+
                                return;
                        }
-                       
-
                        $this->showUploadForm( $this->getUploadForm() );
                }
 
@@ -203,36 +226,34 @@ class SpecialUpload extends SpecialPage {
        /**
         * Show the main upload form
         *
-        * @param $form Mixed: an HTMLForm instance or HTML string to show
+        * @param HTMLForm|string $form An HTMLForm instance or HTML string to show
         */
        protected function showUploadForm( $form ) {
                # Add links if file was previously deleted
-               if ( !$this->mDesiredDestName ) {
+               if ( $this->mDesiredDestName ) {
                        $this->showViewDeletedLinks();
                }
 
                if ( $form instanceof HTMLForm ) {
                        $form->show();
                } else {
-                       global $wgOut;
-                       $wgOut->addHTML( $form );
+                       $this->getOutput()->addHTML( $form );
                }
-
        }
 
        /**
         * Get an UploadForm instance with title and text properly set.
         *
-        * @param $message String: HTML string to add to the form
-        * @param $sessionKey String: session key in case this is a stashed upload
-        * @param $hideIgnoreWarning Boolean: whether to hide "ignore warning" check box
+        * @param string $message HTML string to add to the form
+        * @param string $sessionKey Session key in case this is a stashed upload
+        * @param bool $hideIgnoreWarning Whether to hide "ignore warning" check box
         * @return UploadForm
         */
        protected function getUploadForm( $message = '', $sessionKey = '', $hideIgnoreWarning = false ) {
-               global $wgOut;
-
                # Initialize form
-               $form = new UploadForm( array(
+               $context = new DerivativeContext( $this->getContext() );
+               $context->setTitle( $this->getPageTitle() ); // Remove subpage
+               $form = new UploadForm( [
                        'watch' => $this->getWatchCheck(),
                        'forreupload' => $this->mForReUpload,
                        'sessionkey' => $sessionKey,
@@ -243,78 +264,69 @@ class SpecialUpload extends SpecialPage {
                        'texttop' => $this->uploadFormTextTop,
                        'textaftersummary' => $this->uploadFormTextAfterSummary,
                        'destfile' => $this->mDesiredDestName,
-               ) );
-               $form->setTitle( $this->getTitle() );
+               ], $context, $this->getLinkRenderer() );
 
                # Check the token, but only if necessary
-               if(
+               if (
                        !$this->mTokenOk && !$this->mCancelUpload &&
                        ( $this->mUpload && $this->mUploadClicked )
-               )
-               {
-                       $form->addPreText( wfMsgExt( 'session_fail_preview', 'parseinline' ) );
+               ) {
+                       $form->addPreText( $this->msg( 'session_fail_preview' )->parse() );
                }
 
                # Give a notice if the user is uploading a file that has been deleted or moved
-               # Note that this is independent from the message 'filewasdeleted' that requires JS
+               # Note that this is independent from the message 'filewasdeleted'
                $desiredTitleObj = Title::makeTitleSafe( NS_FILE, $this->mDesiredDestName );
                $delNotice = ''; // empty by default
                if ( $desiredTitleObj instanceof Title && !$desiredTitleObj->exists() ) {
-                       LogEventsList::showLogExtract( $delNotice, array( 'delete', 'move' ), 
-                               $desiredTitleObj->getPrefixedText(),
-                               '', array( 'lim' => 10,
-                                          'conds' => array( "log_action != 'revision'" ),
-                                          'showIfEmpty' => false,
-                                          'msgKey' => array( 'upload-recreate-warning' ) )
+                       $dbr = wfGetDB( DB_REPLICA );
+
+                       LogEventsList::showLogExtract( $delNotice, [ 'delete', 'move' ],
+                               $desiredTitleObj,
+                               '', [ 'lim' => 10,
+                                       'conds' => [ 'log_action != ' . $dbr->addQuotes( 'revision' ) ],
+                                       'showIfEmpty' => false,
+                                       'msgKey' => [ 'upload-recreate-warning' ] ]
                        );
                }
                $form->addPreText( $delNotice );
 
                # Add text to form
-               $form->addPreText( '<div id="uploadtext">' . 
-                       wfMsgExt( 'uploadtext', 'parse', array( $this->mDesiredDestName ) ) . 
+               $form->addPreText( '<div id="uploadtext">' .
+                       $this->msg( 'uploadtext', [ $this->mDesiredDestName ] )->parseAsBlock() .
                        '</div>' );
                # Add upload error message
                $form->addPreText( $message );
 
                # Add footer to form
-               $uploadFooter = wfMsgNoTrans( 'uploadfooter' );
-               if ( $uploadFooter != '-' && !wfEmptyMsg( 'uploadfooter', $uploadFooter ) ) {
+               $uploadFooter = $this->msg( 'uploadfooter' );
+               if ( !$uploadFooter->isDisabled() ) {
                        $form->addPostText( '<div id="mw-upload-footer-message">'
-                               . $wgOut->parse( $uploadFooter ) . "</div>\n" );
+                               . $uploadFooter->parseAsBlock() . "</div>\n" );
                }
 
                return $form;
-
        }
 
        /**
         * Shows the "view X deleted revivions link""
         */
        protected function showViewDeletedLinks() {
-               global $wgOut, $wgUser;
-
                $title = Title::makeTitleSafe( NS_FILE, $this->mDesiredDestName );
+               $user = $this->getUser();
                // Show a subtitle link to deleted revisions (to sysops et al only)
-               if( $title instanceof Title ) {
+               if ( $title instanceof Title ) {
                        $count = $title->isDeleted();
-                       if ( $count > 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 )
-                                       )
+                       if ( $count > 0 && $user->isAllowed( 'deletedhistory' ) ) {
+                               $restorelink = $this->getLinkRenderer()->makeKnownLink(
+                                       SpecialPage::getTitleFor( 'Undelete', $title->getPrefixedText() ),
+                                       $this->msg( 'restorelink' )->numParams( $count )->text()
                                );
-                               $wgOut->addHTML( "<div id=\"contentSub2\">{$link}</div>" );
+                               $link = $this->msg( $user->isAllowed( 'delete' ) ? 'thisisdeleted' : 'viewdeleted' )
+                                       ->rawParams( $restorelink )->parseAsBlock();
+                               $this->getOutput()->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() );
-               }
        }
 
        /**
@@ -326,67 +338,125 @@ class SpecialUpload extends SpecialPage {
         * essentially means that UploadBase::VERIFICATION_ERROR and
         * UploadBase::EMPTY_FILE should not be passed here.
         *
-        * @param $message String: HTML message to be passed to mainUploadForm
+        * @param string $message HTML message to be passed to mainUploadForm
         */
        protected function showRecoverableUploadError( $message ) {
-               $sessionKey = $this->mUpload->stashSession();
-               $message = '<h2>' . wfMsgHtml( 'uploadwarning' ) . "</h2>\n" .
+               $stashStatus = $this->mUpload->tryStashFile( $this->getUser() );
+               if ( $stashStatus->isGood() ) {
+                       $sessionKey = $stashStatus->getValue()->getFileKey();
+                       $uploadWarning = 'upload-tryagain';
+               } else {
+                       $sessionKey = null;
+                       $uploadWarning = 'upload-tryagain-nostash';
+               }
+               $message = '<h2>' . $this->msg( 'uploaderror' )->escaped() . "</h2>\n" .
                        '<div class="error">' . $message . "</div>\n";
 
                $form = $this->getUploadForm( $message, $sessionKey );
-               $form->setSubmitText( wfMsg( 'upload-tryagain' ) );
+               $form->setSubmitText( $this->msg( $uploadWarning )->escaped() );
                $this->showUploadForm( $form );
        }
+
        /**
-        * Stashes the upload, shows the main form, but adds an "continue anyway button".
+        * Stashes the upload, shows the main form, but adds a "continue anyway button".
         * Also checks whether there are actually warnings to display.
         *
-        * @param $warnings Array
-        * @return boolean true if warnings were displayed, false if there are no
-        *      warnings and the should continue processing like there was no warning
+        * @param array $warnings
+        * @return bool True if warnings were displayed, false if there are no
+        *   warnings and it should continue processing
         */
        protected function showUploadWarning( $warnings ) {
                # If there are no warnings, or warnings we can ignore, return early.
                # mDestWarningAck is set when some javascript has shown the warning
                # to the user. mForReUpload is set when the user clicks the "upload a
                # new version" link.
-               if ( !$warnings || ( count( $warnings ) == 1 && 
-                       isset( $warnings['exists'] ) && 
-                       ( $this->mDestWarningAck || $this->mForReUpload ) ) )
-               {
+               if ( !$warnings || ( count( $warnings ) == 1
+                       && isset( $warnings['exists'] )
+                       && ( $this->mDestWarningAck || $this->mForReUpload ) )
+               {
                        return false;
                }
 
-               $sessionKey = $this->mUpload->stashSession();
+               $stashStatus = $this->mUpload->tryStashFile( $this->getUser() );
+               if ( $stashStatus->isGood() ) {
+                       $sessionKey = $stashStatus->getValue()->getFileKey();
+                       $uploadWarning = 'uploadwarning-text';
+               } else {
+                       $sessionKey = null;
+                       $uploadWarning = 'uploadwarning-text-nostash';
+               }
+
+               // Add styles for the warning, reused from the live preview
+               $this->getOutput()->addModuleStyles( 'mediawiki.special.upload.styles' );
 
-               $warningHtml = '<h2>' . wfMsgHtml( 'uploadwarning' ) . "</h2>\n"
-                       . '<ul class="warning">';
-               foreach( $warnings as $warning => $args ) {
-                       if( $warning == 'exists' ) {
+               $linkRenderer = $this->getLinkRenderer();
+               $warningHtml = '<h2>' . $this->msg( 'uploadwarning' )->escaped() . "</h2>\n"
+                       . '<div class="mw-destfile-warning"><ul>';
+               foreach ( $warnings as $warning => $args ) {
+                       if ( $warning == 'badfilename' ) {
+                               $this->mDesiredDestName = Title::makeTitle( NS_FILE, $args )->getText();
+                       }
+                       if ( $warning == 'exists' ) {
                                $msg = "\t<li>" . self::getExistsWarning( $args ) . "</li>\n";
-                       } 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";
+                       } elseif ( $warning == 'no-change' ) {
+                               $file = $args;
+                               $filename = $file->getTitle()->getPrefixedText();
+                               $msg = "\t<li>" . wfMessage( 'fileexists-no-change', $filename )->parse() . "</li>\n";
+                       } elseif ( $warning == 'duplicate-version' ) {
+                               $file = $args[0];
+                               $count = count( $args );
+                               $filename = $file->getTitle()->getPrefixedText();
+                               $message = wfMessage( 'fileexists-duplicate-version' )
+                                       ->params( $filename )
+                                       ->numParams( $count );
+                               $msg = "\t<li>" . $message->parse() . "</li>\n";
+                       } elseif ( $warning == 'was-deleted' ) {
+                               # If the file existed before and was deleted, warn the user of this
+                               $ltitle = SpecialPage::getTitleFor( 'Log' );
+                               $llink = $linkRenderer->makeKnownLink(
+                                       $ltitle,
+                                       wfMessage( 'deletionlog' )->text(),
+                                       [],
+                                       [
+                                               'type' => 'delete',
+                                               'page' => Title::makeTitle( NS_FILE, $args )->getPrefixedText(),
+                                       ]
+                               );
+                               $msg = "\t<li>" . wfMessage( 'filewasdeleted' )->rawParams( $llink )->parse() . "</li>\n";
+                       } elseif ( $warning == 'duplicate' ) {
+                               $msg = $this->getDupeWarning( $args );
+                       } elseif ( $warning == 'duplicate-archive' ) {
+                               if ( $args === '' ) {
+                                       $msg = "\t<li>" . $this->msg( 'file-deleted-duplicate-notitle' )->parse()
+                                               . "</li>\n";
+                               } else {
+                                       $msg = "\t<li>" . $this->msg( 'file-deleted-duplicate',
+                                                       Title::makeTitle( NS_FILE, $args )->getPrefixedText() )->parse()
+                                               . "</li>\n";
+                               }
                        } else {
                                if ( $args === true ) {
-                                       $args = array();
+                                       $args = [];
                                } elseif ( !is_array( $args ) ) {
-                                       $args = array( $args );
+                                       $args = [ $args ];
                                }
-                               $msg = "\t<li>" . wfMsgExt( $warning, 'parseinline', $args ) . "</li>\n";
+                               $msg = "\t<li>" . $this->msg( $warning, $args )->parse() . "</li>\n";
                        }
                        $warningHtml .= $msg;
                }
-               $warningHtml .= "</ul>\n";
-               $warningHtml .= wfMsgExt( 'uploadwarning-text', 'parse' );
+               $warningHtml .= "</ul></div>\n";
+               $warningHtml .= $this->msg( $uploadWarning )->parseAsBlock();
 
                $form = $this->getUploadForm( $warningHtml, $sessionKey, /* $hideIgnoreWarning */ true );
-               $form->setSubmitText( wfMsg( 'upload-tryagain' ) );
-               $form->addButton( 'wpUploadIgnoreWarning', wfMsg( 'ignorewarning' ) );
-               $form->addButton( 'wpCancelUpload', wfMsg( 'reuploaddesc' ) );
+               $form->setSubmitText( $this->msg( 'upload-tryagain' )->text() );
+               $form->addButton( [
+                       'name' => 'wpUploadIgnoreWarning',
+                       'value' => $this->msg( 'ignorewarning' )->text()
+               ] );
+               $form->addButton( [
+                       'name' => 'wpCancelUpload',
+                       'value' => $this->msg( 'reuploaddesc' )->text()
+               ] );
 
                $this->showUploadForm( $form );
 
@@ -397,10 +467,10 @@ class SpecialUpload extends SpecialPage {
        /**
         * Show the upload form with error message, but do not stash the file.
         *
-        * @param $message HTML string
+        * @param string $message HTML string
         */
        protected function showUploadError( $message ) {
-               $message = '<h2>' . wfMsgHtml( 'uploadwarning' ) . "</h2>\n" .
+               $message = '<h2>' . $this->msg( 'uploadwarning' )->escaped() . "</h2>\n" .
                        '<div class="error">' . $message . "</div>\n";
                $this->showUploadForm( $this->getUploadForm( $message ) );
        }
@@ -410,16 +480,16 @@ class SpecialUpload extends SpecialPage {
         * Checks are made in SpecialUpload::execute()
         */
        protected function processUpload() {
-               global $wgUser, $wgOut;
-
                // Fetch the file if required
                $status = $this->mUpload->fetchFile();
-               if( !$status->isOK() ) {
-                       $this->showUploadError( $wgOut->parse( $status->getWikiText() ) );
+               if ( !$status->isOK() ) {
+                       $this->showUploadError( $this->getOutput()->parse( $status->getWikiText() ) );
+
                        return;
                }
-
-               if( !wfRunHooks( 'UploadForm:BeforeProcessing', array( &$this ) ) ) {
+               // Avoid PHP 7.1 warning of passing $this by reference
+               $upload = $this;
+               if ( !Hooks::run( 'UploadForm:BeforeProcessing', [ &$upload ] ) ) {
                        wfDebug( "Hook 'UploadForm:BeforeProcessing' broke processing the file.\n" );
                        // This code path is deprecated. If you want to break upload processing
                        // do so by hooking into the appropriate hooks in UploadBase::verifyUpload
@@ -429,89 +499,138 @@ class SpecialUpload extends SpecialPage {
                        return;
                }
 
-
                // Upload verification
                $details = $this->mUpload->verifyUpload();
                if ( $details['status'] != UploadBase::OK ) {
                        $this->processVerificationError( $details );
+
                        return;
                }
-               
+
                // Verify permissions for this title
-               $permErrors = $this->mUpload->verifyPermissions( $wgUser );
-               if( $permErrors !== true ) {
+               $permErrors = $this->mUpload->verifyTitlePermissions( $this->getUser() );
+               if ( $permErrors !== true ) {
                        $code = array_shift( $permErrors[0] );
-                       $this->showRecoverableUploadError( wfMsgExt( $code,
-                                       'parseinline', $permErrors[0] ) );
+                       $this->showRecoverableUploadError( $this->msg( $code, $permErrors[0] )->parse() );
+
                        return;
                }
 
                $this->mLocalFile = $this->mUpload->getLocalFile();
 
                // Check warnings if necessary
-               if( !$this->mIgnoreWarning ) {
+               if ( !$this->mIgnoreWarning ) {
                        $warnings = $this->mUpload->checkWarnings();
-                       if( $this->showUploadWarning( $warnings ) ) {
+                       if ( $this->showUploadWarning( $warnings ) ) {
                                return;
                        }
                }
 
+               // This is as late as we can throttle, after expected issues have been handled
+               if ( UploadBase::isThrottled( $this->getUser() ) ) {
+                       $this->showRecoverableUploadError(
+                               $this->msg( 'actionthrottledtext' )->escaped()
+                       );
+                       return;
+               }
+
                // Get the page text if this is not a reupload
-               if( !$this->mForReUpload ) {
+               if ( !$this->mForReUpload ) {
                        $pageText = self::getInitialPageText( $this->mComment, $this->mLicense,
-                               $this->mCopyrightStatus, $this->mCopyrightSource );
+                               $this->mCopyrightStatus, $this->mCopyrightSource, $this->getConfig() );
                } else {
                        $pageText = false;
                }
-               $status = $this->mUpload->performUpload( $this->mComment, $pageText, $this->mWatchthis, $wgUser );
+
+               $changeTags = $this->getRequest()->getVal( 'wpChangeTags' );
+               if ( is_null( $changeTags ) || $changeTags === '' ) {
+                       $changeTags = [];
+               } else {
+                       $changeTags = array_filter( array_map( 'trim', explode( ',', $changeTags ) ) );
+               }
+
+               if ( $changeTags ) {
+                       $changeTagsStatus = ChangeTags::canAddTagsAccompanyingChange(
+                               $changeTags, $this->getUser() );
+                       if ( !$changeTagsStatus->isOK() ) {
+                               $this->showUploadError( $this->getOutput()->parse( $changeTagsStatus->getWikiText() ) );
+
+                               return;
+                       }
+               }
+
+               $status = $this->mUpload->performUpload(
+                       $this->mComment,
+                       $pageText,
+                       $this->mWatchthis,
+                       $this->getUser(),
+                       $changeTags
+               );
+
                if ( !$status->isGood() ) {
-                       $this->showUploadError( $wgOut->parse( $status->getWikiText() ) );
+                       $this->showRecoverableUploadError( $this->getOutput()->parse( $status->getWikiText() ) );
+
                        return;
                }
 
                // Success, redirect to description page
                $this->mUploadSuccessful = true;
-               wfRunHooks( 'SpecialUploadComplete', array( &$this ) );
-               $wgOut->redirect( $this->mLocalFile->getTitle()->getFullURL() );
+               // Avoid PHP 7.1 warning of passing $this by reference
+               $upload = $this;
+               Hooks::run( 'SpecialUploadComplete', [ &$upload ] );
+               $this->getOutput()->redirect( $this->mLocalFile->getTitle()->getFullURL() );
        }
 
        /**
         * Get the initial image page text based on a comment and optional file status information
+        * @param string $comment
+        * @param string $license
+        * @param string $copyStatus
+        * @param string $source
+        * @param Config $config Configuration object to load data from
+        * @return string
         */
-       public static function getInitialPageText( $comment = '', $license = '', $copyStatus = '', $source = '' ) {
-               global $wgUseCopyrightUpload, $wgForceUIMsgAsContentMsg;
-               $wgForceUIMsgAsContentMsg = (array) $wgForceUIMsgAsContentMsg;
+       public static function getInitialPageText( $comment = '', $license = '',
+               $copyStatus = '', $source = '', Config $config = null
+       ) {
+               if ( $config === null ) {
+                       wfDebug( __METHOD__ . ' called without a Config instance passed to it' );
+                       $config = MediaWikiServices::getInstance()->getMainConfig();
+               }
 
+               $msg = [];
+               $forceUIMsgAsContentMsg = (array)$config->get( 'ForceUIMsgAsContentMsg' );
                /* These messages are transcluded into the actual text of the description page.
                 * Thus, forcing them as content messages makes the upload to produce an int: template
                 * instead of hardcoding it there in the uploader language.
                 */
-               foreach( array( 'license-header', 'filedesc', 'filestatus', 'filesource' ) as $msgName ) {
-                       if ( in_array( $msgName, $wgForceUIMsgAsContentMsg ) ) {
+               foreach ( [ 'license-header', 'filedesc', 'filestatus', 'filesource' ] as $msgName ) {
+                       if ( in_array( $msgName, $forceUIMsgAsContentMsg ) ) {
                                $msg[$msgName] = "{{int:$msgName}}";
                        } else {
-                               $msg[$msgName] = wfMsgForContent( $msgName );
+                               $msg[$msgName] = wfMessage( $msgName )->inContentLanguage()->text();
                        }
                }
 
-               if ( $wgUseCopyrightUpload ) {
+               if ( $config->get( 'UseCopyrightUpload' ) ) {
                        $licensetxt = '';
                        if ( $license != '' ) {
-                               $licensetxt = '== ' . $msg[ 'license-header' ] . " ==\n" . '{{' . $license . '}}' . "\n";
+                               $licensetxt = '== ' . $msg['license-header'] . " ==\n" . '{{' . $license . '}}' . "\n";
                        }
-                       $pageText = '== ' . $msg[ 'filedesc' ] . " ==\n" . $comment . "\n" .
-                               '== ' . $msg[ 'filestatus' ] . " ==\n" . $copyStatus . "\n" .
+                       $pageText = '== ' . $msg['filedesc'] . " ==\n" . $comment . "\n" .
+                               '== ' . $msg['filestatus'] . " ==\n" . $copyStatus . "\n" .
                                "$licensetxt" .
-                               '== ' . $msg[ 'filesource' ] . " ==\n" . $source;
+                               '== ' . $msg['filesource'] . " ==\n" . $source;
                } else {
                        if ( $license != '' ) {
-                               $filedesc = $comment == '' ? '' : '== ' . $msg[ 'filedesc' ] . " ==\n" . $comment . "\n";
+                               $filedesc = $comment == '' ? '' : '== ' . $msg['filedesc'] . " ==\n" . $comment . "\n";
                                        $pageText = $filedesc .
-                                       '== ' . $msg[ 'license-header' ] . " ==\n" . '{{' . $license . '}}' . "\n";
+                                       '== ' . $msg['license-header'] . " ==\n" . '{{' . $license . '}}' . "\n";
                        } else {
                                $pageText = $comment;
                        }
                }
+
                return $pageText;
        }
 
@@ -525,74 +644,91 @@ class SpecialUpload extends SpecialPage {
         *
         * Note that the page target can be changed *on the form*, so our check
         * state can get out of sync.
+        * @return bool|string
         */
        protected function getWatchCheck() {
-               global $wgUser;
-               if( $wgUser->getOption( 'watchdefault' ) ) {
+               if ( $this->getUser()->getOption( 'watchdefault' ) ) {
                        // Watch all edits!
                        return true;
                }
 
+               $desiredTitleObj = Title::makeTitleSafe( NS_FILE, $this->mDesiredDestName );
+               if ( $desiredTitleObj instanceof Title && $this->getUser()->isWatched( $desiredTitleObj ) ) {
+                       // Already watched, don't change that
+                       return true;
+               }
+
                $local = wfLocalFile( $this->mDesiredDestName );
-               if( $local && $local->exists() ) {
+               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();
+                       return false;
                } else {
                        // New page should get watched if that's our option.
-                       return $wgUser->getOption( 'watchcreations' );
+                       return $this->getUser()->getOption( 'watchcreations' ) ||
+                               $this->getUser()->getOption( 'watchuploads' );
                }
        }
 
-
        /**
         * Provides output to the user for a result of UploadBase::verifyUpload
         *
-        * @param $details Array: result of UploadBase::verifyUpload
+        * @param array $details Result of UploadBase::verifyUpload
+        * @throws MWException
         */
        protected function processVerificationError( $details ) {
-               global $wgFileExtensions, $wgLang;
-
-               switch( $details['status'] ) {
-
+               switch ( $details['status'] ) {
                        /** Statuses that only require name changing **/
                        case UploadBase::MIN_LENGTH_PARTNAME:
-                               $this->showRecoverableUploadError( wfMsgHtml( 'minlength1' ) );
+                               $this->showRecoverableUploadError( $this->msg( 'minlength1' )->escaped() );
                                break;
                        case UploadBase::ILLEGAL_FILENAME:
-                               $this->showRecoverableUploadError( wfMsgExt( 'illegalfilename',
-                                       'parseinline', $details['filtered'] ) );
+                               $this->showRecoverableUploadError( $this->msg( 'illegalfilename',
+                                       $details['filtered'] )->parse() );
+                               break;
+                       case UploadBase::FILENAME_TOO_LONG:
+                               $this->showRecoverableUploadError( $this->msg( 'filename-toolong' )->escaped() );
                                break;
                        case UploadBase::FILETYPE_MISSING:
-                               $this->showRecoverableUploadError( wfMsgExt( 'filetype-missing',
-                                       'parseinline' ) );
+                               $this->showRecoverableUploadError( $this->msg( 'filetype-missing' )->parse() );
+                               break;
+                       case UploadBase::WINDOWS_NONASCII_FILENAME:
+                               $this->showRecoverableUploadError( $this->msg( 'windows-nonascii-filename' )->parse() );
                                break;
 
                        /** Statuses that require reuploading **/
                        case UploadBase::EMPTY_FILE:
-                               $this->showUploadError( wfMsgHtml( 'emptyfile' ) );
+                               $this->showUploadError( $this->msg( 'emptyfile' )->escaped() );
                                break;
                        case UploadBase::FILE_TOO_LARGE:
-                               $this->showUploadError( wfMsgHtml( 'largefileserver' ) );
+                               $this->showUploadError( $this->msg( 'largefileserver' )->escaped() );
                                break;
                        case UploadBase::FILETYPE_BADTYPE:
-                               $finalExt = $details['finalExt'];
-                               $this->showUploadError(
-                                       wfMsgExt( 'filetype-banned-type',
-                                               array( 'parseinline' ),
-                                               htmlspecialchars( $finalExt ),
-                                               implode(
-                                                       wfMsgExt( 'comma-separator', array( 'escapenoentities' ) ),
-                                                       $wgFileExtensions
-                                               ),
-                                               $wgLang->formatNum( count( $wgFileExtensions ) )
-                                       )
-                               );
+                               $msg = $this->msg( 'filetype-banned-type' );
+                               if ( isset( $details['blacklistedExt'] ) ) {
+                                       $msg->params( $this->getLanguage()->commaList( $details['blacklistedExt'] ) );
+                               } else {
+                                       $msg->params( $details['finalExt'] );
+                               }
+                               $extensions = array_unique( $this->getConfig()->get( 'FileExtensions' ) );
+                               $msg->params( $this->getLanguage()->commaList( $extensions ),
+                                       count( $extensions ) );
+
+                               // Add PLURAL support for the first parameter. This results
+                               // in a bit unlogical parameter sequence, but does not break
+                               // old translations
+                               if ( isset( $details['blacklistedExt'] ) ) {
+                                       $msg->params( count( $details['blacklistedExt'] ) );
+                               } else {
+                                       $msg->params( 1 );
+                               }
+
+                               $this->showUploadError( $msg->parse() );
                                break;
                        case UploadBase::VERIFICATION_ERROR:
                                unset( $details['status'] );
                                $code = array_shift( $details['details'] );
-                               $this->showUploadError( wfMsgExt( $code, 'parseinline', $details['details'] ) );
+                               $this->showUploadError( $this->msg( $code, $details['details'] )->parse() );
                                break;
                        case UploadBase::HOOK_ABORTED:
                                if ( is_array( $details['error'] ) ) { # allow hooks to return error details in an array
@@ -603,7 +739,7 @@ class SpecialUpload extends SpecialPage {
                                        $args = null;
                                }
 
-                               $this->showUploadError( wfMsgExt( $error, 'parseinline', $args ) );
+                               $this->showUploadError( $this->msg( $error, $args )->parse() );
                                break;
                        default:
                                throw new MWException( __METHOD__ . ": Unknown value `{$details['status']}`" );
@@ -613,16 +749,16 @@ class SpecialUpload extends SpecialPage {
        /**
         * Remove a temporarily kept file stashed by saveTempUploadedFile().
         *
-        * @return Boolean: success
+        * @return bool Success
         */
        protected function unsaveUploadedFile() {
-               global $wgOut;
                if ( !( $this->mUpload instanceof UploadFromStash ) ) {
                        return true;
                }
                $success = $this->mUpload->unsaveUploadedFile();
                if ( !$success ) {
-                       $wgOut->showFileDeleteError( $this->mUpload->getTempPath() );
+                       $this->getOutput()->showFileDeleteError( $this->mUpload->getTempPath() );
+
                        return false;
                } else {
                        return true;
@@ -635,486 +771,79 @@ class SpecialUpload extends SpecialPage {
         * Formats a result of UploadBase::getExistsWarning as HTML
         * This check is static and can be done pre-upload via AJAX
         *
-        * @param $exists Array: the result of UploadBase::getExistsWarning
-        * @return String: empty string if there is no warning or an HTML fragment
+        * @param array $exists The result of UploadBase::getExistsWarning
+        * @return string Empty string if there is no warning or an HTML fragment
         */
        public static function getExistsWarning( $exists ) {
-               global $wgUser;
-
                if ( !$exists ) {
                        return '';
                }
 
                $file = $exists['file'];
                $filename = $file->getTitle()->getPrefixedText();
-               $warning = '';
-
-               $sk = $wgUser->getSkin();
+               $warnMsg = null;
 
-               if( $exists['warning'] == 'exists' ) {
+               if ( $exists['warning'] == 'exists' ) {
                        // Exact match
-                       $warning = wfMsgExt( 'fileexists', 'parseinline', $filename );
-               } elseif( $exists['warning'] == 'page-exists' ) {
+                       $warnMsg = wfMessage( 'fileexists', $filename );
+               } elseif ( $exists['warning'] == 'page-exists' ) {
                        // Page exists but file does not
-                       $warning = wfMsgExt( 'filepageexists', 'parseinline', $filename );
+                       $warnMsg = wfMessage( 'filepageexists', $filename );
                } elseif ( $exists['warning'] == 'exists-normalized' ) {
-                       $warning = wfMsgExt( 'fileexists-extension', 'parseinline', $filename,
+                       $warnMsg = wfMessage( 'fileexists-extension', $filename,
                                $exists['normalizedFile']->getTitle()->getPrefixedText() );
                } elseif ( $exists['warning'] == 'thumb' ) {
                        // Swapped argument order compared with other messages for backwards compatibility
-                       $warning = wfMsgExt( 'fileexists-thumbnail-yes', 'parseinline',
+                       $warnMsg = wfMessage( 'fileexists-thumbnail-yes',
                                $exists['thumbFile']->getTitle()->getPrefixedText(), $filename );
                } elseif ( $exists['warning'] == 'thumb-name' ) {
                        // 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 = wfMsgExt( 'file-thumbnail-no', 'parseinline', $badPart );
+                       $warnMsg = wfMessage( 'file-thumbnail-no', $badPart );
                } elseif ( $exists['warning'] == 'bad-prefix' ) {
-                       $warning = wfMsgExt( 'filename-bad-prefix', 'parseinline', $exists['prefix'] );
-               } elseif ( $exists['warning'] == 'was-deleted' ) {
-                       # If the file existed before and was deleted, warn the user of this
-                       $ltitle = SpecialPage::getTitleFor( 'Log' );
-                       $llink = $sk->linkKnown(
-                               $ltitle,
-                               wfMsgHtml( 'deletionlog' ),
-                               array(),
-                               array(
-                                       'type' => 'delete',
-                                       'page' => $filename
-                               )
-                       );
-                       $warning = wfMsgWikiHtml( 'filewasdeleted', $llink );
+                       $warnMsg = wfMessage( 'filename-bad-prefix', $exists['prefix'] );
                }
 
-               return $warning;
-       }
-
-       /**
-        * Get a list of warnings
-        *
-        * @param $filename String: local filename, e.g. 'file exists', 'non-descriptive filename'
-        * @return Array: list of warning messages
-        */
-       public static function ajaxGetExistsWarning( $filename ) {
-               $file = wfFindFile( $filename );
-               if( !$file ) {
-                       // Force local file so we have an object to do further checks against
-                       // if there isn't an exact match...
-                       $file = wfLocalFile( $filename );
-               }
-               $s = '&#160;';
-               if ( $file ) {
-                       $exists = UploadBase::getExistsWarning( $file );
-                       $warning = self::getExistsWarning( $exists );
-                       if ( $warning !== '' ) {
-                               $s = "<div>$warning</div>";
-                       }
-               }
-               return $s;
+               return $warnMsg ? $warnMsg->title( $file->getTitle() )->parse() : '';
        }
 
        /**
         * Construct a warning and a gallery from an array of duplicate files.
+        * @param array $dupes
+        * @return string
         */
-       public static function getDupeWarning( $dupes ) {
-               if( $dupes ) {
-                       global $wgOut;
-                       $msg = '<gallery>';
-                       foreach( $dupes as $file ) {
-                               $title = $file->getTitle();
-                               $msg .= $title->getPrefixedText() .
-                                       '|' . $title->getText() . "\n";
-                       }
-                       $msg .= '</gallery>';
-                       return '<li>' .
-                               wfMsgExt( 'file-exists-duplicate', array( 'parse' ), count( $dupes ) ) .
-                               $wgOut->parse( $msg ) .
-                               "</li>\n";
-               } else {
+       public function getDupeWarning( $dupes ) {
+               if ( !$dupes ) {
                        return '';
                }
-       }
-
-}
 
-/**
- * Sub class of HTMLForm that provides the form section of SpecialUpload
- */
-class UploadForm extends HTMLForm {
-       protected $mWatch;
-       protected $mForReUpload;
-       protected $mSessionKey;
-       protected $mHideIgnoreWarning;
-       protected $mDestWarningAck;
-       protected $mDestFile;
-
-       protected $mComment;
-       protected $mTextTop;
-       protected $mTextAfterSummary;
-
-       protected $mSourceIds;
-
-       public function __construct( $options = array() ) {
-               $this->mWatch = !empty( $options['watch'] );
-               $this->mForReUpload = !empty( $options['forreupload'] );
-               $this->mSessionKey = isset( $options['sessionkey'] )
-                               ? $options['sessionkey'] : '';
-               $this->mHideIgnoreWarning = !empty( $options['hideignorewarning'] );
-               $this->mDestWarningAck = !empty( $options['destwarningack'] );
-               $this->mDestFile = isset( $options['destfile'] ) ? $options['destfile'] : '';
-
-               $this->mComment = isset( $options['description'] ) ?
-                       $options['description'] : '';
-
-               $this->mTextTop = isset( $options['texttop'] )
-                       ? $options['texttop'] : '';
-
-               $this->mTextAfterSummary = isset( $options['textaftersummary'] )
-                       ? $options['textaftersummary'] : ''; 
-
-               $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' );
-               # Used message keys: 'accesskey-upload', 'tooltip-upload'
-               $this->setSubmitTooltip( 'upload' );
-               $this->setId( 'mw-upload-form' );
-
-               # Build a list of IDs for javascript insertion
-               $this->mSourceIds = array();
-               foreach ( $sourceDescriptor as $field ) {
-                       if ( !empty( $field['id'] ) ) {
-                               $this->mSourceIds[] = $field['id'];
-                       }
+               $gallery = ImageGalleryBase::factory( false, $this->getContext() );
+               $gallery->setShowBytes( false );
+               $gallery->setShowDimensions( false );
+               foreach ( $dupes as $file ) {
+                       $gallery->add( $file->getTitle() );
                }
 
+               return '<li>' .
+                       $this->msg( 'file-exists-duplicate' )->numParams( count( $dupes ) )->parse() .
+                       $gallery->toHTML() . "</li>\n";
        }
 
-       /**
-        * Get the descriptor of the fieldset that contains the file source
-        * selection. The section is 'source'
-        *
-        * @return Array: descriptor array
-        */
-       protected function getSourceSection() {
-               global $wgLang, $wgUser, $wgRequest;
-               global $wgMaxUploadSize;
-
-               if ( $this->mSessionKey ) {
-                       return array(
-                               'SessionKey' => array(
-                                       'type' => 'hidden',
-                                       'default' => $this->mSessionKey,
-                               ),
-                               'SourceType' => array(
-                                       'type' => 'hidden',
-                                       'default' => 'Stash',
-                               ),
-                       );
-               }
-
-               $canUploadByUrl = UploadFromUrl::isEnabled() && $wgUser->isAllowed( 'upload_by_url' );
-               $radio = $canUploadByUrl;
-               $selectedSourceType = strtolower( $wgRequest->getText( 'wpSourceType', 'File' ) );
-
-               $descriptor = array();
-               if ( $this->mTextTop ) {
-                       $descriptor['UploadFormTextTop'] = array(
-                               'type' => 'info',
-                               'section' => 'source',
-                               'default' => $this->mTextTop,
-                               'raw' => true,
-                       );
-               }
-
-               $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( min( 
-                                                       wfShorthandToInteger(
-                                                               ini_get( 'upload_max_filesize' )
-                                                       ), $wgMaxUploadSize
-                                               ) )
-                                       )
-                               ) . ' ' . wfMsgHtml( 'upload_source_file' ),
-                       'checked' => $selectedSourceType == 'file',
-               );
-               if ( $canUploadByUrl ) {
-                       $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',
-                       );
-               }
-               wfRunHooks( 'UploadFormSourceDescriptors', array( &$descriptor, &$radio, $selectedSourceType ) );
-
-               $descriptor['Extensions'] = array(
-                       'type' => 'info',
-                       'section' => 'source',
-                       'default' => $this->getExtensionsMessage(),
-                       'raw' => true,
-               );
-               return $descriptor;
-       }
-
-       /**
-        * Get the messages indicating which extensions are preferred and prohibitted.
-        *
-        * @return String: HTML string containing the message
-        */
-       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 $wgLang, $wgCheckFileExtensions, $wgStrictFileExtensions,
-               $wgFileExtensions, $wgFileBlacklist;
-
-               if( $wgCheckFileExtensions ) {
-                       if( $wgStrictFileExtensions ) {
-                               # Everything not permitted is banned
-                               $extensionsList =
-                                       '<div id="mw-upload-permitted">' .
-                                       wfMsgWikiHtml( 'upload-permitted', $wgLang->commaList( $wgFileExtensions ) ) .
-                                       "</div>\n";
-                       } else {
-                               # We have to list both preferred and prohibited
-                               $extensionsList =
-                                       '<div id="mw-upload-preferred">' .
-                                       wfMsgWikiHtml( 'upload-preferred', $wgLang->commaList( $wgFileExtensions ) ) .
-                                       "</div>\n" .
-                                       '<div id="mw-upload-prohibited">' .
-                                       wfMsgWikiHtml( 'upload-prohibited', $wgLang->commaList( $wgFileBlacklist ) ) .
-                                       "</div>\n";
-                       }
-               } else {
-                       # Everything is permitted.
-                       $extensionsList = '';
-               }
-               return $extensionsList;
-       }
-
-       /**
-        * Get the descriptor of the fieldset that contains the file description
-        * input. The section is 'description'
-        *
-        * @return Array: descriptor array
-        */
-       protected function getDescriptionSection() {
-               global $wgUser;
-
-               $descriptor = array(
-                       'DestFile' => array(
-                               'type' => 'text',
-                               'section' => 'description',
-                               'id' => 'wpDestFile',
-                               'label-message' => 'destfilename',
-                               'size' => 60,
-                               'default' => $this->mDestFile,
-                               # FIXME: hack to work around poor handling of the 'default' option in HTMLForm
-                               'nodata' => strval( $this->mDestFile ) !== '',
-                       ),
-                       'UploadDescription' => array(
-                               'type' => 'textarea',
-                               'section' => 'description',
-                               'id' => 'wpUploadDescription',
-                               'label-message' => $this->mForReUpload
-                                       ? 'filereuploadsummary'
-                                       : 'fileuploadsummary',
-                               'default' => $this->mComment,
-                               'cols' => intval( $wgUser->getOption( 'cols' ) ),
-                               'rows' => 8,
-                       )
-               );
-               if ( $this->mTextAfterSummary ) {
-                       $descriptor['UploadFormTextAfterSummary'] = array(
-                               'type' => 'info',
-                               'section' => 'description',
-                               'default' => $this->mTextAfterSummary,
-                               'raw' => true,
-                       );
-               }
-
-               $descriptor += array(
-                       'EditTools' => array(
-                               'type' => 'edittools',
-                               'section' => 'description',
-                       )
-               );
-
-               if ( $this->mForReUpload ) {
-                       $descriptor['DestFile']['readonly'] = true;
-               } else {
-                       $descriptor['License'] = array(
-                               'type' => 'select',
-                               'class' => 'Licenses',
-                               'section' => 'description',
-                               'id' => 'wpLicense',
-                               'label-message' => 'license',
-                       );
-               }
-
-               global $wgUseCopyrightUpload;
-               if ( $wgUseCopyrightUpload ) {
-                       $descriptor['UploadCopyStatus'] = array(
-                               'type' => 'text',
-                               'section' => 'description',
-                               'id' => 'wpUploadCopyStatus',
-                               'label-message' => 'filestatus',
-                       );
-                       $descriptor['UploadSource'] = array(
-                               'type' => 'text',
-                               'section' => 'description',
-                               'id' => 'wpUploadSource',
-                               'label-message' => 'filesource',
-                       );
-               }
-
-               return $descriptor;
+       protected function getGroupName() {
+               return 'media';
        }
 
        /**
-        * Get the descriptor of the fieldset that contains the upload options,
-        * such as "watch this file". The section is 'options'
+        * Should we rotate images in the preview on Special:Upload.
         *
-        * @return Array: descriptor array
-        */
-       protected function getOptionsSection() {
-               global $wgUser;
-
-               if ( $wgUser->isLoggedIn() ) {
-                       $descriptor = array(
-                               'Watchthis' => array(
-                                       'type' => 'check',
-                                       'id' => 'wpWatchthis',
-                                       'label-message' => 'watchthisupload',
-                                       'section' => 'options',
-                                       'default' => $wgUser->getOption( 'watchcreations' ),
-                               )
-                       );
-               }
-               if ( !$this->mHideIgnoreWarning ) {
-                       $descriptor['IgnoreWarning'] = array(
-                               'type' => 'check',
-                               'id' => 'wpIgnoreWarning',
-                               'label-message' => 'ignorewarnings',
-                               'section' => 'options',
-                       );
-               }
-
-               $descriptor['DestFileWarningAck'] = array(
-                       'type' => 'hidden',
-                       'id' => 'wpDestFileWarningAck',
-                       'default' => $this->mDestWarningAck ? '1' : '',
-               );
-               
-               if ( $this->mForReUpload ) {
-                       $descriptor['ForReUpload'] = array(
-                               'type' => 'hidden',
-                               'id' => 'wpForReUpload',
-                               'default' => '1',
-                       );
-               }
-
-               return $descriptor;
-       }
-
-       /**
-        * Add the upload JS and show the form.
-        */
-       public function show() {
-               $this->addUploadJS();
-               parent::show();
-       }
-
-       /**
-        * Add upload JS to $wgOut
-        */
-       protected function addUploadJS() {
-               global $wgUseAjax, $wgAjaxUploadDestCheck, $wgAjaxLicensePreview, $wgEnableAPI, $wgStrictFileExtensions;
-               global $wgOut;
-
-               $useAjaxDestCheck = $wgUseAjax && $wgAjaxUploadDestCheck;
-               $useAjaxLicensePreview = $wgUseAjax && $wgAjaxLicensePreview && $wgEnableAPI;
-
-               $scriptVars = array(
-                       'wgAjaxUploadDestCheck' => $useAjaxDestCheck,
-                       'wgAjaxLicensePreview' => $useAjaxLicensePreview,
-                       'wgUploadAutoFill' => !$this->mForReUpload &&
-                               // If we received mDestFile from the request, don't autofill
-                               // the wpDestFile textbox
-                               $this->mDestFile === '',
-                       'wgUploadSourceIds' => $this->mSourceIds,
-                       'wgStrictFileExtensions' => $wgStrictFileExtensions,
-                       'wgCapitalizeUploads' => MWNamespace::isCapitalized( NS_FILE ),
-               );
-
-               $wgOut->addScript( Skin::makeVariablesScript( $scriptVars ) );
-
-               // For <charinsert> support
-               $wgOut->addModules( array( 'mediawiki.legacy.edit', 'mediawiki.legacy.upload' ) );
-       }
-
-       /**
-        * Empty function; submission is handled elsewhere.
+        * This controls js: mw.config.get( 'wgFileCanRotate' )
         *
-        * @return bool false
+        * @todo What about non-BitmapHandler handled files?
+        * @return bool
         */
-       function trySubmit() {
-               return false;
+       public static function rotationEnabled() {
+               $bitmapHandler = new BitmapHandler();
+               return $bitmapHandler->autoRotateEnabled();
        }
-
 }
-
-/**
- * A form field that contains a radio box in the label
- */
-class UploadSourceField extends HTMLTextField {
-       function getLabelHtml( $cellAttributes = array() ) {
-               $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'],
-                       );
-                       if ( !empty( $this->mParams['checked'] ) ) {
-                               $attribs['checked'] = 'checked';
-                       }
-                       $label .= Html::element( 'input', $attribs );
-               }
-
-               return Html::rawElement( 'td', array( 'class' => 'mw-label' ) + $cellAttributes, $label );
-       }
-
-       function getSize() {
-               return isset( $this->mParams['size'] )
-                       ? $this->mParams['size']
-                       : 60;
-       }
-}
-