Jack Phoenix has uploaded a new change for review. ( 
https://gerrit.wikimedia.org/r/334788 )

Change subject: Publishing ShoutWiki's version (v. 3.1-SW) of MultiUpload for 
MW 1.28+
......................................................................

Publishing ShoutWiki's version (v. 3.1-SW) of MultiUpload for MW 1.28+

* Old legacy cruft removed for good; if you need support for an EOL'd version 
of MediaWiki, use an older version of the extension
* Extension registration is now required for loading this extension
* Windows unzipping support in the API module, sorta
* JavaScript fixes
* Lots and lots of ugly copypasta

Might work/probably works with MW 1.27, but that hasn't been tested.
Still, there was merely one line I had to comment out to make this
1.28-compatible when we upgraded from 1.27 to 1.28, so I'm pretty
confident that this'll work with 1.27, but as always, use at your own
risk.

Change-Id: I3390021444af2eafcb54dc1d1b8a399f8a2bb819
---
D MultiUpload.i18n.php
D MultiUpload.php
M MultiUploadApi.php
M README
M SpecialMultiUpload.php
D SpecialUpload.php
M extension.json
M resources/ext.multiupload.shared.js
M resources/ext.multiupload.unpack.js
D resources/mediawiki.special.upload.js.patched.1.21.3
M resources/mw.FormDataTransport.js
D resources/upload.js.patched.1.21.3
12 files changed, 427 insertions(+), 2,496 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/MultiUpload 
refs/changes/88/334788/1

diff --git a/MultiUpload.i18n.php b/MultiUpload.i18n.php
deleted file mode 100644
index d252b1c..0000000
--- a/MultiUpload.i18n.php
+++ /dev/null
@@ -1,35 +0,0 @@
-<?php
-/**
- * This is a backwards-compatibility shim, generated by:
- * 
https://git.wikimedia.org/blob/mediawiki%2Fcore.git/HEAD/maintenance%2FgenerateJsonI18n.php
- *
- * Beginning with MediaWiki 1.23, translation strings are stored in json files,
- * and the EXTENSION.i18n.php file only exists to provide compatibility with
- * older releases of MediaWiki. For more information about this migration, see:
- * https://www.mediawiki.org/wiki/Requests_for_comment/Localisation_format
- *
- * This shim maintains compatibility back to MediaWiki 1.17.
- */
-$messages = array();
-if ( !function_exists( 'wfJsonI18nShim97d1b12e3cd5e296' ) ) {
-       function wfJsonI18nShim97d1b12e3cd5e296( $cache, $code, &$cachedData ) {
-               $codeSequence = array_merge( array( $code ), 
$cachedData['fallbackSequence'] );
-               foreach ( $codeSequence as $csCode ) {
-                       $fileName = dirname( __FILE__ ) . "/i18n/$csCode.json";
-                       if ( is_readable( $fileName ) ) {
-                               $data = FormatJson::decode( file_get_contents( 
$fileName ), true );
-                               foreach ( array_keys( $data ) as $key ) {
-                                       if ( $key === '' || $key[0] === '@' ) {
-                                               unset( $data[$key] );
-                                       }
-                               }
-                               $cachedData['messages'] = array_merge( $data, 
$cachedData['messages'] );
-                       }
-
-                       $cachedData['deps'][] = new FileDependency( $fileName );
-               }
-               return true;
-       }
-
-       $GLOBALS['wgHooks']['LocalisationCacheRecache'][] = 
'wfJsonI18nShim97d1b12e3cd5e296';
-}
diff --git a/MultiUpload.php b/MultiUpload.php
deleted file mode 100644
index 11560d2..0000000
--- a/MultiUpload.php
+++ /dev/null
@@ -1,165 +0,0 @@
-<?php
-/**
- * MultiUpload extension for MediaWiki 1.19 and later
- *
- * @file
- * @ingroup Extensions
- * @author Travis Derouin
- * @author Lee Worden <worden....@gmail.com>
- * @version 3.0
- * @date 19 August 2014
- * @copyright Copyright © Lee Worden <worden....@gmail.com>
- * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 
2.0 or later
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- * http://www.gnu.org/copyleft/gpl.html
- */
-
-/**
- * Protect against register_globals vulnerabilities.
- * This line must be present before any global variable is referenced.
- */
-if ( !defined( 'MEDIAWIKI' ) ) {
-       echo( "This is an extension to the MediaWiki package and cannot be run 
standalone.\n" );
-       die( -1 );
-}
-
-# ===== Extension credits that will show up on Special:Version =====
-$wgExtensionCredits['specialpage'][] = array(
-       'name'         => 'MultiUpload',
-       'version'      => '3.0',
-       'author'       => array( 'Travis Derouin', 'Lee Worden' ),
-       'url'          => 
'https://www.mediawiki.org/wiki/Extension:MultiUpload',
-       'descriptionmsg' => 'multiupload-desc',
-);
-
-# ===== Configuration variables =====
-
-# start with 1 file selector shown
-$wgMultiUploadInitialNumberOfImportRows = 1;
-
-# Can't have a huge number of files to upload on the single form -
-# for instance, because of PHP restrictions on number
-# and length of POST values
-# $wgMultiUploadMaxImportFilesPerPage = 20;
-
-# where we unpack .tar and .zip files
-$wgMultiUploadTempDir = '/tmp';
-
-# ===== Register the special page =====
-$wgSpecialPages['MultiUpload'] = 'SpecialMultiUpload';
-$wgAutoloadClasses['SpecialMultiUpload'] = __DIR__ . '/SpecialMultiUpload.php';
-$wgAutoloadClasses['MultiUploadForm'] = __DIR__ . '/SpecialMultiUpload.php';
-$wgAutoloadClasses['FauxWebRequestUpload'] = __DIR__ . 
'/SpecialMultiUpload.php';
-$wgAutoloadClasses['DerivativeRequestWithFiles'] = __DIR__ . 
'/SpecialMultiUpload.php';
-$wgAutoloadClasses['UploadRow'] = __DIR__ . '/SpecialMultiUpload.php';
-$wgAutoloadClasses['UploadFormRow'] = __DIR__ . '/SpecialMultiUpload.php';
-
-# ===== ResourceLoader =====
-$resourceModuleTemplate = array(
-       'localBasePath' => __DIR__ . '/resources',
-);
-
-$wgResourceModules['special.upload.patched'] = $resourceModuleTemplate + array(
-       'scripts' => array(
-               'mediawiki.special.upload.js.patched',
-               'upload.js.patched',
-       ),
-       'styles' => array(
-               'ext.multiupload.mw1.19.compatibility.css', # actually useful 
in 1.23 as well
-       ),
-       'messages' => array(
-               'widthheight',
-               'size-bytes',
-               'size-kilobytes',
-               'size-megabytes',
-               'size-gigabytes',
-               'largefileserver',
-       ),
-       'dependencies' => array(
-               'mediawiki.libs.jpegmeta',
-               'mediawiki.api',
-               'mediawiki.Title',
-               'mediawiki.legacy.wikibits',
-               'mediawiki.util',
-               'jquery.spinner',
-       ),
-);
-
-// slightly different patched code for older versions of MW
-if ( version_compare( $wgVersion, '1.22', '<' ) ) {
-       $wgResourceModules['special.upload.patched']['scripts'] = array(
-               'mediawiki.special.upload.js.patched.1.21.3',
-               'upload.js.patched.1.21.3',
-       );
-}
-
-$wgResourceModules['ext.multiupload.top'] = $resourceModuleTemplate + array(
-       'scripts' => array( 'ext.multiupload.top.js' ),
-       'styles' => array( 'ext.multiupload.top.css' ),
-       'position' => 'top',
-);
-
-$wgResourceModules['ext.multiupload.unpack'] = $resourceModuleTemplate + array(
-       'scripts' => array(
-               'ext.multiupload.unpack.js',
-               'mw.FormDataTransport.js',
-       ),
-       'dependencies' => array(
-               'mediawiki.api',
-               'ext.multiupload.top',
-       ),
-       'messages' => array(
-               'multiupload-upload-package-error',
-               'multiupload-unpack-error',
-               'multiupload-http-error',
-               'multiupload-file-unpacked-from',
-       ),
-);
-
-$wgResourceModules['ext.multiupload.shared'] = $resourceModuleTemplate + array(
-       'scripts' => array(
-               'ext.multiupload.shared.js',
-       ),
-       'styles' => array(
-               'ext.multiupload.shared.css',
-       ),
-       'dependencies' => array(
-               'ext.multiupload.top',
-               'special.upload.patched',
-       ),
-       'messages' => array(
-               'multiupload-row', # note: this message has to be named just so 
- it overlaps with id names in the DOM, created by HTMLForm
-               'multiupload-unpack-button',
-       ),
-);
-
-$wgResourceModules['ext.multiupload'] = $resourceModuleTemplate + array(
-       'scripts' => array( 'ext.multiupload.js' ),
-       'dependencies' => array(
-               'ext.multiupload.shared',
-       ),
-);
-
-# api.php actions
-$wgAPIModules['multiupload-unpack'] = 'MultiUploadApiUnpack';
-$wgAutoloadClasses['MultiUploadApiUnpack'] = __DIR__ . '/MultiUploadApi.php';
-
-# (potentially) multilingual messages
-$wgExtensionMessagesFiles['MultiUpload'] = __DIR__ . '/MultiUpload.i18n.php';
-$wgMessagesDirs['MultiUpload'] = __DIR__ . '/i18n';
-
-# special page aliases
-$wgExtensionMessagesFiles['MultiUploadAlias'] = __DIR__ . 
'/MultiUpload.alias.php';
\ No newline at end of file
diff --git a/MultiUploadApi.php b/MultiUploadApi.php
index 7551506..d605c9f 100644
--- a/MultiUploadApi.php
+++ b/MultiUploadApi.php
@@ -13,6 +13,36 @@
        }
 
        /**
+        * Get the temporary directory and the prefix in a platform-independent 
way,
+        * no matter what.
+        *
+        * @return array( temp dir (such as /tmp/MultiUpload_unpack_5pzFrr on 
*NIX), prefix (such as /tmp/MultiUpload_unpack_ on *NIX) )
+        */
+       public function getTempDirAndPrefix() {
+               global $wgMultiUploadTempDir;
+               if ( wfIsWindows() ) {
+                       // tempnam(), or at least how this code uses it, is 
bugged on Windows
+                       // @see http://php.net/manual/en/function.tempnam.php
+                       // Quoth that page: "Note: Windows uses only the first 
three characters of prefix."
+                       $ourTempDir = is_dir( $wgMultiUploadTempDir ) ? 
$wgMultiUploadTempDir : wfTempDir(); // C:\Users\<user name>\AppData\Local\Temp
+                       $unpackLocation = realpath( tempnam( $ourTempDir, 
MultiUploadApiUnpack::$unpackDirBase ) ); // C:\Users\<user 
name>\AppData\Local\Temp\MulD6D8.tmp
+                       $removeMe = end( explode( DIRECTORY_SEPARATOR, 
$unpackLocation ) ); // such as MulD6D8.tmp
+                       $prefix = str_replace( str_replace( 'Mul', '', 
$removeMe ), '', $unpackLocation ); // should be something like C:\Users\<user 
name>\AppData\Local\Temp\Mul
+               } else {
+                       // the end result is something like 
"/tmp/MultiUpload_unpack_Whcdu9"
+                       // here (for the $unpackLocation var) and 
"/tmp/MultiUpload_unpack_"
+                       // for the $prefix var
+                       $unpackLocation = realpath( tempnam(
+                               $wgMultiUploadTempDir,
+                               MultiUploadApiUnpack::$unpackDirBase
+                       ) );
+                       $prefix = realpath( $wgMultiUploadTempDir ) . '/' .
+                               MultiUploadApiUnpack::$unpackDirBase;
+               }
+               return array( $unpackLocation, $prefix );
+       }
+
+       /**
         * Given a package (e.g. tar.gz or .zip file), unpack it into a 
temporary
         * location.
         *
@@ -25,14 +55,8 @@
         *   In case of error, returns array(false, error_message).
         */
        public function unpack( $pkgFile, $srcName ) {
-               global $wgMultiUploadTempDir;
+               list( $unpackLocation, $prefix ) = $this->getTempDirAndPrefix();
 
-               $unpackLocation = realpath( tempnam(
-                       $wgMultiUploadTempDir,
-                       MultiUploadApiUnpack::$unpackDirBase
-               ) );
-               $prefix = realpath( $wgMultiUploadTempDir ) . '/' .
-                               MultiUploadApiUnpack::$unpackDirBase;
                wfDebugLog( 'MultiUploadApi', "unpackLocation is 
$unpackLocation, prefix is $prefix\n" );
 
                if ( strncmp( $unpackLocation, $prefix, strlen( $prefix ) ) != 
0 ) {
@@ -63,20 +87,42 @@
                # references. So I would need to check tar contents for ../ 
before extracting. In practice
                # it seems to be catching this case — but I should trap it 
explicitly anyway?
                if ( $this->suffixMatches( $srcName, '.tgz' ) || 
$this->suffixMatches( $srcName, '.tar.gz' ) ) {
-                       $unpackCommand = 'tar -xz -C ' . escapeshellarg( 
$unpackLocation )
-                               . ' -f ' . escapeshellarg( $pkgFile );
+                       $unpackCommand = 'tar -xz -C ' . wfEscapeShellArg( 
$unpackLocation )
+                               . ' -f ' . wfEscapeShellArg( $pkgFile );
+                       system( $unpackCommand, $unpackSuccess );
                } elseif ( $this->suffixMatches( $srcName, '.tar' ) ) {
-                       $unpackCommand = 'tar -x -C ' . escapeshellarg( 
$unpackLocation )
-                               . ' -f ' . escapeshellarg( $pkgFile );
+                       $unpackCommand = 'tar -x -C ' . wfEscapeShellArg( 
$unpackLocation )
+                               . ' -f ' . wfEscapeShellArg( $pkgFile );
+                       system( $unpackCommand, $unpackSuccess );
                } elseif ( $this->suffixMatches( $srcName, '.zip' ) ) {
                        # unzip also rejects files outside of the extraction 
directory
-                       $unpackCommand = 'unzip -q ' . escapeshellarg( $pkgFile 
)
-                               . ' -d ' . escapeshellarg( $unpackLocation );
+                       if ( wfIsWindows() ) {
+                               // Windows is different
+                               // @see 
https://phabricator.wikimedia.org/T124908
+                               $E_STRICT_IS_STUPID = explode( '/', $pkgFile );
+                               $removeMeToGetTheRealTempDirPathOnWindows = 
end( $E_STRICT_IS_STUPID ); // [sic]
+                               $realUnpackLocation = str_replace( 
$removeMeToGetTheRealTempDirPathOnWindows, '', $pkgFile );
+
+                               $zip = new ZipArchive;
+                               $res = $zip->open( $pkgFile );
+                               if ( $res ) {
+                                       wfDebugLog( 'MultiUploadApi', 'On 
Windows; ZipArchive extraction OK, extracting to ' . print_r( 
$realUnpackLocation, true ) );
+                                       $zip->extractTo( $realUnpackLocation );
+                                       $zip->close();
+                                       $unpackSuccess = 0;
+                               } else {
+                                       wfDebugLog( 'MultiUploadApi', 'On 
Windows; ZipArchive extraction FAILED, status code = ' . print_r( $res, true ) 
);
+                                       // something went wrong and extraction 
failed
+                                       $unpackSuccess = $res;
+                               }
+                       } else {
+                               $unpackCommand = 'unzip -q ' . 
wfEscapeShellArg( $pkgFile )
+                                       . ' -d ' . wfEscapeShellArg( 
$unpackLocation );
+                               system( $unpackCommand, $unpackSuccess );
+                       }
                } else {
                        return array( false, "Unknown filetype $srcName" );
                }
-
-               system( $unpackCommand, $unpackSuccess );
 
                if ( $unpackSuccess != 0 ) {
                        return array( false,
@@ -142,6 +188,8 @@
        }
 
        public function execute() {
+               //global $wgMultiUploadTempDir;
+
                $params = $this->extractRequestParams();
                wfDebugLog( 'MultiUploadApi', 'MultiUploadApiUnpack, params is 
' . json_encode( $params ) . "\n" );
 
@@ -159,6 +207,7 @@
 
                // Second is to unpack it. Code for that is in ImportQueue.
                $packageFileName = $params['filename'];
+               wfDebugLog( 'MultiUploadApi', __METHOD__ . ': $packageFileName 
= ' . print_r( $packageFileName, true ) . ' and $path = ' . print_r( $path, 
true ) );
                list( $success, $unpackCode ) = $this->unpack( $path, 
$packageFileName );
                if ( !$success ) {
                        $this->dieUsage(
@@ -167,10 +216,15 @@
                        );
                }
 
-               global $wgMultiUploadTempDir;
+               // $prefix is unused, but meh
+               list( $unpackDir, $prefix ) = $this->getTempDirAndPrefix();
+               wfDebugLog( 'MultiUploadApi', 'in ' . __METHOD__ . ', raw 
$unpackDir is: ' . print_r( $unpackDir, true ) . ' and $prefix is : ' . 
print_r( $prefix, true ) );
+               // $unpackDir nowadays contains $unpackCode already
+               /*
                $unpackDir = realpath( $wgMultiUploadTempDir ) . '/'
                        . MultiUploadApiUnpack::$unpackDirBase
                        . $unpackCode;
+               */
 
                // done with the package - delete it
                $stash->removeFile( $sessionkey );
@@ -179,14 +233,34 @@
 
                // now to traverse that directory, stash all the files and 
remember their
                // names
-               $filenames = $this->recursiveFindFiles( $unpackDir );
+               if ( wfIsWindows() ) {
+                       // $path is something like 
D:\xamppnew\htdocs\shoutwiki\trunk/images/temp/5/56/20161107221803!php8B81.tmp
+                       // so we need to remove the last part
+                       $dir = str_replace( end( explode( '/', $path ) ), '', 
$path );
+               } else {
+                       $dir = $unpackDir;
+               }
+               $filenames = $this->recursiveFindFiles( $dir );
+               wfDebugLog( 'MultiUploadApi', 'filenames after finding them 
recursively: ' . print_r( $filenames, true ) );
                natsort( $filenames );
+               wfDebugLog( 'MultiUploadApi', 'filenames after natsort()ing: ' 
. print_r( $filenames, true ) );
                $filedata = array();
+
                foreach ( $filenames as $filename ) {
-                       $stashedFile = $stash->stashFile( $unpackDir . '/' . 
$filename, 'file' );
+                       if ( $filename == 'index.html' ) {
+                               // I don't even...
+                               continue;
+                       }
+                       if ( wfIsWindows() ) {
+                               $fileToStash = $dir . $filename;
+                       } else {
+                               $fileToStash = $unpackDir . '/' . $filename;
+                       }
+                       wfDebugLog( 'MultiUploadApi', 'fileToStash: ' . 
print_r( $fileToStash, true ) . ', filename: ' . print_r( $filename, true ) );
+                       $stashedFile = $stash->stashFile( $fileToStash, 'file' 
);
                        $filedata[] = array( $stashedFile->getFileKey(), 
$filename );
                }
-               $this->recursiveUnlink( $unpackDir, true );
+               $this->recursiveUnlink( $dir, true );
 
                $res = array(
                        'contents' => $filedata,
@@ -221,4 +295,4 @@
        public function getDescription() {
                return 'Unpack a zip or tar file before importing its 
contents.';
        }
-}
+}
\ No newline at end of file
diff --git a/README b/README
index 16b9677..667100e 100644
--- a/README
+++ b/README
@@ -7,7 +7,7 @@
 === Requirements ===
 
 * PHP 5.x
-* MediaWiki 1.19 or later
+* MediaWiki 1.27 or later
 
 === Installation ===
 
@@ -17,7 +17,7 @@
 
 Add the line
 
-       require_once("extensions/MultiUpload/MultiUpload.php");
+       wfLoadExtension( 'MultiUpload' );
 
 to your LocalSettings.php file.
 
diff --git a/SpecialMultiUpload.php b/SpecialMultiUpload.php
index 49d40cd..a295210 100644
--- a/SpecialMultiUpload.php
+++ b/SpecialMultiUpload.php
@@ -25,13 +25,6 @@
        die();
 }
 
-/* use the local, patched version of SpecialUpload.php for now */
-global $wgVersion;
-$wgAutoloadLocalClasses['SpecialUpload']
-       = $wgAutoloadLocalClasses['UploadForm']
-       = $wgAutoloadLocalClasses['UploadSourceField']
-       = __DIR__ . '/SpecialUpload.php';
-
 /**
  * Special page for uploading multiple files in one submission.
  */
@@ -55,9 +48,42 @@
                return 'media';
        }
 
-       protected function handleRequestData() {
+       /**
+        * Special page entry point
+        */
+       public function execute( $par ) {
+               $this->setHeaders();
+               $this->outputHeader();
+
+               # Check uploading enabled
+               if ( !UploadBase::isEnabled() ) {
+                       throw new ErrorPageError( 'uploaddisabled', 
'uploaddisabledtext' );
+               }
+
+               # Check permissions
+               $user = $this->getUser();
+               $permissionRequired = UploadBase::isAllowed( $user );
+               if ( $permissionRequired !== true ) {
+                       throw new PermissionsError( $permissionRequired );
+               }
+
+               # Check blocks
+               if ( $user->isBlocked() ) {
+                       throw new UserBlockedError( $user->getBlock() );
+               }
+
+               # Check whether we actually want to allow changing stuff
+               $this->checkReadOnly();
+
+               $this->handleRequestData();
+       }
+
+       public function handleRequestData() {
                $request = $this->getRequest();
-               $this->checkToken( $request );
+
+               $token = $request->getVal( 'wpEditToken' );
+               $this->mTokenOk = $this->getUser()->matchEditToken( $token );
+
                $this->mFrom = 1; # $request->getVal( 'from', 1 );
                global $wgMultiUploadInitialNumberOfImportRows;
                $this->mTo = $request->getVal( 'wpLastRowIndex',
@@ -128,10 +154,12 @@
                $this->mSourceIds = array();
                $this->mMessagePrefix = 'multiupload';
                $this->setSubmitText( wfMessage( 'multiupload-submit' 
)->parse() );
+               // ashley 28 October 2016: I have no idea what I'm doing...
+               $this->constructForm( $context );
        }
 
-       protected function constructData( array $options = array(), 
IContextSource $context = null ) {
-       }
+//     protected function constructData( array $options = array(), 
IContextSource $context = null ) {
+//     }
 
        protected function constructForm( IContextSource $context ) {
                $descriptor = $this->getGlobalFormDescriptors()
@@ -159,24 +187,22 @@
                return wfMessage( "{$this->mMessagePrefix}-$msg", $parts 
)->parse();
        }
 
-       protected function addJsConfigVars( $out ) {
-               parent::addJsConfigVars( $out );
+       protected function addUploadJS() {
+               $out = $this->getOutput();
+               parent::addUploadJS();
                $jsConfig = array(
                        'wpFirstRowIndex' => $this->mPage->mFrom,
                        'wpLastRowIndex' => $this->mPage->mTo,
                        'wgMultiUploadMaxPhpUploadSize' => min(
                                wfShorthandToInteger( ini_get( 
'upload_max_filesize' ) ),
                                wfShorthandToInteger( ini_get( 'post_max_size' 
) )
-                       ),
-
+                       )
                );
                foreach ( $this->mRows as $row ) {
                        $jsConfig = $jsConfig + $row->jsConfigVars();
                }
                $out->addJsConfigVars( $jsConfig );
-       }
 
-       protected function addRLModules( $out ) {
                $out->addModules( array(
                        'ext.multiupload.top',
                        'ext.multiupload',
@@ -187,7 +213,7 @@
 /**
  * Hoping this gets merged into core, won't have to do it here
  */
-if ( !class_exists( 'FauxWebRequestUpload' ) ) {
+//if ( !class_exists( 'FauxWebRequestUpload' ) ) {
        /**
         * A WebRequestUpload that can be faked.
         */
@@ -222,12 +248,12 @@
                        }
                }
        }
-} else {
+//} else {
        /**
         * If the feature is in MW core, just use it
         */
-       class DerivativeRequestWithFiles extends DerivativeRequest { }
-}
+//     class DerivativeRequestWithFiles extends DerivativeRequest { }
+//}
 
 class UploadRow extends SpecialUpload {
        public $mPage;
@@ -290,19 +316,45 @@
                return $this->mRequest;
        }
 
-       protected function handleRequestData() {
+       /**
+        * Respond to submitted form data and/or display upload form
+        * @since 1.23
+        */
+       public function handleRequestData() {
                $request = $this->getRequest();
                $this->mUploadSuccessful = $request->getCheck( 
'wpUploadSuccessful' );
-               parent::handleRequestData();
-       }
 
-       /**
-        * We don't do our own form output - we give all output to the
-        * page object to aggregate into a single form
-        */
-       protected function showUploadForm( $form ) {
-               // it gets called
-               // wfDebug(" *** SHOWUPLOADFORM SHOULD NOT BE CALLED! *** \n");
+               $this->loadRequest();
+
+               # 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;
+                       }
+               }
+
+               # Process upload or show a form
+               if ( $this->shouldProcessUpload() ) {
+                       $this->processUpload();
+               } else {
+                       # Backwards compatibility hook
+                       if ( !Hooks::run( 'UploadForm:initial', array( &$this ) 
) ) {
+                               wfDebug( "Hook 'UploadForm:initial' broke 
output of the upload form" );
+                               return;
+                       }
+                       // MediaWiki 1.28 fix -- either MW doesn't call the 
empty function
+                       // below or then calling it has no effect, but for 
whatever reason
+                       // MW tends to act like it calls the *parent* class' 
function w/
+                       // that name, which breaks stuff. Commenting this out 
doesn't appear
+                       // to have any nasty side-effects, so...
+                       //$this->showUploadForm( $this->getUploadForm() );
+               }
+
+               # Cleanup
+               if ( $this->mUpload ) {
+                       $this->mUpload->cleanupTempFile();
+               }
        }
 
        /**
@@ -310,13 +362,35 @@
         * this is called, wait and do it when the page object is ready to
         * assemble its full output form.
         *
-        * @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
-        * @return UploadForm
-        * todo lots of redundancy here
+        * @param HTMLForm|string $form An HTMLForm instance or HTML string to 
show [unused]
         */
-       public function getUploadForm( $message = '', $sessionKey = '', 
$hideIgnoreWarning = false ) {
+       public function showUploadForm( $form ) {
+       }
+
+       /**
+        * Format an upload error message for display.
+        *
+        * @param string $message HTML string
+        * @return string HTML message
+        * @since 1.23
+        */
+       protected function getUploadError( $message ) {
+               return '<h2>' . $this->msg( 'uploadwarning' )->escaped() . 
"</h2>\n" .
+                       '<div class="error">' . $message . "</div>\n";
+       }
+
+       /**
+        * Construct a recoverable error message.
+        *
+        * See showRecoverableUploadError below.
+        *
+        * @param string $message HTML message to be passed to mainUploadForm
+        * @return string formatted HTML
+        * @since 1.23
+        */
+       protected function getRecoverableUploadError( $message ) {
+               return '<h2>' . $this->msg( 'uploaderror' )->escaped() . 
"</h2>\n" .
+                       '<div class="error">' . $message . "</div>\n";
        }
 
        public function showUploadError( $message ) {
@@ -328,6 +402,87 @@
                $this->mFormMessage .= $this->getRecoverableUploadError( 
$message );
        }
 
+       /**
+        * Construct a formatted list of upload warnings.
+        *
+        * @todo FIXME: filthy copypasta to work around a fatal --ashley 29 
October 2016
+        *
+        * @param array $warnings
+        * @return string|bool A string if there are warnings to display, false 
if there are no
+        *         warnings and it should continue processing
+        * @return string Formatted HTML
+        * @since 1.23
+        */
+       protected function getUploadWarning( $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 ) )
+               ) {
+                       return false;
+               }
+
+               $warningHtml = '<h2>' . $this->msg( 'uploadwarning' 
)->escaped() . "</h2>\n"
+                       . '<ul class="warning">';
+               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 = $this->getDupeWarning( $args );
+                       } elseif ( $warning == 'duplicate-archive' ) {
+                               $msg = "\t<li>" . $this->msg( 
'file-deleted-duplicate',
+                                               Title::makeTitle( NS_FILE, 
$args )->getPrefixedText() )->parse()
+                                       . "</li>\n";
+                       } else {
+                               if ( $args === true ) {
+                                       $args = array();
+                               } elseif ( !is_array( $args ) ) {
+                                       $args = array( $args );
+                               }
+                               $msg = "\t<li>" . $this->msg( $warning, $args 
)->parse() . "</li>\n";
+                       }
+                       $warningHtml .= $msg;
+               }
+               $warningHtml .= "</ul>\n";
+               $warningHtml .= $this->msg( 'uploadwarning-text' 
)->parseAsBlock();
+
+               return $warningHtml;
+       }
+
+       /**
+        * Stash the upload, show the main form, but add a "continue anyway" 
button.
+        * Also check whether there are actually warnings to display.
+        *
+        * @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 ) {
+               $warningHtml = $this->getUploadWarning( $warnings );
+               if ( $warningHtml === false ) {
+                       return false;
+               }
+
+               $sessionKey = $this->mUpload->stashSession();
+
+               $form = $this->getUploadForm( $warningHtml, $sessionKey, /* 
$hideIgnoreWarning */ true );
+               $form->setSubmitText( $this->msg( 'upload-tryagain' )->text() );
+               $form->addButton( 'wpUploadIgnoreWarning', $this->msg( 
'ignorewarning' )->text() );
+               $form->addButton( 'wpCancelUpload', $this->msg( 'reuploaddesc' 
)->text() );
+
+               $this->showUploadForm( $form );
+
+               # Indicate that we showed a form
+               return true;
+       }
+
+       /* OLD VERSION:
        protected function showUploadWarning( $warnings ) {
                $warningHtml = $this->getUploadWarning( $warnings );
                if ( $warningHtml === false ) {
@@ -349,6 +504,7 @@
 
                return true;
        }
+       */
 
        /**
         * This is apparently a pretty bad one.
@@ -382,12 +538,50 @@
        protected function createFormRow() {
                return new UploadFormRow(
                        $this,
-                       $this->getFormOptions(
-                               $this->mSessionKey,
-                               $this->mHideIgnoreWarning
+                       array(
+                               'watch' => $this->getWatchCheck(),
+                               'forreupload' => $this->mForReUpload,
+                               'sessionkey' => $this->mSessionKey,
+                               'hideignorewarning' => 
$this->mHideIgnoreWarning,
+                               'destwarningack' => 
(bool)$this->mDestWarningAck,
+
+                               'description' => $this->mComment,
+                               'texttop' => $this->uploadFormTextTop,
+                               'textaftersummary' => 
$this->uploadFormTextAfterSummary,
+                               'destfile' => $this->mDesiredDestName,
+                               'sourcetype' => $this->mSourceType,
                        ),
                        $this->getContext()
                );
+       }
+
+       /**
+        * Assemble the text of the "view X deleted revisions" link
+        *
+        * @todo FIXME: yet another filthy hack since this returns a string
+        * instead of using core's OutputPage directly as 
SpecialUpload::showViewDeletedLinks()
+        * does. --ashley 29 October 2016
+        *
+        * @return string
+        * @since 1.23
+        */
+       protected function getViewDeletedLinks() {
+               $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 ) {
+                       $count = $title->isDeleted();
+                       if ( $count > 0 && $user->isAllowed( 'deletedhistory' ) 
) {
+                               $restorelink = Linker::linkKnown(
+                                       SpecialPage::getTitleFor( 'Undelete', 
$title->getPrefixedText() ),
+                                       $this->msg( 'restorelink' )->numParams( 
$count )->escaped()
+                               );
+                               $link = $this->msg( $user->isAllowed( 'delete' 
) ? 'thisisdeleted' : 'viewdeleted' )
+                                       ->rawParams( $restorelink 
)->parseAsBlock();
+                               return "<div id=\"contentSub2\">{$link}</div>";
+                       }
+               }
+               return '';
        }
 
        public function getFormDescriptors() {
@@ -396,6 +590,7 @@
 
                $preText = '';
 
+               # Add links if file was previously deleted
                if ( $this->mDesiredDestName ) {
                        $preText .= $this->getViewDeletedLinks();
                }
@@ -427,6 +622,8 @@
                                $this->mUploadSuccessful );
        }
 
+       // @todo FIXME: <s>not called anymore</s>, core 
SpecialUpload::execute() used to
+       // call this right above the 'UploadForm:initial' hook --ashley 28 
October 2016
        protected function shouldProcessUpload() {
                return ( !$this->mUploadSuccessful &&
                                $this->mPage->mTokenOk && !$this->mCancelUpload 
&&
@@ -434,11 +631,17 @@
                                $this->mUploadClicked ) );
        }
 
+       // @todo FIXME: doesn't exist in core anymore as of 1.27. remove? 
--ashley 28 October 2016
        protected function uploadSucceeded() {
                $this->mDesiredDestName = 
$this->mLocalFile->getTitle()->getDBkey();
        }
 
        /**
+        * @todo FIXME: probably should be something like this:
+        * @code
+        $this->getOutput()->addJsConfigVars( array( ... ) );
+        * @endcode
+        * but where would it then be called from? Hmm... --ashley 28 October 
2016
         * @return array
         */
        public function jsConfigVars() {
@@ -450,6 +653,17 @@
                                $this->mDesiredDestName === '' ),
                );
        }
+
+       protected function addUploadJS() {
+               $out = $this->getOutput();
+               $out->addJsConfigVars( array(
+                       'wgMultiUploadAutoFill' . $this->mRowNumber =>
+                               ( !$this->mForReUpload &&
+                               // if mDestFile was provided in the request,
+                               // don't overwrite it by autofilling
+                               $this->mDesiredDestName === '' ),
+               ) );
+       }
 }
 
 class UploadFormRow extends UploadForm {
@@ -457,7 +671,29 @@
 
        function __construct( $row, array $options = array(), IContextSource 
$context = null ) {
                $this->mRow = $row;
-               $this->constructData( $options, $context );
+               # setContext is called in the constructor, but it's needed
+               # before we get there
+               $this->setContext( $context );
+
+               $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'] : '';
+
+               $this->mSourceType = isset( $options['sourcetype'] )
+                       ? $options['sourcetype'] : '';
                # HTMLForm::__construct( array(), $context, 'upload' );
                # $this->mSourceIds = array();
        }
diff --git a/SpecialUpload.php b/SpecialUpload.php
deleted file mode 100644
index 7c151a3..0000000
--- a/SpecialUpload.php
+++ /dev/null
@@ -1,1288 +0,0 @@
-<?php
-/**
- * Implements Special:Upload
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup SpecialPage
- * @ingroup Upload
- */
-
-/**
- * Form for handling uploads and special page.
- *
- * @ingroup SpecialPage
- * @ingroup Upload
- */
-class SpecialUpload extends SpecialPage {
-       /**
-        * Constructor : initialise object
-        * Get data POSTed through the form and assign them to the object
-        * @param $request WebRequest : data posted.
-        */
-       public function __construct( $request = null ) {
-               parent::__construct( 'Upload', 'upload' );
-       }
-
-       /** Misc variables **/
-       public $mRequest; // The WebRequest or FauxRequest this form is 
supposed to handle
-       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
-       public $mComment;
-       public $mLicense;
-
-       /** User input variables from the root section **/
-       public $mIgnoreWarning;
-       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
-       public $mTokenOk;
-       public $mUploadSuccessful = false; // Subclasses can use this to 
determine whether a file was uploaded
-
-       /** Text injection points for hooks not using HTMLForm **/
-       public $uploadFormTextTop;
-       public $uploadFormTextAfterSummary;
-
-       /**
-        * Initialize instance variables from request and create an Upload 
handler
-        */
-       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->getFileName( 
'wpUploadFile' );
-               }
-               $this->mLicense = $request->getText( 'wpLicense' );
-
-               $this->mDestWarningAck = $request->getText( 
'wpDestFileWarningAck' );
-               $this->mIgnoreWarning = $request->getCheck( 'wpIgnoreWarning' )
-                       || $request->getCheck( 'wpUploadIgnoreWarning' );
-               $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->mCancelUpload = $request->getCheck( 'wpCancelUpload' )
-                       || $request->getCheck( 'wpReUpload' ); // b/w compat
-
-               $this->checkToken( $request );
-
-               $this->uploadFormTextTop = '';
-               $this->uploadFormTextAfterSummary = '';
-       }
-
-       /**
-        * Check for valid edit token, to protect against remote posting
-        * with user credentials.
-        *
-        * @param $request WebRequest object
-        * @since 1.23
-        */
-       protected function checkToken( $request ) {
-               $token = $request->getVal( 'wpEditToken' );
-               $this->mTokenOk = $this->getUser()->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 object
-        * @return Boolean
-        */
-       public function userCanExecute( User $user ) {
-               return UploadBase::isEnabled() && parent::userCanExecute( $user 
);
-       }
-
-       /**
-        * Special page entry point
-        */
-       public function execute( $par ) {
-               $this->setHeaders();
-               $this->outputHeader();
-
-               # Check uploading enabled
-               if ( !UploadBase::isEnabled() ) {
-                       throw new ErrorPageError( 'uploaddisabled', 
'uploaddisabledtext' );
-               }
-
-               # Check permissions
-               $user = $this->getUser();
-               $permissionRequired = UploadBase::isAllowed( $user );
-               if ( $permissionRequired !== true ) {
-                       throw new PermissionsError( $permissionRequired );
-               }
-
-               # Check blocks
-               if ( $user->isBlocked() ) {
-                       throw new UserBlockedError( $user->getBlock() );
-               }
-
-               # Check whether we actually want to allow changing stuff
-               $this->checkReadOnly();
-
-               $this->handleRequestData();
-       }
-
-       /**
-        * Respond to submitted form data and/or display upload form
-        * @since 1.23
-        */
-       protected function handleRequestData() {
-               $this->loadRequest();
-
-               # 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;
-                       }
-               }
-
-               # Process upload or show a form
-               if ( $this->shouldProcessUpload() ) {
-                       $this->processUpload();
-               } else {
-                       # Backwards compatibility hook
-                       if ( !Hooks::run( 'UploadForm:initial', array( &$this ) 
) ) {
-                               wfDebug( "Hook 'UploadForm:initial' broke 
output of the upload form" );
-                               return;
-                       }
-                       $this->showUploadForm( $this->getUploadForm() );
-               }
-
-               # Cleanup
-               if ( $this->mUpload ) {
-                       $this->mUpload->cleanupTempFile();
-               }
-       }
-
-       /**
-        * Decide whether an upload has been requested
-        * @return Bool
-        * @since 1.23
-        */
-       protected function shouldProcessUpload() {
-               return (
-                       $this->mTokenOk && !$this->mCancelUpload &&
-                       ( $this->mUpload && $this->mUploadClicked )
-               );
-       }
-
-       /**
-        * Show the main upload form
-        *
-        * @param $form Mixed: an HTMLForm instance or HTML string to show
-        */
-       protected function showUploadForm( $form ) {
-               # Add links if file was previously deleted
-               if ( $this->mDesiredDestName ) {
-                       $this->showViewDeletedLinks();
-               }
-
-               if ( $form instanceof HTMLForm ) {
-                       $form->show();
-               } else {
-                       $this->getOutput()->addHTML( $form );
-               }
-
-       }
-
-       /**
-        * Assemble options array for constructing form data.
-        *
-        * @param string $sessionKey session key in case this is a stashed 
upload
-        * @param $hideIgnoreWarning Boolean: Whether to hide "ignore warning" 
check box
-        * @return Array
-        */
-       protected function getFormOptions( $sessionKey = '', $hideIgnoreWarning 
= false ) {
-               return array(
-                       'watch' => $this->getWatchCheck(),
-                       'forreupload' => $this->mForReUpload,
-                       'sessionkey' => $sessionKey,
-                       'hideignorewarning' => $hideIgnoreWarning,
-                       'destwarningack' => (bool)$this->mDestWarningAck,
-
-                       'description' => $this->mComment,
-                       'texttop' => $this->uploadFormTextTop,
-                       'textaftersummary' => $this->uploadFormTextAfterSummary,
-                       'destfile' => $this->mDesiredDestName,
-                       'sourcetype' => $this->mSourceType,
-               );
-       }
-
-       /**
-        * 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
-        * @param $hideIgnoreWarning Boolean: whether to hide "ignore warning" 
check box
-        * @return UploadForm
-        */
-       protected function getUploadForm( $message = '', $sessionKey = '', 
$hideIgnoreWarning = false ) {
-               $context = new DerivativeContext( $this->getContext() );
-               $context->setTitle( $this->getTitle() ); // Remove subpage
-               # Initialize form
-               $form = new UploadForm(
-                       $this->getFormOptions( $sessionKey, $hideIgnoreWarning 
),
-                       $context
-               );
-               $form->setTitle( $this->getTitle() );
-
-               # Check the token, but only if necessary
-               if (
-                       !$this->mTokenOk && !$this->mCancelUpload &&
-                       ( $this->mUpload && $this->mUploadClicked )
-               ) {
-                       $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
-               $desiredTitleObj = Title::makeTitleSafe( NS_FILE, 
$this->mDesiredDestName );
-               $delNotice = ''; // empty by default
-               if ( $desiredTitleObj instanceof Title && 
!$desiredTitleObj->exists() ) {
-                       LogEventsList::showLogExtract( $delNotice, array( 
'delete', 'move' ),
-                               $desiredTitleObj,
-                               '', array( 'lim' => 10,
-                                       'conds' => array( "log_action != 
'revision'" ),
-                                       'showIfEmpty' => false,
-                                       'msgKey' => array( 
'upload-recreate-warning' ) )
-                       );
-               }
-               $form->addPreText( $delNotice );
-
-               # Add text to form
-               $form->addPreText( '<div id="uploadtext">' .
-                       $this->msg( 'uploadtext', array( 
$this->mDesiredDestName ) )->parseAsBlock() .
-                       '</div>' );
-               # Add upload error message
-               $form->addPreText( $message );
-
-               # Add footer to form
-               $uploadFooter = $this->msg( 'uploadfooter' );
-               if ( !$uploadFooter->isDisabled() ) {
-                       $form->addPostText( '<div 
id="mw-upload-footer-message">'
-                               . $uploadFooter->parseAsBlock() . "</div>\n" );
-               }
-
-               return $form;
-       }
-
-       /**
-        * Assemble the text of the "view X deleted revisions" link
-        * @return string
-        * @since 1.23
-        */
-       protected function getViewDeletedLinks() {
-               $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 ) {
-                       $count = $title->isDeleted();
-                       if ( $count > 0 && $user->isAllowed( 'deletedhistory' ) 
) {
-                               $restorelink = Linker::linkKnown(
-                                       SpecialPage::getTitleFor( 'Undelete', 
$title->getPrefixedText() ),
-                                       $this->msg( 'restorelink' )->numParams( 
$count )->escaped()
-                               );
-                               $link = $this->msg( $user->isAllowed( 'delete' 
) ? 'thisisdeleted' : 'viewdeleted' )
-                                       ->rawParams( $restorelink 
)->parseAsBlock();
-                               return "<div id=\"contentSub2\">{$link}</div>";
-                       }
-               }
-               return '';
-       }
-
-       /*
-        * Show the "view X deleted revisions" link
-        */
-       protected function showViewDeletedLinks() {
-               $html = $this->getViewDeletedLinks();
-               if ( $html !== '' ) {
-                       $this->getOutput()->addHTML( $html );
-               }
-       }
-
-       /**
-        * Construct a recoverable error message.
-        *
-        * See showRecoverableUploaderror, below.
-        *
-        * @param string $message HTML message to be passed to mainUploadForm
-        * @return string formatted HTML
-        * @since 1.23
-        */
-       protected function getRecoverableUploadError( $message ) {
-               return '<h2>' . $this->msg( 'uploaderror' )->escaped() . 
"</h2>\n" .
-                       '<div class="error">' . $message . "</div>\n";
-       }
-
-       /**
-        * Stash the upload and show 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 showRecoverableUploadError( $message ) {
-               $sessionKey = $this->mUpload->stashSession();
-               $form = $this->getUploadForm(
-                       $this->getRecoverableUploadError( $message ),
-                       $sessionKey
-               );
-               $form->setSubmitText( $this->msg( 'upload-tryagain' 
)->escaped() );
-               $this->showUploadForm( $form );
-       }
-
-       /**
-        * Construct a formatted list of upload warnings.
-        *
-        * @param $warnings Array
-        * @return mixed: a string if there are warnings to display, false if 
there are no
-        *         warnings and it should continue processing
-        * @param Array $warnings
-        * @return string formatted HTML
-        * @since 1.23
-        */
-       protected function getUploadWarning( $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 ) )
-               ) {
-                       return false;
-               }
-
-               $warningHtml = '<h2>' . $this->msg( 'uploadwarning' 
)->escaped() . "</h2>\n"
-                       . '<ul class="warning">';
-               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 = $this->getDupeWarning( $args );
-                       } elseif ( $warning == 'duplicate-archive' ) {
-                               $msg = "\t<li>" . $this->msg( 
'file-deleted-duplicate',
-                                               Title::makeTitle( NS_FILE, 
$args )->getPrefixedText() )->parse()
-                                       . "</li>\n";
-                       } else {
-                               if ( $args === true ) {
-                                       $args = array();
-                               } elseif ( !is_array( $args ) ) {
-                                       $args = array( $args );
-                               }
-                               $msg = "\t<li>" . $this->msg( $warning, $args 
)->parse() . "</li>\n";
-                       }
-                       $warningHtml .= $msg;
-               }
-               $warningHtml .= "</ul>\n";
-               $warningHtml .= $this->msg( 'uploadwarning-text' 
)->parseAsBlock();
-
-               return $warningHtml;
-       }
-
-       /**
-        * Stash the upload, show the main form, but add a "continue anyway" 
button.
-        * Also check whether there are actually warnings to display.
-        *
-        * @param $warnings Array
-        * @return boolean true if warnings were displayed, false if there are 
no
-        *         warnings and it should continue processing
-        */
-       protected function showUploadWarning( $warnings ) {
-               $warningHtml = $this->getUploadWarning( $warnings );
-               if ( $warningHtml === false ) {
-                       return false;
-               }
-
-               $sessionKey = $this->mUpload->stashSession();
-
-               $form = $this->getUploadForm( $warningHtml, $sessionKey, /* 
$hideIgnoreWarning */ true );
-               $form->setSubmitText( $this->msg( 'upload-tryagain' )->text() );
-               $form->addButton( 'wpUploadIgnoreWarning', $this->msg( 
'ignorewarning' )->text() );
-               $form->addButton( 'wpCancelUpload', $this->msg( 'reuploaddesc' 
)->text() );
-
-               $this->showUploadForm( $form );
-
-               # Indicate that we showed a form
-               return true;
-       }
-
-       /**
-        * Format an upload error message for display.
-        *
-        * @param string $message HTML string
-        * @return string HTML message
-        * @since 1.23
-        */
-       protected function getUploadError( $message ) {
-               return '<h2>' . $this->msg( 'uploadwarning' )->escaped() . 
"</h2>\n" .
-                       '<div class="error">' . $message . "</div>\n";
-       }
-
-       /**
-        * Show the upload form with error message, but do not stash the file.
-        *
-        * @param string $message HTML string
-        */
-       protected function showUploadError( $message ) {
-               $this->showUploadForm( $this->getUploadForm(
-                       $this->getUploadError( $message )
-               ) );
-       }
-
-       /**
-        * Check various conditions and do the upload.
-        * Other checks are made in SpecialUpload::execute()
-        */
-       protected function processUpload() {
-               // Fetch the file if required
-               $status = $this->mUpload->fetchFile();
-               if ( !$status->isOK() ) {
-                       $this->showUploadError( $this->getOutput()->parse( 
$status->getWikiText() ) );
-                       return;
-               }
-
-               if ( !Hooks::run( 'UploadForm:BeforeProcessing', array( &$this 
) ) ) {
-                       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
-                       // and UploadBase::verifyFile.
-                       // If you use this hook to break uploading, the user 
will be returned
-                       // an empty form with no error message whatsoever.
-                       return;
-               }
-
-               // Upload verification
-               $details = $this->mUpload->verifyUpload();
-               if ( $details['status'] != UploadBase::OK ) {
-                       $this->processVerificationError( $details );
-                       return;
-               }
-
-               // Verify permissions for this title
-               $permErrors = $this->mUpload->verifyTitlePermissions( 
$this->getUser() );
-               if ( $permErrors !== true ) {
-                       $code = array_shift( $permErrors[0] );
-                       $this->showRecoverableUploadError( $this->msg( $code, 
$permErrors[0] )->parse() );
-                       return;
-               }
-
-               $this->mLocalFile = $this->mUpload->getLocalFile();
-
-               // Check warnings if necessary
-               if ( !$this->mIgnoreWarning ) {
-                       $warnings = $this->mUpload->checkWarnings();
-                       if ( $this->showUploadWarning( $warnings ) ) {
-                               return;
-                       }
-               }
-
-               // 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, $this->getUser() );
-               if ( !$status->isGood() ) {
-                       $this->showUploadError( $this->getOutput()->parse( 
$status->getWikiText() ) );
-                       return;
-               }
-
-               // Success, redirect to description page
-               $this->mUploadSuccessful = true;
-               Hooks::run( 'SpecialUploadComplete', array( &$this ) );
-               $this->uploadSucceeded();
-       }
-
-       /**
-        * Once upload is successful, redirect to the updated File: page
-        * @since 1.23
-        */
-       protected function uploadSucceeded() {
-               $this->getOutput()->redirect( 
$this->mLocalFile->getTitle()->getFullURL() );
-       }
-
-       /**
-        * Get the initial image page text based on a comment and optional file 
status information
-        * @param $comment string
-        * @param $license string
-        * @param $copyStatus string
-        * @param $source string
-        * @return string
-        */
-       public static function getInitialPageText( $comment = '', $license = 
'', $copyStatus = '', $source = '' ) {
-               global $wgUseCopyrightUpload, $wgForceUIMsgAsContentMsg;
-
-               $msg = array();
-               /* 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, 
(array)$wgForceUIMsgAsContentMsg ) ) {
-                               $msg[$msgName] = "{{int:$msgName}}";
-                       } else {
-                               $msg[$msgName] = wfMessage( $msgName 
)->inContentLanguage()->text();
-                       }
-               }
-
-               if ( $wgUseCopyrightUpload ) {
-                       $licensetxt = '';
-                       if ( $license != '' ) {
-                               $licensetxt = '== ' . $msg['license-header'] . 
" ==\n" . '{{' . $license . '}}' . "\n";
-                       }
-                       $pageText = '== ' . $msg['filedesc'] . " ==\n" . 
$comment . "\n" .
-                               '== ' . $msg['filestatus'] . " ==\n" . 
$copyStatus . "\n" .
-                               "$licensetxt" .
-                               '== ' . $msg['filesource'] . " ==\n" . $source;
-               } else {
-                       if ( $license != '' ) {
-                               $filedesc = $comment == '' ? '' : '== ' . 
$msg['filedesc'] . " ==\n" . $comment . "\n";
-                                       $pageText = $filedesc .
-                                       '== ' . $msg['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.
-        *
-        * 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.
-        * @return Bool|String
-        */
-       protected function getWatchCheck() {
-               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() ) {
-                       // We're uploading a new version of an existing file.
-                       // No creation, so don't watch it if we're not already.
-                       return false;
-               } else {
-                       // New page should get watched if that's our option.
-                       return $this->getUser()->getOption( 'watchcreations' );
-               }
-       }
-
-       /**
-        * Provides output to the user for a result of UploadBase::verifyUpload
-        *
-        * @param array $details result of UploadBase::verifyUpload
-        * @throws MWException
-        */
-       protected function processVerificationError( $details ) {
-               global $wgFileExtensions;
-
-               switch ( $details['status'] ) {
-
-                       /** Statuses that only require name changing **/
-                       case UploadBase::MIN_LENGTH_PARTNAME:
-                               $this->showRecoverableUploadError( $this->msg( 
'minlength1' )->escaped() );
-                               break;
-                       case UploadBase::ILLEGAL_FILENAME:
-                               $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( $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( $this->msg( 'emptyfile' 
)->escaped() );
-                               break;
-                       case UploadBase::FILE_TOO_LARGE:
-                               $this->showUploadError( $this->msg( 
'largefileserver' )->escaped() );
-                               break;
-                       case UploadBase::FILETYPE_BADTYPE:
-                               $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( $wgFileExtensions );
-                               $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( $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
-                                       $args = $details['error'];
-                                       $error = array_shift( $args );
-                               } else {
-                                       $error = $details['error'];
-                                       $args = null;
-                               }
-
-                               $this->showUploadError( $this->msg( $error, 
$args )->parse() );
-                               break;
-                       default:
-                               throw new MWException( __METHOD__ . ": Unknown 
value `{$details['status']}`" );
-               }
-       }
-
-       /**
-        * Remove a temporarily kept file stashed by saveTempUploadedFile().
-        *
-        * @return Boolean: success
-        */
-       protected function unsaveUploadedFile() {
-               if ( !( $this->mUpload instanceof UploadFromStash ) ) {
-                       return true;
-               }
-               $success = $this->mUpload->unsaveUploadedFile();
-               if ( !$success ) {
-                       $this->showFileDeleteError();
-                       return false;
-               } else {
-                       return true;
-               }
-       }
-
-       /**
-        * Produce error output if uploaded file can't be unsaved.
-        * @since 1.23
-        */
-       protected function showFileDeleteError() {
-               $this->getOutput()->showFileDeleteError( 
$this->mUpload->getTempPath() );
-       }
-
-       /*** Functions for formatting warnings ***/
-
-       /**
-        * Formats a result of UploadBase::getExistsWarning as HTML
-        * This check is static and can be done pre-upload via AJAX
-        *
-        * @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 ) {
-               if ( !$exists ) {
-                       return '';
-               }
-
-               $file = $exists['file'];
-               $filename = $file->getTitle()->getPrefixedText();
-               $warning = '';
-
-               if ( $exists['warning'] == 'exists' ) {
-                       // Exact match
-                       $warning = wfMessage( 'fileexists', $filename 
)->parse();
-               } elseif ( $exists['warning'] == 'page-exists' ) {
-                       // Page exists but file does not
-                       $warning = wfMessage( 'filepageexists', $filename 
)->parse();
-               } elseif ( $exists['warning'] == 'exists-normalized' ) {
-                       $warning = wfMessage( 'fileexists-extension', $filename,
-                               
$exists['normalizedFile']->getTitle()->getPrefixedText() )->parse();
-               } elseif ( $exists['warning'] == 'thumb' ) {
-                       // Swapped argument order compared with other messages 
for backwards compatibility
-                       $warning = wfMessage( 'fileexists-thumbnail-yes',
-                               
$exists['thumbFile']->getTitle()->getPrefixedText(), $filename )->parse();
-               } 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 = wfMessage( 'file-thumbnail-no', $badPart 
)->parse();
-               } elseif ( $exists['warning'] == 'bad-prefix' ) {
-                       $warning = wfMessage( 'filename-bad-prefix', 
$exists['prefix'] )->parse();
-               } elseif ( $exists['warning'] == 'was-deleted' ) {
-                       # If the file existed before and was deleted, warn the 
user of this
-                       $ltitle = SpecialPage::getTitleFor( 'Log' );
-                       $llink = Linker::linkKnown(
-                               $ltitle,
-                               wfMessage( 'deletionlog' )->escaped(),
-                               array(),
-                               array(
-                                       'type' => 'delete',
-                                       'page' => $filename
-                               )
-                       );
-                       $warning = wfMessage( 'filewasdeleted' )->rawParams( 
$llink )->parseAsBlock();
-               }
-
-               return $warning;
-       }
-
-       /**
-        * Construct a warning and a gallery from an array of duplicate files.
-        * @param $dupes array
-        * @return string
-        */
-       public function getDupeWarning( $dupes ) {
-               if ( !$dupes ) {
-                       return '';
-               }
-
-               $gallery = ImageGalleryBase::factory();
-               $gallery->setContext( $this->getContext() );
-               $gallery->setShowBytes( false );
-               foreach ( $dupes as $file ) {
-                       $gallery->add( $file->getTitle() );
-               }
-               return '<li>' .
-                       wfMessage( 'file-exists-duplicate' )->numParams( count( 
$dupes ) )->parse() .
-                       $gallery->toHtml() . "</li>\n";
-       }
-
-       protected function getGroupName() {
-               return 'media';
-       }
-}
-
-/**
- * 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 $mSourceType;
-
-       protected $mSourceIds;
-
-       protected $mMaxFileSize = array();
-
-       protected $mMaxUploadSize = array();
-
-       /**
-        * constructor: make upload form using options provided by SpecialUpload
-        */
-       public function __construct( array $options = array(), IContextSource 
$context = null ) {
-               $this->constructData( $options, $context );
-               $this->constructForm( $context );
-
-               # Set some form properties
-               $this->setSubmitText( $this->msg( 'uploadbtn' )->text() );
-               $this->setSubmitName( 'wpUpload' );
-               # Used message keys: 'accesskey-upload', 'tooltip-upload'
-               $this->setSubmitTooltip( 'upload' );
-               $this->setId( 'mw-upload-form' );
-       }
-
-       /**
-        * Initialize member data and form descriptors using options provided 
by SpecialUpload
-        * @since 1.23
-        */
-       protected function constructData( array $options = array(), 
IContextSource $context = null ) {
-               # setContext is called in the constructor, but it's needed
-               # before we get there
-               $this->setContext( $context );
-
-               $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'] : '';
-
-               $this->mSourceType = isset( $options['sourcetype'] )
-                       ? $options['sourcetype'] : '';
-       }
-
-       protected function constructForm( IContextSource $context ) {
-               $sourceDescriptor = $this->getSourceSection();
-               $descriptor = $sourceDescriptor
-                       + $this->getDescriptionSection()
-                       + $this->getOptionsSection();
-
-               Hooks::run( 'UploadFormInitDescriptor', array( &$descriptor ) );
-               parent::__construct( $descriptor, $context, 'upload' );
-
-               # Build a list of IDs for javascript insertion
-               $this->mSourceIds = array();
-               foreach ( $sourceDescriptor as $field ) {
-                       if ( !empty( $field['id'] ) ) {
-                               $this->mSourceIds[] = $field['id'];
-                       }
-               }
-       }
-
-       /**
-        * Get the descriptor of the fieldset that contains the file source
-        * selection. The section is 'source'
-        *
-        * @return Array: descriptor array
-        */
-       protected function getSourceSection() {
-               global $wgCopyUploadsFromSpecialUpload;
-
-               if ( $this->mSessionKey ) {
-                       return array(
-                               'SessionKey' => array(
-                                       'type' => 'hidden',
-                                       'default' => $this->mSessionKey,
-                               ),
-                               'SourceType' => array(
-                                       'type' => 'hidden',
-                                       'default' => 'Stash',
-                               ),
-                       );
-               }
-
-               $canUploadByUrl = UploadFromUrl::isEnabled()
-                       && UploadFromUrl::isAllowed( $this->getUser() )
-                       && $wgCopyUploadsFromSpecialUpload;
-               $radio = $canUploadByUrl;
-               $selectedSourceType = strtolower( $this->mSourceType );
-
-               $descriptor = array();
-               if ( $this->mTextTop ) {
-                       $descriptor['UploadFormTextTop'] = array(
-                               'type' => 'info',
-                               'section' => 'source',
-                               'default' => $this->mTextTop,
-                               'raw' => true,
-                       );
-               }
-
-               $this->mMaxUploadSize['file'] = UploadBase::getMaxUploadSize( 
'file' );
-               # Limit to upload_max_filesize unless we are running under 
HipHop and
-               # that setting doesn't exist
-               if ( ! ( ( function_exists( 'wfIsHipHop' ) and wfIsHipHop() )
-                       || ( function_exists( 'wfIsHHVM()' ) and wfIsHHVM() ) ) 
) {
-                       $this->mMaxUploadSize['file'] = min( 
$this->mMaxUploadSize['file'],
-                               wfShorthandToInteger( ini_get( 
'upload_max_filesize' ) ),
-                               wfShorthandToInteger( ini_get( 'post_max_size' 
) )
-                       );
-               }
-
-               $descriptor['UploadFile'] = array(
-                       'class' => 'UploadSourceField',
-                       'section' => 'source',
-                       'type' => 'file',
-                       'id' => 'wpUploadFile',
-                       'label-message' => 'sourcefilename',
-                       'upload-type' => 'File',
-                       'radio' => &$radio,
-                       'radio-name' => 'wpSourceType',
-                       'help' => $this->msg( 'upload-maxfilesize',
-                               $this->getContext()->getLanguage()->formatSize( 
$this->mMaxUploadSize['file'] ) )
-                               ->parse() .
-                               $this->msg( 'word-separator' )->escaped() .
-                               $this->msg( 'upload_source_file' )->escaped(),
-                       'checked' => $selectedSourceType == 'file',
-               );
-
-               if ( $canUploadByUrl ) {
-                       $this->mMaxUploadSize['url'] = 
UploadBase::getMaxUploadSize( 'url' );
-                       $descriptor['UploadFileURL'] = array(
-                               'class' => 'UploadSourceField',
-                               'section' => 'source',
-                               'id' => 'wpUploadFileURL',
-                               'label-message' => 'sourceurl',
-                               'upload-type' => 'url',
-                               'radio' => &$radio,
-                               'radio-name' => 'wpSourceType',
-                               'help' => $this->msg( 'upload-maxfilesize',
-                                       
$this->getContext()->getLanguage()->formatSize( $this->mMaxUploadSize['url'] ) )
-                                       ->parse() .
-                                       $this->msg( 'word-separator' 
)->escaped() .
-                                       $this->msg( 'upload_source_url' 
)->escaped(),
-                               'checked' => $selectedSourceType == 'url',
-                       );
-               }
-               Hooks::run( '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 $wgCheckFileExtensions, $wgStrictFileExtensions,
-               $wgFileExtensions, $wgFileBlacklist;
-
-               if ( $wgCheckFileExtensions ) {
-                       if ( $wgStrictFileExtensions ) {
-                               # Everything not permitted is banned
-                               $extensionsList =
-                                       '<div id="mw-upload-permitted">' .
-                                       $this->msg( 'upload-permitted', 
$this->getContext()->getLanguage()->commaList( array_unique( $wgFileExtensions 
) ) )->parseAsBlock() .
-                                       "</div>\n";
-                       } else {
-                               # We have to list both preferred and prohibited
-                               $extensionsList =
-                                       '<div id="mw-upload-preferred">' .
-                                               $this->msg( 'upload-preferred', 
$this->getContext()->getLanguage()->commaList( array_unique( $wgFileExtensions 
) ) )->parseAsBlock() .
-                                       "</div>\n" .
-                                       '<div id="mw-upload-prohibited">' .
-                                               $this->msg( 
'upload-prohibited', $this->getContext()->getLanguage()->commaList( 
array_unique( $wgFileBlacklist ) ) )->parseAsBlock() .
-                                       "</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() {
-               if ( $this->mSessionKey ) {
-                       $stash = 
RepoGroup::singleton()->getLocalRepo()->getUploadStash();
-                       try {
-                               $file = $stash->getFile( $this->mSessionKey );
-                       } catch ( MWException $e ) {
-                               $file = null;
-                       }
-                       if ( $file ) {
-                               global $wgContLang;
-
-                               $mto = $file->transform( array( 'width' => 120 
) );
-                               $this->addHeaderText(
-                                       '<div class="thumb t' . 
$wgContLang->alignEnd() . '">' .
-                                       Html::element( 'img', array(
-                                               'src' => $mto->getUrl(),
-                                               'class' => 'thumbimage',
-                                       ) ) . '</div>', 'description' );
-                       }
-               }
-
-               $descriptor = array(
-                       'DestFile' => array(
-                               'type' => 'text',
-                               'section' => 'description',
-                               'id' => 'wpDestFile',
-                               'label-message' => 'destfilename',
-                               'size' => 60,
-                               'default' => $this->mDestFile,
-                               # @todo 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' => 80,
-                               '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',
-                               'message' => 'edittools-upload',
-                       )
-               );
-
-               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;
-       }
-
-       /**
-        * Get the descriptor of the fieldset that contains the upload options,
-        * such as "watch this file". The section is 'options'
-        *
-        * @return Array: descriptor array
-        */
-       protected function getOptionsSection() {
-               $user = $this->getUser();
-               if ( $user->isLoggedIn() ) {
-                       $descriptor = array(
-                               'Watchthis' => array(
-                                       'type' => 'check',
-                                       'id' => 'wpWatchthis',
-                                       'label-message' => 'watchthisupload',
-                                       'section' => 'options',
-                                       'default' => $this->mWatch,
-                               )
-                       );
-               }
-               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 the OutputPage
-        */
-       protected function addUploadJs() {
-               $out = $this->getOutput();
-               $this->addJsConfigVars( $out );
-               $this->addRLModules( $out );
-       }
-
-       protected function addJsConfigVars( $out ) {
-               global $wgUseAjax, $wgAjaxUploadDestCheck, 
$wgAjaxLicensePreview, $wgEnableAPI, $wgStrictFileExtensions;
-
-               $useAjaxDestCheck = $wgUseAjax && $wgAjaxUploadDestCheck;
-               $useAjaxLicensePreview = $wgUseAjax && $wgAjaxLicensePreview && 
$wgEnableAPI;
-               $this->mMaxUploadSize['*'] = UploadBase::getMaxUploadSize();
-
-               $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 ),
-                       'wgMaxUploadSize' => $this->mMaxUploadSize,
-               );
-
-               $out->addJsConfigVars( $scriptVars );
-       }
-
-       protected function addRLModules( $out ) {
-               $out->addModules( array(
-                       'mediawiki.action.edit', // For <charinsert> support
-                       'mediawiki.legacy.upload', // Old form stuff...
-                       'mediawiki.special.upload', // Newer extras for 
thumbnail preview.
-               ) );
-       }
-
-       /**
-        * Empty function; submission is handled elsewhere.
-        *
-        * @return bool false
-        */
-       function trySubmit() {
-               return false;
-       }
-
-}
-
-/**
- * A form field that contains a radio box in the label
- */
-class UploadSourceField extends HTMLTextField {
-
-       /**
-        * @param $cellAttributes array
-        * @return string
-        */
-       function getLabelHtml( $cellAttributes = array() ) {
-               if ( !empty( $this->mParams['radio'] ) ) {
-                       $id = "wpSourceType{$this->mParams['upload-type']}";
-                       $attribs = array(
-                               'name' => $this->mParams['radio-name'],
-                               'type' => 'radio',
-                               'id' => $this->mParams['radio-name'] . 
$this->mParams['upload-type'],
-                               'value' => $this->mParams['upload-type'],
-                       );
-                       if ( !empty( $this->mParams['checked'] ) ) {
-                               $attribs['checked'] = 'checked';
-                       }
-                       $label = Html::rawElement( 'label', array( 'for' => $id 
), $this->mLabel );
-                       $label .= Html::element( 'input', $attribs );
-               } else {
-                       $id = $this->mParams['id'];
-                       $label = Html::rawElement( 'label', array( 'for' => $id 
), $this->mLabel );
-               }
-               return Html::rawElement( 'td', array( 'class' => 'mw-label' ) + 
$cellAttributes, $label );
-       }
-
-       /**
-        * @return int
-        */
-       function getSize() {
-               return isset( $this->mParams['size'] )
-                       ? $this->mParams['size']
-                       : 60;
-       }
-}
diff --git a/extension.json b/extension.json
index f2cc82d..db561bb 100644
--- a/extension.json
+++ b/extension.json
@@ -1,6 +1,6 @@
 {
        "name": "MultiUpload",
-       "version": "3.0",
+       "version": "3.1-SW",
        "author": [
                "Travis Derouin",
                "Lee Worden"
@@ -75,6 +75,7 @@
                                "mw.FormDataTransport.js"
                        ],
                        "dependencies": [
+                               "jquery.client",
                                "mediawiki.api",
                                "ext.multiupload.top"
                        ],
diff --git a/resources/ext.multiupload.shared.js 
b/resources/ext.multiupload.shared.js
index c7a12f0..661601f 100644
--- a/resources/ext.multiupload.shared.js
+++ b/resources/ext.multiupload.shared.js
@@ -140,7 +140,15 @@
                }
                $fieldset.addClass( 'row' );
                $fieldset.data( 'row-index', i );
-               if ( $.isEmpty( $fieldset.children( 'div.row' ) ) ) {
+               if (
+                       $fieldset.children( 'div.row' ) === '' ||
+                       $fieldset.children( 'div.row' ) === 0 ||
+                       $fieldset.children( 'div.row' ) === "0" ||
+                       $fieldset.children( 'div.row' ) === null ||
+                       $fieldset.children( 'div.row' ) === false ||
+                       $fieldset.children( 'div.row' ) === undefined
+               )
+               {
                        $( '<div>' ).addClass( 'row' )
                                .append( $fieldset.children().not( 'legend' ) )
                                .appendTo( $fieldset );
@@ -189,7 +197,11 @@
 
                // give it a close button if it doesn't have one
                var $cb = $fieldset.find( '.multiupload-close-button-container' 
);
-               if ( $.isEmpty( $cb ) ) {
+               if (
+                       $cb === '' || $cb === 0 || $cb === "0" || $cb === null 
||
+                       $cb === false || $cb === undefined
+               )
+               {
                        $fieldset.children( 'div.row' ).prepend(
                                $( '<div/>' ).addClass( 
'multiupload-close-button-container' ).append(
                                        $( '<span/>' )
@@ -271,10 +283,19 @@
                // append the row after the existing rows.
                $fieldset.hide();
                // TODO: make the animation happen sometime after this returns
-               if ( !$lastfs || $.isEmpty( $lastfs ) ) {
+               if (
+                       !$lastfs ||
+                       $lastfs === '' || $lastfs === 0 || $lastfs === "0" || 
$lastfs === null ||
+                       $lastfs === false || $lastfs === undefined
+               )
+               {
                        $lastfs = mw.libs.ext.multiupload.findLastRow();
                }
-               if ( $.isEmpty( $lastfs ) ) {
+               if (
+                       $lastfs === '' || $lastfs === 0 || $lastfs === "0" || 
$lastfs === null ||
+                       $lastfs === false || $lastfs === undefined
+               )
+               {
                        $( 'form#mw-upload-form' ).prepend( $fieldset );
                } else {
                        $fieldset.insertAfter( $lastfs );
diff --git a/resources/ext.multiupload.unpack.js 
b/resources/ext.multiupload.unpack.js
index 61e5cd8..de007b0 100644
--- a/resources/ext.multiupload.unpack.js
+++ b/resources/ext.multiupload.unpack.js
@@ -1,30 +1,6 @@
 /* global $, mw */
 ( function ( $, mw ) {
 
-function notify( message ) {
-       // message could be a jQuery object, or just a string.
-       try {
-               // if wiki is new enough to have mw.notify(), use it.
-               mw.loader.using( [ 'mediawiki.notification', 'mediawiki.notify' 
], function() {
-                       mw.notify( message );
-               } );
-       } catch ( e ) {
-               // otherwise, use dialog().
-               mw.loader.using( 'jquery.ui.dialog', function() {
-                       var dialogOpts = {
-                               buttons: [ {
-                                       text: mw.message( 
'multiupload-notify-ok' ).plain(),
-                                       click: function () {
-                                               $( this ).dialog( 'close' );
-                                       }
-                               } ]
-                       };
-                       $( '<div/>' ).append( message ).dialog( dialogOpts );
-                       // TODO make it stretch to width of message
-               } );
-       }
-}
-
 function apiErr( code, result, message ) {
        var $errdiv = $( '<div/>' ).append( message );
        var errtxt = code;
@@ -41,7 +17,9 @@
        if ( result && result.error && result.error.messages ) {
                $errdiv.append( '<br/>' + result.error.messages );
        }
-       notify( $errdiv );
+       mw.loader.using( [ 'mediawiki.notification', 'mediawiki.notify' ], 
function() {
+               mw.notify( $errdiv );
+       } );
 }
 
 function unpackOnServer( input, sessionkey, spinnerName ) {
@@ -50,20 +28,19 @@
                        action: 'multiupload-unpack',
                        key: sessionkey,
                        filename: input.files[0].name
-               }, {
-                       ok: function ( data ) {
-                               mw.libs.ext.multiupload.removeTinySpinner( 
spinnerName );
-                               if ( 'multiupload-unpack' in data && 'contents' 
in data['multiupload-unpack'] ) {
-                                       reloadForm( input, 
data['multiupload-unpack'].contents );
-                               } else {
-                                       // TODO: if invalid token, get a new 
one and redo
-                                       notify( 'Error unpacking ' + 
input.files[0].name );
-                               }
-                       },
-                       err: function ( code, result ) {
-                               mw.libs.ext.multiupload.removeTinySpinner( 
spinnerName );
-                               apiErr( code, result, mw.message( 
'multiupload-unpack-error' ).parse() );
+               } ).done( function ( data ) {
+                       mw.libs.ext.multiupload.removeTinySpinner( spinnerName 
);
+                       if ( 'multiupload-unpack' in data && 'contents' in 
data['multiupload-unpack'] ) {
+                               reloadForm( input, 
data['multiupload-unpack'].contents );
+                       } else {
+                               // TODO: if invalid token, get a new one and 
redo
+                               mw.loader.using( [ 'mediawiki.notification', 
'mediawiki.notify' ], function() {
+                                       mw.notify( 'Error unpacking ' + 
input.files[0].name );
+                               } );
                        }
+               } ).fail( function ( code, result ) {
+                       mw.libs.ext.multiupload.removeTinySpinner( spinnerName 
);
+                       apiErr( code, result, mw.message( 
'multiupload-unpack-error' ).parse() );
                } );
        } );
 }
@@ -118,7 +95,7 @@
 }
 
 var enableChunked = ( mw.config.get( 'wgVersion' ).match( /^1\.2[0-9]\./ ) ? 
true : false );
-$.extend( mw.UploadWizard.config,  {
+$.extend( mw.UploadWizard.config, {
        chunkSize: 5 * 1024 * 1024,
        enableChunked: enableChunked,
        maxPhpUploadSize: mw.config.get( 'wgMultiUploadMaxPhpUploadSize' )
diff --git a/resources/mediawiki.special.upload.js.patched.1.21.3 
b/resources/mediawiki.special.upload.js.patched.1.21.3
deleted file mode 100644
index 169f36b..0000000
--- a/resources/mediawiki.special.upload.js.patched.1.21.3
+++ /dev/null
@@ -1,379 +0,0 @@
-/**
- * JavaScript for Special:Upload
- * Note that additional code still lives in skins/common/upload.js
- */
-( function ( mw, $ ) {
-       /**
-        * Add a preview to the upload form
-        */
-       $( document ).ready( function () {
-               /**
-                * Is the FileAPI available with sufficient functionality?
-                */
-               function hasFileAPI() {
-                       return window.FileReader !== undefined;
-               }
-
-               /**
-                * Check if this is a recognizable image type...
-                * Also excludes files over 10M to avoid going insane on memory 
usage.
-                *
-                * @todo is there a way we can ask the browser what's supported 
in <img>s?
-                * @todo put SVG back after working around Firefox 7 bug 
<https://bugzilla.wikimedia.org/show_bug.cgi?id=31643>
-                *
-                * @param {File} file
-                * @return boolean
-                */
-               function fileIsPreviewable( file ) {
-                       var known = ['image/png', 'image/gif', 'image/jpeg', 
'image/svg+xml'],
-                               tooHuge = 10 * 1024 * 1024;
-                       return ( $.inArray( file.type, known ) !== -1 ) && 
file.size > 0 && file.size < tooHuge;
-               }
-
-               /**
-                * Show a thumbnail preview of PNG, JPEG, GIF, and SVG files 
prior to upload
-                * in browsers supporting HTML5 FileAPI.
-                *
-                * As of this writing, known good:
-                * - Firefox 3.6+
-                * - Chrome 7.something
-                *
-                * tableId and thumbnailId parameters must be properly escaped
-                * HTML id attribute values.
-                *
-                * @todo check file size limits and warn of likely failures
-                *
-                * @param {File} file
-                * @param {string} tableId ID of table element above which to 
insert the thumbnail
-                * @param {string} thumbnailId ID to be given to thumbnail 
element
-                */
-               function showPreview( file, tableId, thumbnailId ) {
-                       var $canvas,
-                               ctx,
-                               meta,
-                               previewSize = 180,
-                               $thumb = $( '<div id="' + thumbnailId + '" 
class="thumb tright">' +
-                                                       '<div 
class="thumbinner">' +
-                                                               '<div 
class="mw-small-spinner" style="width: 180px; height: 180px"></div>' +
-                                                               '<div 
class="thumbcaption"><div class="filename"></div><div 
class="fileinfo"></div></div>' +
-                                                       '</div>' +
-                                               '</div>' );
-
-                       $thumb.find( '.filename' ).text( file.name ).end()
-                               .find( '.fileinfo' ).text( prettySize( 
file.size ) ).end();
-
-                       $canvas = $('<canvas width="' + previewSize + '" 
height="' + previewSize + '" ></canvas>');
-                       ctx = $canvas[0].getContext( '2d' );
-                       $( '#' + tableId ).parent().prepend( $thumb );
-
-                       fetchPreview( file, function ( dataURL ) {
-                               var img = new Image(),
-                                       rotation = 0;
-
-                               if ( meta && meta.tiff && meta.tiff.Orientation 
) {
-                                       rotation = ( 360 - ( function () {
-                                               // See includes/media/Bitmap.php
-                                               switch ( 
meta.tiff.Orientation.value ) {
-                                                       case 8:
-                                                               return 90;
-                                                       case 3:
-                                                               return 180;
-                                                       case 6:
-                                                               return 270;
-                                                       default:
-                                                               return 0;
-                                               }
-                                       }() ) ) % 360;
-                               }
-
-                               img.onload = function () {
-                                       var info, width, height, x, y, dx, dy, 
logicalWidth, logicalHeight;
-
-                                       // Fit the image within the 
previewSizexpreviewSize box
-                                       if ( img.width > img.height ) {
-                                               width = previewSize;
-                                               height = img.height / img.width 
* previewSize;
-                                       } else {
-                                               height = previewSize;
-                                               width = img.width / img.height 
* previewSize;
-                                       }
-                                       // Determine the offset required to 
center the image
-                                       dx = (180 - width) / 2;
-                                       dy = (180 - height) / 2;
-                                       switch ( rotation ) {
-                                               // If a rotation is applied, 
the direction of the axis
-                                               // changes as well. You can 
derive the values below by
-                                               // drawing on paper an axis 
system, rotate it and see
-                                               // where the positive axis 
direction is
-                                               case 0:
-                                                       x = dx;
-                                                       y = dy;
-                                                       logicalWidth = 
img.width;
-                                                       logicalHeight = 
img.height;
-                                                       break;
-                                               case 90:
-
-                                                       x = dx;
-                                                       y = dy - previewSize;
-                                                       logicalWidth = 
img.height;
-                                                       logicalHeight = 
img.width;
-                                                       break;
-                                               case 180:
-                                                       x = dx - previewSize;
-                                                       y = dy - previewSize;
-                                                       logicalWidth = 
img.width;
-                                                       logicalHeight = 
img.height;
-                                                       break;
-                                               case 270:
-                                                       x = dx - previewSize;
-                                                       y = dy;
-                                                       logicalWidth = 
img.height;
-                                                       logicalHeight = 
img.width;
-                                                       break;
-                                       }
-
-                                       ctx.clearRect( 0, 0, 180, 180 );
-                                       ctx.rotate( rotation / 180 * Math.PI );
-                                       ctx.drawImage( img, x, y, width, height 
);
-                                       
$thumb.find('.mw-small-spinner').replaceWith($canvas);
-
-                                       // Image size
-                                       info = mw.msg( 'widthheight', 
logicalWidth, logicalHeight ) +
-                                               ', ' + prettySize( file.size );
-
-                                       $( '#' + thumbnailId + ' .fileinfo' 
).text( info );
-                               };
-                               img.src = dataURL;
-                       }, mw.config.get( 'wgFileCanRotate' ) ? function ( data 
) {
-                               /*jshint camelcase: false, nomen: false */
-                               try {
-                                       meta = mw.libs.jpegmeta( data, 
file.fileName );
-                                       meta._binary_data = null;
-                               } catch ( e ) {
-                                       meta = null;
-                               }
-                       } : null );
-               }
-
-               /**
-                * Start loading a file into memory; when complete, pass it as a
-                * data URL to the callback function. If the callbackBinary is 
set it will
-                * first be read as binary and afterwards as data URL. Useful 
if you want
-                * to do preprocessing on the binary data first.
-                *
-                * @param {File} file
-                * @param {function} callback
-                * @param {function} callbackBinary
-                */
-               function fetchPreview( file, callback, callbackBinary ) {
-                       var reader = new FileReader();
-                       if ( callbackBinary && 'readAsBinaryString' in reader ) 
{
-                               // To fetch JPEG metadata we need a binary 
string; start there.
-                               // todo:
-                               reader.onload = function () {
-                                       callbackBinary( reader.result );
-
-                                       // Now run back through the regular 
code path.
-                                       fetchPreview( file, callback );
-                               };
-                               reader.readAsBinaryString( file );
-                       } else if ( callbackBinary && 'readAsArrayBuffer' in 
reader ) {
-                               // readAsArrayBuffer replaces readAsBinaryString
-                               // However, our JPEG metadata library wants a 
string.
-                               // So, this is going to be an ugly conversion.
-                               reader.onload = function () {
-                                       var i,
-                                               buffer = new Uint8Array( 
reader.result ),
-                                               string = '';
-                                       for ( i = 0; i < buffer.byteLength; i++ 
) {
-                                               string += String.fromCharCode( 
buffer[i] );
-                                       }
-                                       callbackBinary( string );
-
-                                       // Now run back through the regular 
code path.
-                                       fetchPreview( file, callback );
-                               };
-                               reader.readAsArrayBuffer( file );
-                       } else if ( 'URL' in window && 'createObjectURL' in 
window.URL ) {
-                               // Supported in Firefox 4.0 and above 
<https://developer.mozilla.org/en/DOM/window.URL.createObjectURL>
-                               // WebKit has it in a namespace for now but 
that's ok. ;)
-                               //
-                               // Lifetime of this URL is until document 
close, which is fine
-                               // for Special:Upload -- if this code gets used 
on longer-running
-                               // pages, add a revokeObjectURL() when it's no 
longer needed.
-                               //
-                               // Prefer this over readAsDataURL for Firefox 7 
due to bug reading
-                               // some SVG files from data URIs 
<https://bugzilla.mozilla.org/show_bug.cgi?id=694165>
-                               callback( window.URL.createObjectURL( file ) );
-                       } else {
-                               // This ends up decoding the file to base-64 
and back again, which
-                               // feels horribly inefficient.
-                               reader.onload = function () {
-                                       callback( reader.result );
-                               };
-                               reader.readAsDataURL( file );
-                       }
-               }
-
-               /**
-                * Format a file size attractively.
-                * @todo match numeric formatting
-                *
-                * @param {number} s
-                * @return string
-                */
-               function prettySize( s ) {
-                       var sizeMsgs = ['size-bytes', 'size-kilobytes', 
'size-megabytes', 'size-gigabytes'];
-                       while ( s >= 1024 && sizeMsgs.length > 1 ) {
-                               s /= 1024;
-                               sizeMsgs = sizeMsgs.slice( 1 );
-                       }
-                       return mw.msg( sizeMsgs[0], Math.round( s ) );
-               }
-
-               /**
-                * Clear the file upload preview area.
-                *
-                * thumbnailId parameter must be a properly escaped
-                * HTML id attribute value.
-                *
-                * @param {string} thumbnailId ID of thumbnail element to be 
removed
-                */
-               function clearPreview( thumbnailId ) {
-                       $( '#' + thumbnailId ).remove();
-               }
-
-               /**
-                * Check if the file does not exceed the maximum size
-                *
-                * fileId and errorId parameters must be properly escaped
-                * HTML id attribute values.
-                *
-                * @param {File} file
-                * @param {string} fileId ID of upload form field
-                * @param {string} errorId ID of element to contain error output
-                */
-               function checkMaxUploadSize( file, fileId, errorId ) {
-                       var maxSize, $error;
-
-                       function getMaxUploadSize( type ) {
-                               var sizes = mw.config.get( 'wgMaxUploadSize' );
-
-                               if ( sizes[type] !== undefined ) {
-                                       return sizes[type];
-                               }
-                               return sizes['*'];
-                       }
-
-                       $( '.' + errorId ).remove();
-
-                       maxSize = getMaxUploadSize( 'file' );
-                       if ( file.size > maxSize ) {
-                               $error = $( '<p class="error 
mw-upload-source-error" id="' + errorId + '">' +
-                                       mw.message( 'largefileserver', 
file.size, maxSize ).escaped() + '</p>' );
-
-                               $( '#' + fileId ).after( $error );
-
-                               return false;
-                       }
-
-                       return true;
-               }
-
-               /**
-                * Initialization
-                *
-                * All parameters must be properly escaped
-                * HTML id attribute values.
-                *
-                * @param {string} fileId ID of upload field
-                * @param {string} thumbnailId ID of thumbnail element
-                * @param {string} errorId ID of element where error output 
will appear
-                * @param {string} tableId ID of table element above which 
thumbnail will be added
-                */
-               window.setupThumbnail = function ( fileId, thumbnailId, 
errorId, tableId ) {
-                       if ( hasFileAPI() ) {
-                               // Update thumbnail when the file selection 
control is updated.
-                               $( '#' + fileId )
-                                       .change( function () {
-                                               clearPreview( thumbnailId );
-                                               if ( this.files && 
this.files.length ) {
-                                                       // Note: would need to 
be updated to handle multiple files.
-                                                       var file = 
this.files[0];
-
-                                                       if ( ! 
checkMaxUploadSize(
-                                                               file,
-                                                               fileId,
-                                                               errorId
-                                                       ) ) {
-                                                               return;
-                                                       }
-
-                                                       if ( fileIsPreviewable( 
file ) ) {
-                                                               showPreview(
-                                                                       file,
-                                                                       tableId,
-                                                                       
thumbnailId
-                                                               );
-                                                       }
-                                               }
-                                       } )
-                                       .change();
-                       }
-               };
-               window.setupThumbnail(
-                       'wpUploadFile',
-                       'mw-upload-thumbnail',
-                       'wpSourceTypeFile-error',
-                       'mw-htmlform-source'
-               );
-       } );
-
-       /**
-        * When there are multiple source fields, allow only one enabled at once
-        *
-        * Parameters errorId and radioName must be appropriately escaped for
-        * insertion directly into jQuery selector strings.
-        *
-        * @param rows selected table rows within which to operate
-        * @param {string} errorId ID of element where source errors go
-        * @param {string} radioName name of radio button selecting source 
fields
-        */
-       window.setupSourceFields = function ( $rows, errorId, radioName ) {
-               var i, $row;
-
-               function createHandler( $currentRow ) {
-                       /**
-                        * @param {jQuery.Event}
-                        */
-                       return function () {
-                               $( '#' + errorId ).remove();
-                               if ( this.checked ) {
-                                       // Disable all inputs
-                                       $rows.find( 'input[name!="' + radioName 
+ '"]' ).prop( 'disabled', true );
-                                       // Re-enable the current one
-                                       $currentRow.find( 'input' ).prop( 
'disabled', false );
-                               }
-                       };
-               }
-
-               for ( i = $rows.length; i; i-- ) {
-                       $row = $rows.eq(i - 1);
-                       $row
-                               .find( 'input[name="' + radioName + '"]' )
-                               .change( createHandler( $row ) );
-               }
-       };
-
-       /**
-        * Disable all upload source fields except the selected one
-        */
-       $( document ).ready( function() {
-               window.setupSourceFields(
-                       $( '.mw-htmlform-field-UploadSourceField' ),
-                       'wpSourceTypeFile-error',
-                       'wpSourceType'
-               );
-       } );
-
-}( mediaWiki, jQuery ) );
diff --git a/resources/mw.FormDataTransport.js 
b/resources/mw.FormDataTransport.js
index 0730974..b3aafc2 100644
--- a/resources/mw.FormDataTransport.js
+++ b/resources/mw.FormDataTransport.js
@@ -15,6 +15,8 @@
 
 
 mw.FormDataTransport = function( postUrl, formData, uploadObject, progressCb, 
transportedCb ) {
+       var profile = $.client.profile();
+
     this.formData = formData;
     this.progressCb = progressCb;
     this.transportedCb = transportedCb;
@@ -32,7 +34,12 @@
     // Workaround for Firefox < 7.0 sending an empty string
     // as filename for Blobs in FormData requests, something PHP does not like
     // https://bugzilla.mozilla.org/show_bug.cgi?id=649150
-    this.gecko = $.browser.mozilla && $.browser.version < '7.0';
+    // From version 7.0 to 22.0, Firefox sends "blob" as the file name
+       // which seems to be accepted by the server
+       // https://bugzilla.mozilla.org/show_bug.cgi?id=690659
+       // 
https://developer.mozilla.org/en-US/docs/Web/API/FormData#Browser_compatibility
+
+       this.insufficientFormDataSupport = profile.name === 'firefox' && 
profile.versionNumber < 7;
 };
 
 mw.FormDataTransport.prototype = {
@@ -174,7 +181,7 @@
             transport.parseResponse(evt, transport.transportedCb);
         }, false);
 
-        if(this.gecko) {
+        if(this.insufficientFormDataSupport) {
             formData = this.geckoFormData();
         } else {
             formData = new FormData();
@@ -196,13 +203,13 @@
             formData.append('filekey', this.filekey);
         }
         formData.append('filesize', bytesAvailable);
-        if(this.gecko) {
+        if(this.insufficientFormDataSupport) {
             formData.appendBlob('chunk', chunk, 'chunk.bin');
         } else {
             formData.append('chunk', chunk);
         }
         this.xhr.open('POST', this.postUrl, true);
-        if(this.gecko) {
+        if(this.insufficientFormDataSupport) {
             formData.send(this.xhr);
         } else {
             this.xhr.send(formData);
diff --git a/resources/upload.js.patched.1.21.3 
b/resources/upload.js.patched.1.21.3
deleted file mode 100644
index 231054c..0000000
--- a/resources/upload.js.patched.1.21.3
+++ /dev/null
@@ -1,518 +0,0 @@
-( function ( mw, $ ) {
-var    licenseSelectorCheck, wgUploadWarningObj, wgUploadLicenseObj, 
fillDestFilename,
-       ajaxUploadDestCheck = mw.config.get( 'wgAjaxUploadDestCheck' ),
-       fileExtensions = mw.config.get( 'wgFileExtensions' );
-
-/**
- * onchange function to show a preview when license selector is changed
- *
- * All parameters must be properly escaped
- * HTML id attribute values.
- *
- * @param {string} licenseId ID of license selector field
- * @param {string} destFileId ID of destination filename field
- * @param {string} previewId ID of element where license preview goes
- */
-licenseSelectorCheck = window.licenseSelectorCheck = function( licenseId, 
destFileId, previewId ) {
-       return function() {
-               var selector = document.getElementById( licenseId ),
-                       selection = 
selector.options[selector.selectedIndex].value;
-               if( selector.selectedIndex > 0 ) {
-                       if ( !selection ) {
-                               // Option disabled, but browser is broken and 
doesn't respect this
-                               selector.selectedIndex = 0;
-                       }
-               }
-               // We might show a preview
-               wgUploadLicenseObj.fetchPreview( selection, licenseId, 
destFileId, previewId );
-       };
-};
-
-/**
- * Insert event handlers, empty elements, generally get form ready for
- * the dynamics operations we're going to do when data is entered.
- *
- * All parameters must be properly escaped
- * HTML id attribute values.
- *
- * @param {string} sourceTypeUrlId ID of the radio button for the 
copy-from-URL field
- * @param {string} uploadUrlId ID of copy-from-URL text field
- * @param {string} licenseId ID of license selector field
- * @param {string} warningId ID to attach to table row where warnings will go
- * @param {string} ackId Id of hidden field recording that warning was 
acknowledged
- * @param {string} destFileId ID of destination filename field
- * @param {string} descriptionId unused
- * @param {string} previewId ID of element where license preview goes
- */
-/** TODO: move away from using IDs as args? */
-window.uploadSetupByIds = function( sourceTypeUrlId, uploadUrlId, licenseId, 
warningId, ackId, destFileId, descriptionId, previewId ) {
-       // Disable URL box if the URL copy upload source type is not selected
-       var ein,
-               selector, ua, isMacIe, i,
-               destElt, destFileRow, destFileTbody, row, td, ackElt,
-               wpLicense, wpLicenseRow, wpLicenseTbody,
-               e = document.getElementById( sourceTypeUrlId );
-       if ( e ) {
-               if ( !e.checked ) {
-                       ein = document.getElementById( uploadUrlId );
-                       if ( ein ) {
-                               ein.disabled = true;
-                       }
-               }
-       }
-
-       // For MSIE/Mac: non-breaking spaces cause the <option> not to render.
-       // But for some reason, setting the text to itself works
-       selector = document.getElementById( licenseId );
-       if ( selector ) {
-               ua = navigator.userAgent;
-               isMacIe = ua.indexOf( 'MSIE' ) !== -1 && ua.indexOf( 'Mac' ) 
!== -1;
-               if ( isMacIe ) {
-                       for ( i = 0; i < selector.options.length; i++ ) {
-                               selector.options[i].text = 
selector.options[i].text;
-                       }
-               }
-       }
-
-       // AJAX wpDestFile warnings
-       if ( ajaxUploadDestCheck ) {
-               // Insert event handlers to fetch upload warnings when 
wpDestFile
-               // has been changed
-               destElt = document.getElementById( destFileId );
-               if ( destElt ) {
-                       ackElt = document.getElementsByName( ackId );
-                       destElt.onchange = function () {
-                               wgUploadWarningObj.checkNow( this, warningId, 
ackElt );
-                       };
-                       destElt.onkeyup = function () {
-                               wgUploadWarningObj.keypress( this, warningId, 
ackElt );
-                       };
-                       if ( ! document.getElementById( warningId ) ) {
-                               // Insert a row where the warnings will be 
displayed just below the
-                               // wpDestFile row
-                               destFileRow = destElt.parentNode.parentNode;
-                               destFileTbody = destFileRow.parentNode;
-
-                               row = document.createElement( 'tr' );
-                               td = document.createElement( 'td' );
-                               td.id = warningId;
-                               td.colSpan = 2;
-
-                               row.appendChild( td );
-                               destFileTbody.insertBefore( row, 
destFileRow.nextSibling );
-                       }
-               }
-       }
-
-       wpLicense = document.getElementById( licenseId );
-       if ( mw.config.get( 'wgAjaxLicensePreview' ) && wpLicense ) {
-               if ( ! document.getElementById( previewId ) ) {
-                       // License selector table row
-                       var wpLicenseRow = wpLicense.parentNode.parentNode;
-                       var wpLicenseTbody = wpLicenseRow.parentNode;
-
-                       var row = document.createElement( 'tr' );
-                       var td = document.createElement( 'td' );
-                       row.appendChild( td );
-                       td = document.createElement( 'td' );
-                       td.id = previewId;
-                       row.appendChild( td );
-
-                       wpLicenseTbody.insertBefore( row, 
wpLicenseRow.nextSibling );
-               }
-
-               // License selector check
-               wpLicense.onchange = licenseSelectorCheck( licenseId, 
destFileId, previewId );
-               // Something might already be selected
-               wpLicense.onchange();
-       }
-};
-
-/**
- * call uploadSetupByIds and do a bit of extra setup
- */
-function uploadSetup() {
-       window.uploadSetupByIds( 'wpSourceTypeurl', 'wpUploadFileURL', 
'wpLicense', 'wpDestFile-warning',
-               'wpDestFileWarningAck', 'wpDestFile', 
'mw-htmlform-description', 'mw-license-preview' );
-
-       // fillDestFile setup
-       var     uploadSourceIds = mw.config.get( 'wgUploadSourceIds' ),
-               i, uploadElt,
-               len = Array.isArray(uploadSourceIds) ? uploadSourceIds.length : 
0,
-               upUrl = document.getElementById( 'wpUploadFileURL' ),
-               destFile = document.getElementById( 'wpDestFile' ),
-               upperm = document.getElementById( 'mw-upload-permitted' ),
-               uppro = document.getElementById( 'mw-upload-prohibited' ),
-               warningId = 'wpDestFile-warning',
-               ackElt = document.getElementsByName( 'wpDestFileWarningAck' ),
-               uploadChange = function () {
-                       fillDestFilename( this, upUrl, destFile, upperm, uppro, 
warningId, ackElt, 'wgUploadAutoFill' );
-               };
-       for ( i = 0; i < len; i += 1 ) {
-               uploadElt = document.getElementById( uploadSourceIds[i] );
-               uploadElt.onchange = uploadChange;
-               uploadElt.onchange();
-       }
-}
-
-/**
- * object to manage Ajax call comparing destination filename to existing
- * file pages
- */
-wgUploadWarningObj = window.wgUploadWarningObj = {
-       responseCache: { '' : '' },
-       delay: 500, // ms
-       timeoutID: false,
-
-       /*
-        * keypress handler for destination filename field.
-        * set a timer to check the destination against existing File:
-        * pages when the uploader stops typing.
-        *
-        * warningId parameter must be a properly escaped
-        * HTML id attribute value.
-        *
-        * @param {HTMLElement} destFile destination filename field
-        * @param {string} warningId ID of element where warning will go
-        * @param {HTMLElement} ackElt hidden field element for warning 
acknowledgement
-        */
-       keypress: function ( destFile, warningId, ackElt ) {
-               var cached;
-
-               if ( !ajaxUploadDestCheck || !sajax_init_object() ) return;
-
-               // Clear timer
-               if ( this.timeoutID ) {
-                       window.clearTimeout( this.timeoutID );
-               }
-               // Check response cache
-               for ( cached in this.responseCache ) {
-                       if ( cached === destFile.value ) {
-                               this.setWarning( this.responseCache[cached], 
warningId, ackElt );
-                               return;
-                       }
-               }
-
-               this.timeoutID = setTimeout( function () {
-                       wgUploadWarningObj.timeout( destFile, warningId, ackElt 
);
-               }, this.delay );
-       },
-
-       /**
-        * Initiate an Ajax check of the destination filename
-        *
-        * warningId parameter must be a properly escaped
-        * HTML id attribute value.
-        *
-        * @param {HTMLElement} field destination filename field
-        * @param {string} warningId ID of element where warning will go
-        * @param {HTMLElement} ackElt hidden field element for warning 
acknowledgement
-        */
-       checkNow: function ( field, warningId, ackElt ) {
-               var cached;
-               if ( !ajaxUploadDestCheck ) {
-                       return;
-               }
-               if ( this.timeoutID ) {
-                       window.clearTimeout( this.timeoutID );
-               }
-               for ( cached in this.responseCache ) {
-                       if ( cached === field.value ) {
-                               this.setWarning( this.responseCache[cached], 
warningId, ackElt );
-                               return;
-                       }
-               }
-               this.timeout( field, warningId, ackElt );
-       },
-
-       /**
-        * Start spinner graphic and make Ajax call
-        *
-        * warningId parameter must be a properly escaped
-        * HTML id attribute value.
-        *
-        * @param {HTMLElement} field destination filename field
-        * @param {string} warningId ID of element where warning will go
-        * @param {HTMLElement} ackElt hidden field element for warning 
acknowledgement
-        */
-       timeout: function ( field, warningId, ackElt ) {
-               var filename = field.value;
-
-               if ( !ajaxUploadDestCheck || !sajax_init_object() || filename 
=== '' ) {
-                       return;
-               }
-
-               $.removeSpinner( warningId );
-               $( field ).injectSpinner( warningId );
-               sajax_do_call(
-                       'SpecialUpload::ajaxGetExistsWarning',
-                       [filename],
-                       function ( result ) {
-                               wgUploadWarningObj.processResult( 
result.responseText, filename, warningId, ackElt );
-                       }
-               );
-       },
-
-       /**
-        * Ajax callback function
-        *
-        * warningId parameter must be a properly escaped
-        * HTML id attribute value.
-        *
-        * @param {string} resulthtml message returned by Ajax call
-        * @param {string} filename the filename being checked
-        * @param {string} warningId ID of element where warning will go
-        * @param {HTMLElement} ackElt hidden field element for warning 
acknowledgement
-        */
-       processResult: function ( resulthtml, filename, warningId, ackElt ) {
-               $.removeSpinner( warningId );
-               this.setWarning( resulthtml, warningId, ackElt );
-               this.responseCache[filename] = resulthtml;
-       },
-
-       /**
-        * Put the warning into the page
-        *
-        * warningId parameter must be a properly escaped
-        * HTML id attribute value.
-        *
-        * @param {string} warning HTML text of warning
-        * @param {string} warningId ID of element where warning will go
-        * @param {HTMLElement} ackElt hidden field element for warning 
acknowledgement
-        */
-       setWarning: function ( warning, warningId, ackElt ) {
-               this.setInnerHTML( warningId, 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[0].value = '';
-               } else {
-                       ackElt[0].value = '1';
-               }
-
-       },
-
-       /**
-        * Put message into element
-        *
-        * id parameter must be a properly escaped
-        * HTML id attribute value.
-        *
-        * @param {string} id ID of element where message will go
-        * @param {string} text HTML text of message
-        */
-       setInnerHTML: function ( id, text ) {
-               var element = document.getElementById( id );
-               // Check for no change to avoid flicker in IE 7
-               if (element.innerHTML != text) {
-                       element.innerHTML = text;
-               }
-       }
-};
-
-/**
- * Autofill the destination filename with a guess built from the source
- * filename.  This is called when either the source-file or source-url is
- * updated.
- *
- * warningId parameter must be a properly escaped HTML id attribute value.
- *
- * @param {HTMLElement} upFile source-file element
- * @param {HTMLElement} upUrl source-url element
- * @param {HTMLElement} destFile destination-filename element
- * @param {HTMLElement} upperm element where upload permissions errors go
- * @param {HTMLElement} uppro element where upload-prohibited messages go
- * @param {string} warningId Id of element where upload warnings will go
- * @param {HTMLElement} ackElt hidden form field recording acknowledgement of 
warnings
- * @param {string} configvar name of mw.config variable controlling whether to 
do auto-filling
- */
-fillDestFilename = window.fillDestFilename =
-  function ( upFile, upUrl, destFile, upperm, uppro, warningId, ackElt, 
configvar ) {
-       var path, slash, backslash, fname, found, ext, i;
-
-       // e.g. mw.config.get( 'wgUploadAutoFill' )
-       if ( !mw.config.get( configvar ) ) {
-               return;
-       }
-       if (!document.getElementById) {
-               return;
-       }
-       // Remove any previously flagged errors
-       if ( upperm ) {
-               upperm.className = '';
-       }
-       if ( uppro ) {
-               uppro.className = '';
-       }
-
-       path = upFile.value;
-       // Find trailing part
-       var slash = path.lastIndexOf('/');
-       var backslash = path.lastIndexOf('\\');
-       var fname;
-       if (slash == -1 && backslash == -1) {
-               fname = path;
-       } else if (slash > backslash) {
-               fname = path.substring(slash+1, 10000);
-       } else {
-               fname = path.substring(backslash+1, 10000);
-       }
-
-       // Clear the filename if it does not have a valid extension.
-       // URLs are less likely to have a useful extension, so don't include 
them in the
-       // extension check.
-       if ( upUrl && mw.config.get( 'wgStrictFileExtensions' ) && 
fileExtensions && upFile.id !== upUrl.id ) {
-               found = false;
-               if ( fname.lastIndexOf( '.' ) !== -1 ) {
-                       var ext = fname.substr( fname.lastIndexOf( '.' ) + 1 );
-                       for ( var i = 0; i < fileExtensions.length; i += 1 ) {
-                               if ( fileExtensions[i].toLowerCase() === 
ext.toLowerCase() ) {
-                                       found = true;
-                                       break;
-                               }
-                       }
-               }
-               if( !found ) {
-                       // Not a valid extension
-                       // Clear the upload and set mw-upload-permitted to error
-                       upFile.value = '';
-                       if ( upperm ) {
-                               upperm.className = 'error';
-                       }
-                       if ( uppro ) {
-                               uppro.className = 'error';
-                       }
-                       // Clear wpDestFile as well
-                       if ( destFile ) {
-                               destFile.value = '';
-                       }
-                       return false;
-               }
-       }
-
-       // Replace spaces by underscores
-       fname = fname.replace( / /g, '_' );
-       // Capitalise first letter if needed
-       if ( mw.config.get( 'wgCapitalizeUploads' ) ) {
-               fname = fname.charAt( 0 ).toUpperCase().concat( 
fname.substring( 1, 10000 ) );
-       }
-
-       // Output result
-       if ( destFile ) {
-               // Call decodeURIComponent function to remove possible 
URL-encoded characters
-               // from the file name (bug 30390). Especially likely with 
upload-form-url.
-               // decodeURIComponent can throw an exception if input is 
invalid utf-8
-               try {
-                       destFile.value = decodeURIComponent( fname );
-               } catch ( e ) {
-                       destFile.value = fname;
-               }
-               destFile.onchange();
-       }
-};
-
-window.toggleFilenameFiller = function() {
-       if(!document.getElementById) return;
-       var upfield = document.getElementById('wpUploadFile');
-       var destName = document.getElementById('wpDestFile').value;
-       wgUploadAutoFill = ( destName == '' || destName == ' ' );
-};
-
-/**
- * object to manage Ajax fetching of license text
- */
-wgUploadLicenseObj = window.wgUploadLicenseObj = {
-
-       'responseCache' : { '' : '' },
-
-       /**
-        * Initiate an Ajax request for license text to preview
-        *
-        * licenseId, destFileId and previewId parameters must be
-        * properly escaped HTML id attribute values.
-        *
-        * @param {string} license value of license selector
-        * @param {string} licenseId ID of license selector
-        * @param {string} destFileId ID of destination filename field
-        * @param {string} previewId ID of element where text will go
-        */
-       fetchPreview: function ( license, licenseId, destFileId, previewId ) {
-               var cached, title;
-               if ( !mw.config.get( 'wgAjaxLicensePreview' ) ) {
-                       return;
-               }
-               for ( cached in this.responseCache ) {
-                       if ( cached === license ) {
-                               this.showPreview( this.responseCache[license], 
previewId );
-                               return;
-                       }
-               }
-               $( '#' + licenseId ).injectSpinner( previewId );
-
-               title = document.getElementById( destFileId ).value;
-               if ( !title ) {
-                       title = 'File:Sample.jpg';
-               }
-
-                var url = mw.util.wikiScript( 'api' )
-                        + '?action=parse&text={{' + encodeURIComponent( 
license ) + '}}'
-                        + '&title=' + encodeURIComponent( title )
-                        + '&prop=text&pst&format=json';
-
-                var req = sajax_init_object();
-                req.onreadystatechange = function() {
-                        if ( req.readyState == 4 && req.status == 200 )
-                                wgUploadLicenseObj.processResult( eval( '(' + 
req.responseText + ')' ), license, previewId );
-                };
-                req.open( 'GET', url, true );
-                req.send( '' );
-
-//             ( new mw.Api() ).get( {
-//                     action: 'parse',
-//                     text: '{{' + license + '}}',
-//                     title: title,
-//                     prop: 'text',
-//                     pst: ''
-//             } ).done( function ( result ) {
-//                     wgUploadLicenseObj.processResult( result, license, 
previewId );
-//             } );
-       },
-
-       /**
-        * Ajax callback function.
-        *
-        * previewId parameter must be a properly escaped
-        * HTML id attribute value.
-        *
-        * @param {XMLHTTPRequest} result request object from ajax call
-        * @param {string} license license selector value
-        * @param {string} previewId ID of element where preview text will go
-        */
-       processResult: function ( result, license, previewId ) {
-               $.removeSpinner( previewId );
-               this.responseCache[license] = result.parse.text['*'];
-               this.showPreview( this.responseCache[license], previewId );
-       },
-
-       /**
-        * Display the license preview
-        *
-        * previewId parameter must be a properly escaped
-        * HTML id attribute value.
-        *
-        * @param {string} preview HTML text of license
-        * @param {string} previewId ID of element where text will go
-        */
-       showPreview: function ( preview, previewId ) {
-               var previewPanel = document.getElementById( previewId );
-               if ( previewPanel.innerHTML !== preview ) {
-                       previewPanel.innerHTML = preview;
-               }
-       }
-
-};
-
-$( document ).ready( uploadSetup );
-
-}( mediaWiki, jQuery ) );

-- 
To view, visit https://gerrit.wikimedia.org/r/334788
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I3390021444af2eafcb54dc1d1b8a399f8a2bb819
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/MultiUpload
Gerrit-Branch: master
Gerrit-Owner: Jack Phoenix <j...@countervandalism.net>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to