MediaWiki:FileUploadWizard.js

    From Wikipedia, the free encyclopedia
    Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
    /*
    * ===============================================================
    *                    FileUploadWizard.js
    * Script for uploading files through a dynamic questionnaire.
    * This is the code to accompany [[Wikipedia:File Upload Wizard]].
    * ===============================================================
    */
    
    var fuwTesting = false;
    var fuwDefaultTextboxLength = 60;
    var fuwDefaultTextareaWidth = '90%';
    var fuwDefaultTextareaLines = 3;
    
    // ================================================================
    // Constructor function of global fuw (= File Upload Wizard) object
    // ================================================================
    function fuwGlobal() {
    
       // see if user is logged in, autoconfirmed, experienced etc.
       this.getUserStatus();
    
       fuwSetVisible('warningLoggedOut', (this.userStatus == 'anon'));
       fuwSetVisible('warningNotConfirmed', (this.userStatus == 'notAutoconfirmed'));
       this.disabled = (this.userStatus == 'anon') || (this.userStatus == 'notAutoconfirmed');
       if (this.disabled) {
          return;
       }
       fuwSetVisible('fuwStartScriptLink', false);
    
       // create the form element to wrap the main ScriptForm area
       // containing input elements of Step2 and Step3
       var frm = fuwGet('fuwScriptForm');
       if (! frm) {
          frm = document.createElement('form');
          frm.id = "fuwScriptForm";
          var area = fuwGet('placeholderScriptForm');
          var parent = area.parentNode;
          parent.insertBefore(frm, area);
          parent.removeChild(area);
          frm.appendChild(area);
       }
       this.ScriptForm = frm;
    
       // create the TargetForm element that contains the file selector
       frm = fuwGet('TargetForm');
       if (! frm) {
          frm = document.createElement('form');
          frm.id = "TargetForm";
          var area = fuwGet('placeholderTargetForm');
          var parent = area.parentNode;
          parent.insertBefore(frm, area);
          parent.removeChild(area);
          frm.appendChild(area);
       }
       this.TargetForm = frm;
    
       // For the testing version, create a third form that will display
       // the contents to be submitted, at the bottom of the page
       if (fuwTesting) {
          frm = fuwGet('fuwTestForm');
          if (! frm) {
             frm = document.createElement('form');
             frm.id = "fuwTestForm";
             var area = fuwGet('placeholderTestForm');
             var parent = area.parentNode;
             parent.insertBefore(frm, area);
             parent.removeChild(area);
             frm.appendChild(area);
          }
          this.TestForm = frm;
       }
    
       // objects to hold cached results during validation and processing
       this.opts = { };
       this.warn = { };
    
       // create the input filename box
       var filebox  = document.createElement('input');
       filebox.id   = 'file';
       filebox.name = 'file';
       filebox.type = 'file';
       filebox.size = fuwDefaultTextboxLength;
       filebox.onchange = fuwValidateFile;
       filebox.accept = 'image/png,image/jpeg,image/gif,image/svg+xml,image/tiff,image/x-xcf,application/pdf,image/vnd.djvu,audio/ogg,video/ogg,audio/rtp-midi,audio/mp3,image/webp,video/webm,audio/opus,video/mpeg,audio/wav,audio/flac';
       fuwAppendInput('file', filebox);
    
       // Default values for API call parameters
       this.UploadOptions = {
          filename : '',
          text     : '',
          comment  : '',
          ignorewarnings : 1,
          watch    : 1
       };
    
       if (fuwTesting) {
          fuwMakeHiddenfield('title', mw.config.get('wgPageName') + "/sandbox", 'SandboxTitle');
          fuwMakeHiddenfield('token', mw.user.tokens.get('csrfToken'), 'SandboxToken');
          fuwMakeHiddenfield('recreate', 1, 'SandboxRecreate');
       }
    
       if (fuwTesting) {
    
          // create the sandbox submit button
          btn = document.createElement('input');
          btn.id = 'SandboxButton';
          btn.value = 'Sandbox';
          btn.name  = 'Sandbox';
          btn.disabled = true;
          btn.type = 'button';
          btn.style.width = '12em';
          btn.onclick = fuwSubmitSandbox;
          fuwAppendInput('SandboxButton', btn);
    
       }
    
       // create the real submit button
       btn = document.createElement('input');
       btn.id = "SubmitButton";
       btn.value = "Upload";
       btn.name = "Upload";
       btn.disabled = true;
       btn.type = "button";
       btn.onclick = fuwSubmitUpload;
       btn.style.width = '12em';
       fuwAppendInput('SubmitButton', btn);
    
       // create the Commons submit button
       btn = document.createElement('input');
       btn.id = "CommonsButton";
       btn.value = "Upload on Commons";
       btn.name  = "Upload_on_Commons";
       btn.disabled = true;
       btn.type = "button";
       btn.onclick = fuwSubmitCommons;
       btn.style.width = '12em';
       fuwAppendInput('CommonsButton', btn);
    
       // create reset buttons
       for (i = 1; i<=2; i++) {
          btn = document.createElement('input');
          btn.id = 'ResetButton' + i;
          btn.value = "Reset form";
          btn.name  = "Reset form";
          btn.type  = "button";
          btn.onclick = fuwReset;
          btn.style.width = '12em';
          fuwAppendInput('ResetButton' + i, btn);
       }
    
       // names of radio button fields
       var optionRadioButtons = {
          // top-level copyright status choice
          'FreeOrNonFree' : ['OptionFree','OptionNonFree','OptionNoGood'],
          // main subsections under OptionFree
          'FreeOptions'   : ['OptionOwnWork', 'OptionThirdParty', 'OptionFreeWebsite',
                             'OptionPDOld', 'OptionPDOther'],
          // main subsections under OptionNonFree
          'NonFreeOptions': ['OptionNFSubject','OptionNF3D','OptionNFExcerpt',
                             'OptionNFCover','OptionNFLogo','OptionNFPortrait',
                             'OptionNFMisc'],
          // response options inside warningFileExists
          'FileExistsOptions': 
                            ['NoOverwrite','OverwriteSame','OverwriteDifferent'],
          // choice of evidence in OptionThirdParty subsection
          'ThirdPartyEvidenceOptions' : 
                            ['ThirdPartyEvidenceOptionLink',
                             'ThirdPartyEvidenceOptionOTRS',
                             'ThirdPartyEvidenceOptionOTRSForthcoming',
                             'ThirdPartyEvidenceOptionNone'],
          // choice of PD status in OptionPDOld subsection
          'PDOldOptions'  : ['PDUSExpired','PDURAA','PDFormality','PDOldOther'],
          // choice of PD status in OptionPDOther subsection
          'PDOtherOptions': ['PDOtherUSGov','PDOtherOfficial','PDOtherSimple',
                             'PDOtherOther'],
          // whether target article is wholly or only partly dedicated to discussing non-free work:
          'NFSubjectCheck': ['NFSubjectCheckDedicated','NFSubjectCheckDiscussed'],
          'NF3DCheck'     : ['NF3DCheckDedicated','NF3DCheckDiscussed'],
          // choice about copyright status of photograph in OptionNF3D
          'NF3DOptions'   : ['NF3DOptionFree','NF3DOptionSame']
       };
       for (var group in optionRadioButtons) {
          var op = optionRadioButtons[group];
          for (i=0; i<op.length; i++) {
             fuwMakeRadiobutton(group, op[i]);
          }
       }
       this.ScriptForm.NoOverwrite.checked = true;
       
       // input fields that trigger special
       // onchange() event handlers for validation:
       fuwMakeTextfield('InputName', fuwValidateFilename);
       fuwMakeTextfield('NFArticle', fuwValidateNFArticle);
    
       // names of input fields that trigger normal
       // validation event handler
       var activeTextfields = [
          'Artist3D','Country3D',
          'Date','OwnWorkCreation','OwnWorkPublication',
          'Author','Source',
          'Permission','ThirdPartyOtherLicense',
          'ThirdPartyEvidenceLink','ThirdPartyOTRSTicket',
          'FreeWebsiteOtherLicense',
          'PDOldAuthorLifetime','Publication',
          'PDOldCountry','PDOldPermission',
          'PDOfficialPermission','PDOtherPermission',
          'NFSubjectPurpose', 'NF3DOrigDate', 'NF3DPurpose',
          'NF3DCreator',
          'NFPortraitDeceased',
          'EditSummary'
       ];
       for (i=0; i<activeTextfields.length; i++) {
          fuwMakeTextfield(activeTextfields[i]);
       }
    
       // names of multiline textareas
       var activeTextareas = [
          'InputDesc','NF3DPermission',
          'NFCommercial','NFPurpose','NFReplaceableText',
          'NFReplaceable','NFCommercial','NFMinimality','AnyOther'
       ];
       for (i=0; i<activeTextareas.length; i++) {
          fuwMakeTextarea(activeTextareas[i]);
       };
    
       var checkboxes = [
          'NFCoverCheckDedicated','NFLogoCheckDedicated','NFPortraitCheckDedicated'
       ];
       for (i=0; i<checkboxes.length; i++) {
          fuwMakeCheckbox(checkboxes[i]);
       };
    
       var licenseLists = {
          'OwnWorkLicense' : 
            // array structure as expected for input to fuwMakeSelection() function.
            // any entry that is a two-element array will be turned into an option
            // (first element is the value, second element is the display string).
            // Entries that are one-element arrays will be the label of an option group.
            // Zero-element arrays mark the end of an option group.
            [
            ['Allow all use as long as others credit you and share it under similar conditions'],
            ['self|GFDL|cc-by-sa-4.0|migration=redundant', 
             'Creative Commons Attribution-Share Alike 4.0 + GFDL (recommended)',
             true],
            ['self|cc-by-sa-4.0',
             'Creative Commons Attribution-Share Alike 4.0'],
            [],
            ['Allow all use as long as others credit you'],
            ['self|cc-by-4.0',
             'Creative Commons Attribution 4.0'],
            [],
            ['Reserve no rights'],
            ['self|cc0',
             'CC0 Universal Public Domain Dedication'],
            []
            ],   
          'ThirdPartyLicense' :
            [
            ['', 'please select the correct license...'],
            ['Freely licensed:'],
            ['cc-by-sa-4.0', 'Creative Commons Attribution-Share Alike (cc-by-sa-4.0)'],
            ['cc-by-4.0', 'Creative Commons Attribution (cc-by-4.0)'],
            ['GFDL', 'GNU Free Documentation License (GFDL)'],
            [],
            ['No rights reserved:'],
            ['PD-author', 'Public domain'],
            [],
            ['Other (see below)'],
            []
            ],   
          'FreeWebsiteLicense' :
            [
            ['', 'please select the correct license...'],
            ['Freely licensed:'],
            ['cc-by-sa-4.0', 'Creative Commons Attribution-Share Alike (cc-by-sa-4.0)'],
            ['cc-by-4.0', 'Creative Commons Attribution (cc-by-4.0)'],
            ['GFDL', 'GNU Free Documentation License (GFDL)'],
            [],
            ['No rights reserved:'],
            ['PD-author', 'Public domain'],
            [],
            ['Other (see below)'],
            []
            ],   
          'USGovLicense' :
           [
           ['PD-USGov', 'US Federal Government'],
           ['PD-USGov-NASA','NASA'],
           ['PD-USGov-Military-Navy','US Navy'],
           ['PD-USGov-NOAA','US National Oceanic and Atmospheric Administration'],
           ['PD-USGov-Military-Air_Force','US Air Force'],
           ['PD-USGov-Military-Army','US Army'],
           ['PD-USGov-USGS','United States Geological Survey']
           ],
          'IneligibleLicense' :
           [
           ['', 'please select one...'],
           ['PD-shape','Item consists solely of simple geometric shapes'],
           ['PD-text','Item consists solely of a few individual words or letters'],
           ['PD-textlogo','Logo or similar item consisting solely of letters and simple geometric shapes'],
           ['PD-chem','Chemical structural formula'],
           ['PD-ineligible','Other kind of item that contains no original authorship']
           ],
          'NFSubjectLicense' :
           [
           ['', 'please select one...'],
           ['Non-free 2D art', '2-dimensional artwork (painting, drawing etc.)'], 
           ['Non-free historic image', 'Unique historic photograph'], 
           ['Non-free fair use in', 'something else (please describe in description field on top)']
           ],
          'NF3DLicense' :
           [
           [, 'please select one...'],
           ['Non-free proposed architecture', 'Architectural proposal (not yet completed)'],
           ['Non-free destroyed architecture', 'Destroyed (or unrecognizably altered) architecture'],
           ['Non-free 3D art', 'Other 3-dimensional creative work (sculpture etc.)']
           ],
          'NFCoverLicense' :
             [
             ['', 'please select one...'],
             ['Non-free book cover', 'Cover page of a book'], 
             ['Non-free album cover', 'Cover of a sound recording (album, single, song, CD)'], 
             ['Non-free game cover', 'Cover of a video/computer game'], 
             ['Non-free magazine cover', 'Cover page of a magazine'], 
             ['Non-free video cover', 'Cover of a video'], 
             ['Non-free software cover', 'Cover of a software product'], 
             ['Non-free product cover', 'Cover of some commercial product'], 
             ['Non-free title-card', 'Title screen of a TV programme'], 
             ['Non-free movie poster', 'Movie poster'], 
             ['Non-free poster', 'Official poster of an event'], 
             ['Non-free fair use in', 'something else (please describe in description field on top)']
             ],
          'NFExcerptLicense' :
             [
             ['', 'please select one...'],
             ['Non-free television screenshot', 'Television screenshot'], 
             ['Non-free film screenshot', 'Movie screenshot'], 
             ['Non-free game screenshot', 'Game screenshot'], 
             ['Non-free video screenshot', 'Video screenshot'], 
             ['Non-free music video screenshot', 'Music video screenshot'], 
             ['Non-free software screenshot', 'Software screenshot'], 
             ['Non-free web screenshot', 'Website screenshot'], 
             ['Non-free speech', 'Audio excerpt from a speech'], 
             ['Non-free audio sample', 'Sound sample of an audio recording'], 
             ['Non-free video sample', 'Sample extract from a video'], 
             ['Non-free sheet music', 'Sheet music representing a musical piece'], 
             ['Non-free comic', 'Panel from a comic, graphic novel, manga etc.'], 
             ['Non-free computer icon', 'Computer icon'], 
             ['Non-free newspaper image', 'Page from a newspaper'], 
             ['Non-free fair use in', 'something else (please describe in description field on top)']
             ],      
          'NFLogoLicense' :
             [
             ['Non-free logo', 'Logo of a company, organization etc.'], 
             ['Non-free seal', 'Official seal, coat of arms etc'], 
             ['Non-free symbol', 'Other official symbol']
             ],
          'NFMiscLicense' :
             [
             ['Non-free fair use in', 'something else (please describe in description field on top)'], 
             ['Non-free historic image', 'Historic photograph'], 
             ['Non-free 2D art', '2-dimensional artwork (painting, drawing etc.)'], 
             ['Non-free currency', 'Depiction of currency (banknotes, coins etc.)'], 
             ['Non-free architectural work', 'Architectural work'], 
             ['Non-free 3D art', 'Other 3-dimensional creative work (sculpture etc.)'], 
             ['Non-free book cover', 'Cover page of a book'], 
             ['Non-free album cover', 'Cover of a sound recording(album, single, song, CD)'], 
             ['Non-free game cover', 'Cover of a video/computer game'], 
             ['Non-free magazine cover', 'Cover page of a magazine'], 
             ['Non-free video cover', 'Cover of a video'], 
             ['Non-free software cover', 'Cover of a software product'], 
             ['Non-free product cover', 'Cover of some commercial product'], 
             ['Non-free title-card', 'Title screen of a TV programme'], 
             ['Non-free movie poster', 'Movie poster'], 
             ['Non-free poster', 'Official poster of an event'], 
             ['Non-free television screenshot', 'Television screenshot'], 
             ['Non-free film screenshot', 'Movie screenshot'], 
             ['Non-free game screenshot', 'Game screenshot'], 
             ['Non-free video screenshot', 'Video screenshot'], 
             ['Non-free music video screenshot', 'Music video screenshot'], 
             ['Non-free software screenshot', 'Software screenshot'], 
             ['Non-free web screenshot', 'Website screenshot'], 
             ['Non-free speech', 'Audio excerpt from a speech'], 
             ['Non-free audio sample', 'Sound sample of an audio recording'], 
             ['Non-free video sample', 'Sample extract from a video'], 
             ['Non-free sheet music', 'Sheet music representing a musical piece'], 
             ['Non-free comic', 'Panel from a comic, graphic novel, manga etc.'], 
             ['Non-free computer icon', 'Computer icon'], 
             ['Non-free newspaper image', 'Page from a newspaper'], 
             ['Non-free logo', 'Logo of a company, organization etc.'], 
             ['Non-free seal', 'Official seal, coat of arms etc'], 
             ['Non-free symbol', 'Other official symbol'], 
             ['Non-free sports uniform', 'Sports uniform'], 
             ['Non-free stamp', 'Stamp'] 
             ],
          'NFExtraLicense' :
             [
             ['', 'none'],
             ['Crown copyright and other governmental sources'],
             ['Non-free Crown copyright', 'UK Crown Copyright'],
             ['Non-free New Zealand Crown Copyright', 'NZ Crown Copyright'],
             ['Non-free Canadian Crown Copyright', 'Canadian Crown Copyright'],
             ['Non-free AUSPIC', 'AUSPIC (Australian Parliament image database)'],
             ['Non-free Philippines government', 'Philippines government'],
             ['Non-free Finnish Defence Forces', 'Finnish Defence Forces'],
             [],
             ['Other individual sources'],
             ['Non-free Denver Public Library image', 'Denver Public Library'],
             ['Non-free ESA media', 'ESA (European Space Agency)'],
             [],
             ['Possibly public domain in other countries'],
             ['Non-free Old-50', 'Author died more than 50 years ago.'],
             ['Non-free Old-70', 'Author died more than 70 years ago.'],
             [],
             ['Some permissions granted, but not completely free'],
             ['Non-free promotional', 'From promotional press kit'],
             ['Non-free with NC', 'Permission granted, but only for educational and/or non-commercial purposes'],
             ['Non-free with ND', 'Permission granted, but no derivative works allowed'],
             ['Non-free with permission', 'Permission granted, but only for Wikipedia'],
             []
             ]
       };
       for (var group in licenseLists) {
          fuwMakeSelection(group, licenseLists[group]);
       }
    
    
       this.knownCommonsLicenses = {
          'self|GFDL|cc-by-sa-all|migration=redundant' : 1,
          'self|Cc-zero' : 1,
          'PD-self' : 1,
          'self|GFDL|cc-by-sa-4.0|migration=redundant' : 1,
          'self|GFDL|cc-by-4.0|migration=redundant' : 1,
          'self|GFDL|cc-by-sa-3.0|migration=redundant' : 1,
          'self|GFDL|cc-by-3.0|migration=redundant' : 1,
          'self|cc-by-sa-4.0' : 1,
          'self|cc-by-sa-3.0' : 1,
          'cc-by-sa-4.0' : 1,
          'cc-by-sa-3.0' : 1,
          'cc-by-sa-2.5' : 1,
          'cc-by-4.0' : 1,
          'cc-by-3.0' : 1,
          'cc-by-2.5' : 1,
          'FAL' : 1,
          'PD-old-100' : 1,
          'PD-old' : 1,
          'PD-Art' : 1,
          'PD-US' : 1,
          'PD-USGov' : 1,
          'PD-USGov-NASA' : 1,
          'PD-USGov-Military-Navy' : 1,
          'PD-ineligible' : 1,
          'Attribution' : 1,
          'Copyrighted free use' : 1
       };
    
    
       // textfields that don't react directly
       // to user input and are used only for assembling stuff:
       if (fuwTesting) {
          fuwMakeTextfield('SandboxSummary', function(){void(0);});
          fuwMakeTextarea('SandboxText', function(){void(0);});
          fuwGet('SandboxSummary').disabled="disabled";
          fuwGet('SandboxText').disabled="disabled";
          fuwGet('SandboxText').rows = 12;
       }
    
       // set links to "_blank" target, so we don't accidentally leave the page,
       // because on some browsers that would destroy all the input the user has already entered
       $('.fuwOutLink a').each(function() {
          this.target = '_blank';
       });
    
       // make main area visible
       fuwSetVisible('UploadScriptArea', true);
    
    }
    // ====================================== 
    // end of fuwGlobal constructor function
    // ======================================
    
    
    
    function fuwRadioClick(e) {
       var ev = e || event;
       var src = ev.target || ev.srcElement;
       //alert('onclick event from ' + src + ' (' + src.value + ')');
       fuwUpdateOptions();
       return true;
    }
    
    /* 
    * =============================================================
    * function fuwUpdateOptions
    * =============================================================
    * This is the onchange event handler for most of the input
    * elements in the main form. It changes visibility and disabled
    * status for the various sections of the input form in response
    * to which options are chosen.
    */
    function fuwUpdateOptions() {
    
       var fuw = window.fuw;
       var warn = fuw.warn;
       var opts = fuw.opts = { };
       opts.InputFilename = $('#TargetForm input#file').val();
    
       var widgets = fuw.ScriptForm.elements;
       for (i = 0; i < widgets.length; i++) {
          var w = widgets[i];
          if (w.type == "radio") {
             var nm = w.name;
             var id = w.id;
             var vl = w.checked && !w.disabled && fuwIsVisible(w);
             opts[id] = vl;
             if (vl) opts[nm] = id;
          }
          else {
             var id = w.id;
             var active = !w.disabled && fuwIsVisible(w);
             if (active) {
                var value = ((type == 'checkbox') ? w.checked : w.value);
                opts[id] = value;         
             }
          }
       };
       opts.MainOption = opts.FreeOptions || opts.NonFreeOptions;
       
       // some parts of the input form are re-used across sections
       // and must be moved into the currently active input section:
    
       // minimality section is shared between all NF sections
       fuwMove('NFMinimalitySection', 'detailsNFSubject', (opts.OptionNFSubject)) ||
       fuwMove('NFMinimalitySection', 'detailsNF3D', (opts.OptionNF3D)) ||
       fuwMove('NFMinimalitySection', 'detailsNFExcerpt', (opts.OptionNFExcerpt)) ||
       fuwMove('NFMinimalitySection', 'detailsNFCover', (opts.OptionNFCover)) ||
       fuwMove('NFMinimalitySection', 'detailsNFLogo', (opts.OptionNFLogo)) ||
       fuwMove('NFMinimalitySection', 'detailsNFPortrait', (opts.OptionNFPortrait)) ||
       fuwMove('NFMinimalitySection', 'detailsNFMisc', true);
    
       // AnyOtherInfo section is shared between all
       fuwMove('AnyOtherInfo', 'detailsOwnWork', opts.OptionOwnWork) ||
       fuwMove('AnyOtherInfo', 'detailsThirdParty', opts.OptionThirdParty) ||
       fuwMove('AnyOtherInfo', 'detailsFreeWebsite', opts.OptionFreeWebsite) ||
       fuwMove('AnyOtherInfo', 'detailsPDOld', opts.OptionPDOld) ||
       fuwMove('AnyOtherInfo', 'detailsPDOther', opts.OptionPDOther) ||
       fuwMove('AnyOtherInfo', 'detailsNFSubject', opts.OptionNFSubject) ||
       fuwMove('AnyOtherInfo', 'detailsNF3D', opts.OptionNF3D) ||
       fuwMove('AnyOtherInfo', 'detailsNFExcerpt', opts.OptionNFExcerpt) ||
       fuwMove('AnyOtherInfo', 'detailsNFCover', opts.OptionNFCover) ||
       fuwMove('AnyOtherInfo', 'detailsNFLogo', opts.OptionNFLogo) ||
       fuwMove('AnyOtherInfo', 'detailsNFPortrait', opts.OptionNFPortrait) ||
       fuwMove('AnyOtherInfo', 'detailsNFMisc', opts.OptionNFMisc);
    
       // author input field is shared between all sections except "Own Work".
       // (will serve for the immediate/photographic author, in those cases where there
       // are two author fields)
       fuwMove('Author', 'placeholderFreeWebsiteAuthor', (opts.OptionFreeWebsite)) ||
       fuwMove('Author', 'placeholderPDOldAuthor', (opts.OptionPDOld)) ||
       fuwMove('Author', 'placeholderPDOtherAuthor', (opts.OptionPDOther)) ||
       fuwMove('Author', 'placeholderNFSubjectAuthor', (opts.OptionNFSubject)) ||
       fuwMove('Author', 'placeholderNF3DAuthor', (opts.OptionNF3D)) ||
       fuwMove('Author', 'placeholderNFExcerptAuthor', (opts.OptionNFExcerpt)) ||
       fuwMove('Author', 'placeholderNFCoverAuthor', (opts.OptionNFCover)) ||
       fuwMove('Author', 'placeholderNFPortraitAuthor', (opts.OptionNFPortrait)) ||
       fuwMove('Author', 'placeholderNFMiscAuthor', (opts.OptionNFMisc)) ||
       fuwMove('Author', 'placeholderAuthor', true);
    
       // source input field is shared between all sections except "Own Work".
       // (will serve for immediate/web source, in those cases where there are two
       // source fields involved)
       fuwMove('Source', 'placeholderFreeWebsiteSource', (opts.OptionFreeWebsite)) ||
       fuwMove('Source', 'placeholderPDOldSource', (opts.OptionPDOld)) ||
       fuwMove('Source', 'placeholderPDOtherSource', (opts.OptionPDOther)) ||
       fuwMove('Source', 'placeholderNFSubjectSource', (opts.OptionNFSubject)) ||
       fuwMove('Source', 'placeholderNF3DSource', (opts.OptionNF3D)) ||
       fuwMove('Source', 'placeholderNFExcerptSource', (opts.OptionNFExcerpt)) ||
       fuwMove('Source', 'placeholderNFCoverSource', (opts.OptionNFCover)) ||
       fuwMove('Source', 'placeholderNFLogoSource', (opts.OptionNFLogo)) ||
       fuwMove('Source', 'placeholderNFPortraitSource', (opts.OptionNFPortrait)) ||
       fuwMove('Source', 'placeholderNFMiscSource', (opts.OptionNFMisc)) ||
       fuwMove('Source', 'placeholderSource', true);
    
       // date input field is shared between all sections except "Logo", which doesn't need it.
       // will serve for derived/photographic date in the case of 3D items
       fuwMove('Date', 'placeholderFreeWebsiteDate', (opts.OptionFreeWebsite)) ||
       fuwMove('Date', 'placeholderThirdPartyDate', (opts.OptionThirdParty)) ||
       fuwMove('Date', 'placeholderPDOldDate', (opts.OptionPDOld)) ||
       fuwMove('Date', 'placeholderPDOtherDate', (opts.OptionPDOther)) ||
       fuwMove('Date', 'placeholderNFSubjectDate', (opts.OptionNFSubject)) ||
       fuwMove('Date', 'placeholderNF3DDate', (opts.OptionNF3D)) ||
       fuwMove('Date', 'placeholderNFExcerptDate', (opts.OptionNFExcerpt)) ||
       fuwMove('Date', 'placeholderNFCoverDate', (opts.OptionNFCover)) ||
       fuwMove('Date', 'placeholderNFPortraitDate', (opts.OptionNFPortrait)) ||
       fuwMove('Date', 'placeholderNFMiscDate', (opts.OptionNFMisc)) ||
       fuwMove('Date', 'placeholderDate', true);
       
       // permission field is shared between ThirdParty and FreeWebsite sections
       fuwMove('Permission', 'placeholderFreeWebsitePermission', (opts.OptionFreeWebsite)) ||
       fuwMove('Permission', 'placeholderPermission', true);
    
       // publication field is shared between PDOld, NFPortrait and NFMisc
       fuwMove('Publication', 'placeholderNFPortraitPublication', (opts.OptionNFPortrait)) ||
       fuwMove('Publication', 'placeholderNFMiscPublication', (opts.OptionNFMisc)) ||
       fuwMove('Publication', 'placeholderPublication', true);
    
       // Purpose, Commercial, Replaceable and ReplaceableText FUR fields are shared
       // between some but not all of the non-free sections
       fuwMove('NFPurpose', 'placeholderNFExcerptPurpose', (opts.OptionNFExcerpt)) ||
       fuwMove('NFPurpose', 'placeholderNFPurpose');
       fuwMove('NFCommercial', 'placeholderNFPortraitCommercial', (opts.OptionNFPortrait)) ||
       fuwMove('NFCommercial', 'placeholderNFCommercial');
       fuwMove('NFReplaceable', 'placeholderNFPortraitReplaceable', (opts.OptionNFPortrait)) ||
       fuwMove('NFReplaceable', 'placeholderNFReplaceable');
       fuwMove('NFReplaceableText', 'placeholderNFExcerptReplaceable', (opts.OptionNFExcerpt)) ||
       fuwMove('NFReplaceableText', 'placeholderNFReplaceableText', true);
    
       // submit button goes to Step1 if user has chosen a plain overwrite of an existing file,
       // and to the active section of Step3 if otherwise
       fuwMove('fuwSubmit', 'UploadScriptStep1', (warn.ImageExists && opts.OverwriteSame)) ||
       fuwMove('fuwSubmit', 'detailsOwnWork', opts.OptionOwnWork) ||
       fuwMove('fuwSubmit', 'detailsThirdParty', opts.OptionThirdParty) ||
       fuwMove('fuwSubmit', 'detailsFreeWebsite', opts.OptionFreeWebsite) ||
       fuwMove('fuwSubmit', 'detailsPDOld', opts.OptionPDOld) ||
       fuwMove('fuwSubmit', 'detailsPDOther', opts.OptionPDOther) ||
       fuwMove('fuwSubmit', 'detailsNFSubject', opts.OptionNFSubject) ||
       fuwMove('fuwSubmit', 'detailsNF3D', opts.OptionNF3D) ||
       fuwMove('fuwSubmit', 'detailsNFExcerpt', opts.OptionNFExcerpt) ||
       fuwMove('fuwSubmit', 'detailsNFCover', opts.OptionNFCover) ||
       fuwMove('fuwSubmit', 'detailsNFLogo', opts.OptionNFLogo) ||
       fuwMove('fuwSubmit', 'detailsNFPortrait', opts.OptionNFPortrait) ||
       fuwMove('fuwSubmit', 'fuwSubmitHost', true);
    
    
       // Show and hide warnings:
    
       // filename-related warnings:
       fuwSetVisible('warningIllegalChars', warn.IllegalChars);
       fuwSetVisible('warningBadFilename',  warn.BadFilename);
       fuwSetVisible('warningImageOnCommons', warn.ImageOnCommons);
       fuwSetVisible('warningImageExists', warn.ImageExists);
       fuwMove('warningImageThumb', 'warningImageOnCommons', warn.ImageOnCommons, true) ||
       fuwMove('warningImageThumb', 'warningImageExists', true, true);
    
    
       // notices related to the top-level options:
       fuwSetVisible('warningWhyNotCommons', opts.OptionFree);
       fuwSetVisible('warningNF', opts.OptionNonFree);
       fuwSetVisible('warningNoGood', opts.OptionNoGood);
    
       // warnings related to non-free "used in" article
       fuwSetVisible('warningNFArticleNotFound', warn.NFArticleNotFound);
       fuwSetVisible('warningNFArticleNotMainspace', warn.NFArticleNotMainspace);
       fuwSetVisible('warningUserspaceDraft', warn.UserspaceDraft);
       fuwSetVisible('warningNFArticleDab', warn.NFArticleDab);
       fuwSetVisible('NFArticleOK', warn.NFArticleOK);
    
       // warnings depending on user status:
       if (fuw.userStatus.match(/problem|newbie|notAutoconfirmed/)) {
          fuwSetVisible('warningFreeWebsite', opts.OptionFreeWebsite);
          fuwSetVisible('warningOwnWork', opts.OptionOwnWork);
          fuwSetVisible('warningPDOther', opts.OptionPDOther);
          fuwSetVisible('warningNFSubject', opts.OptionNFSubject);
       }
    
       // hide main sections in case of intended plain overwrite:   
       fuwSetVisible('UploadScriptStep2', !(warn.ImageExists && opts.OverwriteSame));
       fuwSetVisible('UploadScriptStep3', !(warn.ImageExists && opts.OverwriteSame));
    
       // show/hide top-level options
       fuwSetVisible('detailsFreeStatus', opts.OptionFree);
       fuwSetVisible('sendToCommons', opts.OptionFree);
    
       // show/hide details sections
       fuwSetVisible('detailsNFArticle', opts.OptionNonFree);
       fuwSetVisible('detailsNFWorkType', opts.OptionNonFree);
       fuwSetVisible('detailsOwnWork', opts.OptionOwnWork);
       fuwSetVisible('detailsThirdParty', opts.OptionThirdParty);
       fuwSetVisible('detailsFreeWebsite', opts.OptionFreeWebsite);
       fuwSetVisible('detailsPDOld', opts.OptionPDOld);
       fuwSetVisible('detailsPDOther', opts.OptionPDOther);
       fuwSetVisible('detailsNFSubject', opts.OptionNFSubject);
       fuwSetVisible('detailsNF3D', opts.OptionNF3D);
       fuwSetVisible('detailsNFExcerpt', opts.OptionNFExcerpt);
       fuwSetVisible('detailsNFCover', opts.OptionNFCover);
       fuwSetVisible('detailsNFLogo', opts.OptionNFLogo);
       fuwSetVisible('detailsNFPortrait', opts.OptionNFPortrait);
       fuwSetVisible('detailsNFMisc', opts.OptionNFMisc);
    
       fuwSetVisible('EditSummaryDiv', opts.OverwriteSame || opts.OverwriteDifferent);
    
       // set enabled/disabled
       // It might be useful to adapt this to be more liberal about
       // the order of input, at least for experienced users.
    
       //fuwSetEnabled('Artist3D', opts.PD3D);
       //fuwSetEnabled('Country3D', opts.FOP3D);
       fuwSetEnabled('ThirdPartyEvidenceLink', opts.ThirdPartyEvidenceOptionLink);
       fuwSetEnabled('ThirdPartyOTRSTicket', opts.ThirdPartyEvidenceOptionOTRS);
       fuwSetEnabled('NFSubjectPurpose', opts.NFSubjectCheckDiscussed);
       fuwSetEnabled('NF3DPurpose', opts.NF3DCheckDiscussed);
       fuwSetEnabled('NF3DPermission', opts.NF3DOptionFree);
       fuwSetEnabled('USGovLicense', opts.PDOtherUSGov);
       fuwSetEnabled('PDOfficialPermission', opts.PDOtherOfficial);
       fuwSetEnabled('IneligibleLicense', opts.PDOtherSimple);
       fuwSetEnabled('PDOtherPermission', opts.PDOtherOther);
       fuwSetEnabled('AnyOther', true);
    
       // need to re-collect the remaining (non-radiobutton) input into the opts object again,
       // preparing for validation:
       for (i = 0; i < widgets.length; i++) {
          var w = widgets[i];
          var type = w.type;
    
          if (type != "radio") {
             var id = w.id;
             var active = !w.disabled && fuwIsVisible(w);
             if (active) {
                var value = ((type == 'checkbox') ? w.checked : w.value);
                opts[id] = value;         
             }
          }
       };
    
       // final step of validation: check if input is sufficient for
       // setting the submit buttons active
       var valid = fuw.validateInput();
       var validForCommons = valid && opts.OptionFree && !(opts.OverwriteSame || opts.OverwriteDifferent)
             && !opts.ThirdPartyEvidenceOptionNone;
       fuwSetVisible('sendToCommons', opts.OptionFree);
       fuwSetEnabled('CommonsButton', validForCommons);
       fuwGet('fuwSubmitText').innerHTML = opts.OptionFree ? 
             ("<b>No</b>, I want to upload this file here on this wiki only.<br/>" + 
              "<small>This way it can be used only on the English Wikipedia. However, somebody " +
              "else might still decide to copy it to Commons or use it elsewhere later. If you " +
              "do not want your file to be copied to Commons and deleted locally, consider adding " +
              "{{tl|Keep local}}.</small>") :
             "Upload this file.";
       fuwGet('SubmitButton').value = validForCommons ? "Upload locally" : "Upload";   
       fuwSetEnabled('EditSummary', true);
       fuwSetEnabled('SubmitButton', valid && (fuw.userStatus != 'notAutoconfirmed'));
       if (fuwTesting) {
          fuwSetEnabled('SandboxButton', valid);
       }
    
       // if we're in testing mode, update the Sandbox display fields
       // after each input change. In normal mode, collectInput() will
       // only be needed on submit.
       if (fuwTesting) {
          fuw.collectInput();
          fuw.formatOutput(false);
          fuwSetVisible('placeholderTestForm', true);
       }  
    }
    
    // ============================================================
    // methods of the global fuw object
    // ============================================================
    
    // ============================================================
    // report validation status of filename information
    // ============================================================
    // This is called from within fuw.validateInput(), i.e. every
    // time anything in the whole form is changed. It only reports
    // results that have previously been cached in the opts and warn
    // objects. The actual checking is done in the event handler
    // of the file input boxes.
    fuwGlobal.prototype.hasValidFilename = function() {
       var opts = this.opts;
       var warn = this.warn;
       var valid =   
          opts.InputName &&
          opts.InputFilename &&
          !warn.BadFilename &&
          !warn.ImageOnCommons &&
          // if image exists on enwiki, accept only if user has confirmed overwrite:
          !(warn.ImageExists && !(opts.OverwriteSame || opts.OverwriteDifferent));
       //alert("HasValidFilename: " + valid);
       return valid;
    };
    
    // ============================================================
    // validation status for common input elements for all free
    // options
    // ============================================================
    fuwGlobal.prototype.hasValidCommonFreeInput = function() {
       var opts = this.opts;
       var warn = this.warn;
       var valid = opts.InputDesc;
       //alert("HasValidCommonFreeInput: " + valid);
       return valid;
    };
    // ============================================================
    // validation status for common input elements for all non-free
    // options
    // ============================================================
    fuwGlobal.prototype.hasValidCommonNFInput = function() {
       var opts = this.opts;
       var warn = this.warn;
       var valid =
          opts.OptionNonFree &&
          opts.InputDesc && 
          opts.NFArticle &&
          opts.Source &&
          opts.NFMinimality &&
          !warn.NFArticleNotFound &&
          !warn.NFArticleNotMainspace &&
          !warn.NFArticleDab;
       //alert("hasValidCommonNFInput: " + valid);
       return valid;
    };
    // ============================================================
    // Main validation routine. Modify this to tweak which fields
    // are to be considered obligatory for each case group
    // ============================================================
    fuwGlobal.prototype.validateInput = function() {
       var opts = this.opts;
       var warn = this.warn;
       var valid = (
          this.hasValidFilename()
          &&
          (! (opts.OverwriteDifferent && ! opts.EditSummary))
          &&
          (
           ( // overwriting is okay if there is an edit summary
            opts.OverwriteSame && opts.EditSummary
           )
           ||
           ( // free options
             this.hasValidCommonFreeInput() &&
             (
              (opts.OptionOwnWork &&
               opts.Date &&
               opts.OwnWorkLicense)
              ||
              (opts.OptionThirdParty &&
               opts.Author &&
               opts.Source &&
               opts.Permission &&
               (opts.ThirdPartyOtherLicense || opts.ThirdPartyLicense) &&
               ((opts.ThirdPartyEvidenceOptionLink && opts.ThirdPartyEvidenceLink) ||
                opts.ThirdPartyEvidenceOptionOTRS ||
                opts.ThirdPartyEvidenceOptionOTRSForthcoming ||
                opts.ThirdPartyEvidenceOptionNone))
              ||
              (opts.OptionFreeWebsite &&
               opts.Author &&
               opts.Source &&
               (opts.FreeWebsiteOtherLicense || opts.FreeWebsiteLicense) &&
               opts.Permission)
              ||
              (opts.OptionPDOld &&
               opts.Author &&
               opts.PDOldAuthorLifetime &&
               opts.Publication &&
               opts.Date &&
               opts.Source &&
               opts.PDOldOptions && 
               (! (opts.PDOldOther && ! opts.PDOldPermission)))
              ||
              (opts.OptionPDOther &&
               opts.Author &&
               opts.Source &&
               ((opts.PDOtherUSGov && opts.USGovLicense) ||
                (opts.PDOtherOfficial && opts.PDOfficialPermission) ||
                (opts.PDOtherSimple && opts.IneligibleLicense) ||
                (opts.PDOtherOther && opts.PDOtherPermission)))
            )
           ) // end of free options
           ||
           ( // non-free options
             this.hasValidCommonNFInput() &&
             (
              (opts.OptionNFSubject &&
               opts.NFSubjectLicense &&
               opts.Author &&
               (opts.NFSubjectCheckDedicated ||
               (opts.NFSubjectCheckDiscussed && opts.NFSubjectPurpose)))
              ||
              (opts.OptionNF3D &&
               opts.NF3DLicense &&
               opts.NF3DCreator &&
               opts.Author &&
               (opts.NF3DOptionSame ||
               (opts.NF3DOptionFree || opts.NF3DPermission)) && 
               (opts.NF3DCheckDedicated ||
                (opts.NF3DCheckDiscussed && opts.NF3DPurpose)))
              ||
              (opts.OptionNFExcerpt &&
               opts.NFExcerptLicense &&
               opts.Author &&
               opts.NFPurpose)
              ||
              (opts.OptionNFCover &&
               opts.NFCoverLicense &&
               opts.Author &&
               opts.NFCoverCheckDedicated 
              )
              ||
              (opts.OptionNFLogo &&
               opts.NFLogoLicense &&
               opts.NFLogoCheckDedicated
              )
              ||
              (opts.OptionNFPortrait &&
               opts.Publication &&
               opts.NFPortraitDeceased &&
               opts.Author &&
               opts.NFPortraitCheckDedicated &&
               opts.NFReplaceable &&
               opts.NFCommercial)
              ||
              (opts.OptionNFMisc &&
               opts.NFMiscLicense &&
               opts.Author &&
               opts.Publication &&
               opts.NFPurpose &&
               opts.NFReplaceableText &&
               opts.NFReplaceable &&
               opts.NFCommercial)
             )
           ) // end of non-free options
          )
       );
       return valid;
    };
    
    // =============================================================
    // return which template name will be used as the main
    // description template
    // =============================================================
    fuwGlobal.prototype.getDescriptionTemplateName = function() {
       // standard "Information" template for free files:
       if (this.opts.OptionFree) return "Information";
       // experimental new version of fair-use rationale template,
       // designed to fit the fields used in the wizard
       else if (this.opts.OptionNonFree) return "Non-free use rationale 2";
       return undefined;
    };
    
    // =============================================================
    // get the license tag code from the appropriate input element
    // =============================================================
    
    fuwGlobal.prototype.getStandardLicense = function() {
       var opts = this.opts;
       
    }
    
    fuwGlobal.prototype.getLicense = function() {
       var opts = this.opts;
          // ThirdParty and FreeWebsite have alternative input fields
          // for manual entry of other licenses:
       var license = {};
       if (opts.PDOtherOther || opts.PDOldOther) {
          license.special = opts.PDOtherOther ? opts.PDOtherPermission : opts.PDOldPermission;
          if (! (license.special.match(/^\s*\{\{.+\}\}\s*$/))) {
             license.special = '{{PD-because|' + license.special + '}}';
          }
       }
       else {
          license.special = 
             opts.ThirdPartyOtherLicense || 
             opts.FreeWebsiteOtherLicense ||
             (opts.PDOtherOfficial ? ('{{PD-because|official item legally exempt from copyright in its country of origin}}') : null) ||
             (opts.OptionNFPortrait ? ('{{Non-free biog-pic|' + opts.NFArticle + '}}') : null);
       }   
       if (! license.special) {
          // standard, non-parametrized tag licenses from dropdownbox.
          var simpleLicense = (opts.OptionOwnWork ? opts.OwnWorkLicense : null) ||
              (opts.OptionThirdParty ? opts.ThirdPartyLicense : null) ||
              (opts.OptionFreeWebsite ? opts.FreeWebsiteLicense : null) ||
              (opts.OptionNFSubject ? opts.NFSubjectLicense : null) ||
              (opts.OptionNF3D ? opts.NF3DLicense : null) ||
              (opts.OptionNFExcerpt ? opts.NFExcerptLicense : null) ||
              (opts.OptionNFCover ? opts.NFCoverLicense : null) ||
              (opts.OptionNFLogo ? opts.NFLogoLicense : null) ||
              (opts.OptionNFMisc ? opts.NFMiscLicense : null) ||
              (opts.PDOtherUSGov ? opts.USGovLicense : null) ||
              (opts.PDOtherSimple ? opts.IneligibleLicense : null) ||
              (opts.PDUSExpired ? 'PD-US-expired' : null) ||
              (opts.PDURAA   ? 'PD-URAA' : null) ||
              (opts.PDFormality ? 'PD-US' : null);
    
           // "PD-author" needs parameter, at least on Commons
           if (simpleLicense == 'PD-author') {
              license.special = '{{PD-author|' + opts.Author + '}}';
           }
           else if (this.knownCommonsLicenses[simpleLicense]) {
           // make sure we send only those licenses as "standard" licenses
           // that exist in the Commons license dropdown box
              license.standard = simpleLicense;
           }
           else {
              license.special = '\{\{' + simpleLicense + '\}\}';
           }
       }
       return license;
    };
    
    function fuwSubst(template) {
       return '{{subst:' + template + '}}';
    }
    
    // ===================================================================
    // Produce code for local tracking categories
    // ===================================================================
    fuwGlobal.prototype.getTrackingCategory = function() {
       var opts = this.opts;
       var cat = "";
       if (opts.OptionFreeWebsite) { cat = "Files from freely licensed external sources"; }
       else if (opts.OptionThirdParty) { cat = "Files licensed by third parties"; }
       else if (opts.PDOtherOther || opts.PDOldOther) { cat = "Files with non-standard public domain statements"; }
       else if (opts.OptionNFSubject || opts.OptionNF3D) { cat = "Non-free files uploaded as object of commentary"; }
       if (cat) {
          cat = "\n\{\{Category ordered by date|" + cat + "|" + 
          fuwSubst("CURRENTYEAR") + "|" + fuwSubst("CURRENTMONTH") + "|" + fuwSubst("CURRENTDAY2") + "\}\}";
       }
       return cat;
    };
    
    // ===================================================================
    // Get or create an edit summary for the upload
    // ===================================================================
    // Note: if we work with the api.php interface, we can have separate
    // data for the edit summary and the description page, which is far
    // better than the way the index.php interface does it.
    // TO DO: need to actually define an input element for a manually
    // entered edit summary. Must be obligatory when overwriting files.
    // In other cases we'll use an automatic edit summary.
    // ===================================================================
    fuwGlobal.prototype.getEditSummary = function() {
       var opts = this.opts;
       return (
          (opts.EditSummary ? (opts.EditSummary + ' ([[' + mw.config.get('wgPageName') + '|File Upload Wizard]])') : null)||
          ("Uploading " +
           (
            (opts.OptionOwnWork ? 'a self-made file ' : false) ||
            (opts.OptionThirdParty ? 'a free file from somebody else ' : false) ||
            (opts.OptionFreeWebsite ? 'a file from a free published source ' : false) ||
            (opts.OptionPDOld       ? 'an old public-domain work ' : false) ||
            (opts.OptionPDOther     ? 'a public-domain item ' : false) ||
            (opts.OptionNFSubject   ? 'a non-free work, as object of commentary ' : false) ||
            (opts.OptionNF3D        ? 'a depiction of a non-free 3D artwork ' : false) ||
            (opts.OptionNFExcerpt   ? 'an excerpt from a non-free work ' : false) ||
            (opts.OptionNFCover     ? 'a piece of non-free cover art ' : false) ||
            (opts.OptionNFLogo      ? 'a non-free logo ' : false) ||
            (opts.OptionNFPortrait    ? 'a non-free historic portrait ' : false) ||
            (opts.OptionNFMisc      ? 'a non-free file ' : "")
           )
           + 
           ("using [[" + mw.config.get('wgPageName') + "|File Upload Wizard]]")
          ));
    };
    
    
    function fuwPackInfo(text, forCommons) {
       if (forCommons) {
          // reformat wikilinks embedded in description fields to adapt them for Commons
          text = text.replace(/\[\[([^\]]+)\]\]/g, 
             function(str, p1, offset, s) {
    
                // mark File links as local
                if (p1.match(/^:(File|Image):/)) {
                   return "[[:en" + p1 + "]]";
                }      
                // leave prefixed links unchanged:
                else if (p1.match(/^:[\w\-]+:/)) {
                   return str;
                }
                // if the link is piped, add a prefix only
                else if (p1.match(/.+\|/)) {
                   return "[[:en:" + p1 + "]]";
                }
                // introduce a pipe
                else {
                   return "[[:en:" + p1 + "|" + p1 + "]]";
                }
             }
          );
          return "{{en|" + text + "}}";
       } else return text;
    }
    
    // ================================================================
    // This is the main method called by the event handler for the 
    // (experimental) submit button. Its main task is to collect the 
    // input into a single string of wikitext for the description page.
    // ================================================================
    fuwGlobal.prototype.collectInput = function() {
       var opts = this.opts;
    
       // object representing template fields for filling in
       // the description template. Pre-loaded with some
       // standard settings:
       var descFields = this.descFields = { 
          'Description' : opts.InputDesc,
          'Author'      : opts.Author,
          'Date'        : opts.Date,
          'Source'      : opts.Source
       };
       // "other information" (outside the template)
       this.otherInfo = null;
       
       if (opts.OptionNonFree) {
          descFields.Article = opts.NFArticle;
       }
       
       // add/modify option-specific fields:
       switch (opts.MainOption) {
          case 'OptionOwnWork':
          
             // use standard "source" field for optional "how created?" and 
             // "previously published" input fields.
             descFields.Source = fuwAppendLines([
                (opts.OwnWorkCreation || "{{own}}"), 
                "<br/>\n", 
                fuwSurroundString("'''Previously published:''' ", opts.OwnWorkPublication)]);
             var username = mw.user.getName();
             descFields.Author = '[[User:' + username + '|' + username + ']]';
             break;
    
          case 'OptionThirdParty':
          
             // use standard "permission" field for a compilation of the
             // "permission" input field and the various "evidence" options
             var evidence = (
                opts.ThirdPartyEvidenceOptionLink ? 
                   ("The license statement can be found online at: " + opts.ThirdPartyEvidenceLink) :
                   (opts.ThirdPartyEvidenceOptionOTRS ? 
                   ("The license agreement has been forwarded to OTRS." + 
                      fuwSurroundString(" Ticket: ", opts.ThirdPartyOTRSTicket) + "\{\{OTRS pending|year=" + fuwSubst("CURRENTYEAR") + 
                                                                                  "|month=" + fuwSubst("CURRENTMONTH") + 
                                                                                  "|day=" + fuwSubst("CURRENTDAY2") + "\}\}") :
                   (opts.ThirdPartyEvidenceOptionOTRSForthcoming ? 
                   "The license agreement will be forwarded to OTRS shortly. \{\{OTRS pending|year=" + fuwSubst("CURRENTYEAR") + 
                                                                                  "|month=" + fuwSubst("CURRENTMONTH") + 
                                                                                  "|day=" + fuwSubst("CURRENTDAY2") + "\}\}" :
                   (opts.ThirdPartyEvidenceOptionNone ?
                   "Will be provided on request." : null))));
             descFields.Permission = fuwAppendLines([
                opts.ThirdPartyPermission,
                "<br/>\n",
                fuwSurroundString("'''Evidence:''' ", evidence)]);
             break;
             
          case 'OptionFreeWebsite':
             descFields.Permission = opts.Permission;
             break;
             
          case 'OptionPDOld':
             // add "lifetime" input to "author" field
             descFields.Author = fuwAppendLines([
                opts.Author,
                "<br/>\n",
                fuwSurroundString("(Life time: ", opts.PDOldAuthorLifetime, ")")
             ]);
             
             // combine original and direct source into standard "source" field: 
             descFields.Source = fuwAppendLines([
                fuwSurroundString("'''Original publication''': ", opts.Publication),
                "<br/>\n",
                fuwSurroundString("'''Immediate source''': ", opts.Source)
             ]);
             
             // no standard tag available for "lack-of-registration" PD-US. Need
             // to put this into the "permission" field
             if (opts.PDFormality) 
                descFields.Permission = 
                   "Copyright expired because the work was published without a copyright " +
                   "notice and/or without the necessary copyright registration.";
          
             // add optional "explanation" input to "permission" field
             if (opts.PDOldPermission) {
                descFields.Permission = fuwAppendLines([
                   descFields.Permission,
                   "\n\n",
                   opts.PDOldPermission
                ]);
             }
             break;
             
          case 'OptionPDOther':
             // Need "permission" field in case of "official item" option
             if (opts.PDOtherOfficial) 
                descFields.Permission = opts.PDOfficialPermission;
             break; 
    
          case 'OptionNFSubject':
             // most FUR elements can be automatically provided:
             descFields.Purpose = (
                opts.NFSubjectCheckDedicated ? 
                 ("For visual identification of the object of the article. " +
                  "The article as a whole is dedicated specifically to a discussion of this work.") :
                (opts.NFSubjectCheckDiscussed ?
                 ("To support encyclopedic discussion of this work in this article. " +
                  "The illustration is specifically needed to support the following point(s): " +
                  "<br/>\n" + opts.NFSubjectPurpose) : null)
             );
             
             descFields.Replaceability = "Any derivative work based upon the artwork would be a copyright violation, so creation of a free image is not possible.";
             descFields.Commercial = "The use of a low resolution image of the artwork will not impact the commercial viability of the art.";
             break;
    
          case 'OptionNF3D':
             // complex case: we need to assemble attribution and FUR both for the 
             // original 3D work and for the photographic depiction. Both might be 
             // non-free.
             descFields.Author = fuwAppendLines([
                fuwSurroundString("'''Original work:''' ", opts.NF3DCreator),
                "<br/>\n",
                fuwSurroundString("'''Depiction:''' ", opts.Author)
             ]);
             descFields.Date = fuwAppendLines([
                fuwSurroundString("'''Original work:''' ", opts.NF3DOrigDate),
                "<br/>\n",
                fuwSurroundString("'''Depiction:''' ", opts.Date)
             ]);
             descFields.Purpose = (
                opts.NF3DCheckDedicated ? 
                 ("For visual identification of the object of the article. " +
                  "The article as a whole is dedicated specifically to a discussion of this work.") :
                (opts.NF3DCheckDiscussed ? 
                 ("To support encyclopedic discussion of this work in this article. " +
                  "The illustration is specifically needed to support the following point(s): " +
                  "<br/>\n" + opts.NF3DPurpose) : null)
             );
             descFields.Replaceability = "Any derivative work based upon the artwork would be a copyright violation, so creation of a free image is not possible.";
             descFields.Commercial = "The use of a low resolution image of the artwork will not impact the commercial viability of the art.";
             descFields["Other information"] = (
                opts.NF3DOptionSame ?
                ("The image was created and published by the same author who also " +
                 "holds the rights to the original object, and no alternative depiction " +
                 "could be suitably created.") :
                ("The author of the image has released the photographic work under a " +
                 "free license, or it is in the public domain: " + opts.NF3DPermission)
             );
             break; 
             
          case 'OptionNFExcerpt':
             // FURs for screenshots etc. don't normally need to bother
             // about replaceability (with free images) and with commercial role,
             // but do need to bother about purpose and about replaceability with text.
             descFields.Purpose        = opts.NFPurpose;
             descFields.Replaceability_text = opts.NFReplaceableText;
             descFields.Replaceability = "The software or website from which the screenshot is taken is copyrighted and not released under a free license, so creation of a free image is not possible.";
             descFields.Commercial = "The use of a low resolution screenshot from software or a website will not impact the commercial viability of the software or site.";
             break;
             
          case 'OptionNFCover':
             // cover art gets standard rationales.
             descFields.Purpose = 
                "to serve as the primary means of visual identification " +
                "at the top of the article dedicated to the work in question.";
             descFields.Replaceability = "Any derivative work based upon the cover art would be a copyright violation, so creation of a free image is not possible.";
             descFields.Commercial = "The use of a low resolution image of a work's cover will not impact the commercial viability of the work.";
             break;
     
          case 'OptionNFLogo':
             // logos get standard rationales.
             descFields.Purpose = 
                "to serve as the primary means of visual identification " +
                "at the top of the article dedicated to the entity in question.";
             descFields.Replaceability = "Any derivative work based upon the logo would be a copyright violation, so creation of a free image is not possible.";
             descFields.Commercial = "The use of a low resolution image of an organization's logo in the article about that organization will not impact the commercial viability of the logo.";
             break;
    
          case 'OptionNFPortrait':
             // as with other historic photographs, it is useful to have both
             // original publication and direct source
             descFields.Source = fuwAppendLines([
                fuwSurroundString("'''Original publication''': ", opts.Publication),
                "<br/>\n",
                fuwSurroundString("'''Immediate source''': ", opts.Source)
             ]);   
             descFields.Purpose = 
                "for visual identification of the person in question, " +
                "at the top of their biographical article";
             descFields.Replaceability = opts.NFReplaceable;
             descFields.Commercial = opts.NFCommercial;
             descFields['Other information'] = 
                "The subject of the photograph has been deceased since: " + opts.NFPortraitDeceased;
             break;
             
          case 'OptionNFMisc':
             descFields.Source = fuwAppendLines([
                fuwSurroundString(
                   "'''Original publication''': ", 
                   opts.Publication,
                   "<br/>\n'''Immediate source:''' "),
                "",
                opts.Source
             ]);   
             descFields.Purpose = opts.NFPurpose;
             descFields.Replaceability = opts.NFReplaceable;
             descFields.Replaceability_text = opts.NFReplaceable_text;
             descFields.Commercial = opts.NFCommercial; 
             break;     
       };
    
       if (opts.OptionNonFree) {
          // common stuff for all non-free files:
          
          // Minimality field (same for all NF options):
          descFields.Minimality = opts.NFMinimality;
          
          // append optional "extra license" selector and "AnyOther" fields
          // to "Other information" field:
          descFields['Other information'] = fuwAppendLines([
             descFields['Other information'],
             "<br/>\n",
             fuwSurroundString('\{\{', opts.NFExtraLicense, '\}\}'),
             "<br/>\n",
             opts.AnyOther
          ]);
       }
       else {
          // common stuff for all free files:
          descFields.Other_versions = ''
          this.otherInfo = fuwAppendLines([this.otherInfo, "\n\n", opts.AnyOther]);
       
       }
    
    };
    
    fuwGlobal.prototype.formatOutput = function(forCommons) {
       var baseForm = this.ScriptForm;
       var targetForm = this.TargetForm;
       if (fuwTesting) {
          var testForm   = this.TestForm;
       }
       var opts = this.opts;
       var otherInfo = this.otherInfo;
       var descFields = this.descFields;
    
       var summary = "{{" + this.getDescriptionTemplateName();
    
       // assemble all fields into the wikitext of the description page:
       var fieldOrder = [
          'Source', 'Date', 'Author', 'Permission', 'Other_versions',
          'Article', 'Purpose', 'Replaceability', 'Replaceability_text', 
          'Minimality', 'Commercial', 'Other information'
       ];
       summary += "\n|Description = " + fuwPackInfo(descFields['Description'], forCommons);
       for (var i = 0; i < fieldOrder.length; i++) {
          if (descFields[fieldOrder[i]]) {
             summary += "\n|" + fieldOrder[i] + " = " + descFields[fieldOrder[i]];
          }
       }
       summary += "\n}}\n";
       if (otherInfo) {
          summary += "\n;Other information:\n" + fuwPackInfo(otherInfo, forCommons) + "\n";
       }
    
       var editSummary = this.getEditSummary();
       
       var license = this.getLicense();
       
       if (forCommons) {
          // pack our description info into an url pointing to the 
          // standard Commons Special:Upload
          // with pre-loaded description fields
    
          summary = fuwSubst("Upload marker added by en.wp UW") + "\n" + summary;
          summary = summary.replace(/\{\{OTRS pending\}\}/g, fuwSubst("OP"));
    
          if (license.special) {
             // manually format the whole description page including the license tag, if it
             // isn't one of the bare standard licenses in the dropdown box. Otherwise,
             // submit description summary and license as two separate url parameters.
             summary = summary + "\n\n" + license.special;
          }
          return (fuwGetCommonsURL() +
             "?title=Special:Upload" +
             "&wpUploadDescription=" +
             encodeURIComponent(summary) +
             (license.standard ? 
              ("&wpLicense=" + encodeURIComponent(license.standard)) : '') +
             "&wpDestFile=" + 
             encodeURIComponent(opts.InputName));
       }
       else {
          // pack all description into a single "text" parameter to be submitted
          // to the local api.php upload.
          summary = "==Summary==\n" + 
             summary + 
             "\n==Licensing==\n" + 
             (license.standard ? ("\{\{" + license.standard + "\}\}") : license.special) +
             this.getTrackingCategory();
             
          if (fuwTesting) {
             // Testing mode: show our data in the dummy form
             // at the bottom of the page.
             fuwGet('placeholderSandboxFilename').innerHTML = opts.InputName;
             this.TestForm.SandboxSummary.value = editSummary;
             this.TestForm.SandboxText.value = summary;
             fuwSetVisible('placeholderTestForm', true);
          }
          // Set up API call parameters
          this.UploadOptions.filename = opts.InputName;
          this.UploadOptions.text     = summary;
          this.UploadOptions.comment  = editSummary;
    
       }
       
    };
    
    function fuwHasUserGroup(group) {
       // workaround because old IE versions don't have array.indexOf :-(
       for (i = 0; i < mw.config.get('wgUserGroups').length; i++) {
          if (mw.config.get('wgUserGroups')[i] == group) {
             return true;
          }
       }
       return false
    }
    
    fuwGlobal.prototype.getUserStatus = function() {
       // function to determine the experience status and userrights of the current user:
       // 'anon': not logged in; can't use script.
       // 'notAutoconfirmed': can't use local upload, but may use script to prepare upload for Commons
       // 'newbie': autoconfirmed but editcount < 100 
       //    (may be used in future to adapt instructions more to newbie needs)
       // 'problem': autoconfirmed but has 3 or more image-related warnings or deletion notifications among recent user talk entries
       //    (may be used in future to produce more strongly worded instructions)
       // 'autoconfirmed': regular user
       // 'sysop'
    
       if (mw.config.get('wgUserName')) {
          if (fuwHasUserGroup('sysop')) {
             this.userStatus = 'sysop';
          }
          else if (fuwHasUserGroup('autoconfirmed') || fuwHasUserGroup('confirmed')) {
             this.userStatus = 'autoconfirmed';
             $.ajax({
                url     : mw.util.wikiScript( 'api' ),
                type    : 'GET',
                dataType: 'xml',
                traditional : true,
                data:   {
                         format: 'xml',
                         action: 'query',
                         meta  : 'userinfo',
                         uiprop: 'editcount',
                         prop  : 'revisions',
                         titles: 'User talk' + mw.config.get('wgUserName'),
                         rvprop: 'comment|user',
                         rvlimit: 30
                        },      
                success: function(data) {
                // callback func     
                   var fuw = window.fuw;
                   if (data) {
                      var ui = data.getElementsByTagName('userinfo');
                      if (ui) {
                         var editcount = ui[0].getAttribute('editcount');
                         if (editcount < 100) {
                            fuw.userStatus = 'newbie';
                         }
                      }
                      var revs = data.getElementsByTagName('rev');
                      var countWarn = 0;
                      for (i = 0; i < revs.length; i++) {
                         var rev = revs[i];
                         var usr = rev.getAttribute('user');
                         var cmt = rev.getAttribute('comment');
                         if ((usr == 'ImageTaggingBot') ||
                             (cmt.search(/(tagging for deletion of \[\[File)|(Uploading files missing)|(File (source and )?copyright licensing problem)|(Speedy deletion nomination of \[\[File)|(Notification: listing at \[\[possibly unfree files)/) >= 0)) {
                            countWarn += 1;   
                         }
                      }
                      if (countWarn >= 3) {
                         fuw.userStatus = 'problem';
                      }
                   }
                }
             });
          }
          else {
             this.userStatus = 'notAutoconfirmed';
          }
       }
       else {
          this.userStatus = 'anon';
       }
    };
    
    // =================================================================
    // Convenience function for getting the regular index.php
    // interface of Commons. Not very elegant.
    // =================================================================
    function fuwGetCommonsURL() {
       if (document.URL.match(/^https:/)) 
          return "https://commons.wikimedia.org/w/index.php";
       else
          return "http://commons.wikimedia.org/w/index.php";
    }  
    
    // ==================================================================
    // functions for building form elements
    // ==================================================================
    fuwMakeRadiobutton = function(group, option, checked, event) {
       // Stupid IE7 doesn't get "value" attribute unless it's created in this convoluted way.
       // Annoying.   
       var node = $('<input type="radio" id="' + option + '" name="' + group + '" value="' + option + '"></input>')[0];
       if (checked) node.checked = true;
       node.onclick = event || fuwRadioClick;
       node.onclick = event || fuwRadioClick;
       fuwAppendInput(option, node);
    };
    fuwMakeTextfield = function(label, event) {
       var node  = document.createElement('input');
       node.type = 'text';
       node.name = label;
       node.size = fuwDefaultTextboxLength;
       node.onchange = event || fuwUpdateOptions;
       // only for testing:
       //node.value = label;
       fuwAppendInput(label, node);
    };
    fuwMakeTextarea = function(label, event) {
       var node  = document.createElement('textarea');
       node.name = label;
       node.rows = fuwDefaultTextareaLines;
       node.style.width = fuwDefaultTextareaWidth;
       node.onchange = event || fuwUpdateOptions;
       //only for testing:
       //node.innerHTML = label;
       fuwAppendInput(label, node);
    };
    fuwMakeCheckbox = function(label, checked, event) {
       var node  = document.createElement('input');
       node.name = label;
       node.type = 'checkbox';
       //only for testing:
       //node.title= label;
       node.checked = checked;
       node.onchange = event || fuwUpdateOptions;
       fuwAppendInput(label, node);
    }
    fuwMakeHiddenfield = function(name, value, id) {
       var node   = document.createElement('input');
       node.name  = name;
       node.type  = 'hidden';
       node.value = value;
       fuwAppendInput((id || name), node);
    };
    fuwMakeAnchor = function(label, href, content) {
       var node   = document.createElement('a');
       node.name  = label;
       node.target= "_blank";
       node.href  = href;
       node.innerHTML = content;
       fuwAppendInput(label, node);
    };
    fuwMakeSelection = function(name, values) {
       var root = document.createElement('select');
       var current = root;
       try {
          for (i=0; i<values.length; i++) {
             var line = values[i];
             var entry;
             if (line.length == 0) {
                current = root;
             }
             else if (line.length == 1) {
                entry = document.createElement('optgroup');
                entry.setAttribute('label', line[0]);
                root.appendChild(entry);
                current = entry;
             }
             else {
                entry = document.createElement('option');
                entry.setAttribute('value', line[0]);
                entry.setAttribute('title', '{{' + line[0] + '}}');
                entry.innerHTML = line[1];
                if (line.length > 2) {
                   entry.setAttribute('selected', 'selected');
                } 
                current.appendChild(entry);
             }
          }
       } catch (e) { alert("Name: " + name + ", i=" + i); }
       root.name = name;
       root.onchange = fuwUpdateOptions;
       fuwAppendInput(name, root);
    };
    function fuwMakeWikilink(place, target, redlink, display) {
       
       place = fuwGet(place);
       var id = place.id;
       var anchor;
       if (place.tagName == 'A') {
          anchor = place;
       }
       else {
          anchor = document.createElement('a');
          place.appendChild(anchor);
       }
       anchor.href = mw.util.getUrl(target);
       anchor.title = target;
       anchor.innerHTML = target;
       anchor.className = (redlink ? 'new' : null);
    }
    
    function fuwAppendInput(label, content) {
       // append a newly created input element to an existing
       // span element marked as id="placeholderXYZ"
       var node = fuwGet('placeholder' + label);
       var old  = fuwGet(label);
       if (old) {
          old.parentNode.removeChild(old);
       }
       content.id = content.id || label;
       if (node) {
          while (node.hasChildNodes()) {
             node.removeChild(node.firstChild);
          }
          node.appendChild(content);
       }
    }
    
    // ======================================================
    // move an element away from its current position
    // and append it to a target element if condition is true
    // ======================================================
    function fuwMove(mv, tg, condition, toStart) {
       if (condition) {
          move   = fuwGet(mv);
          target = fuwGet(tg);
          if (move && target) {
             var parent = move.parentNode;
             if (! (target===parent)) {
                parent.removeChild(move);
                if (toStart) {
                   target.insertBefore(move, target.firstChild);
                }
                else {
                   target.appendChild(move);
                }
             }
          }
          else {
             alert("Can't find elements: move=" + mv + "(" + move + "), target=" + tg + "(" + target + ")");
          } 
       }
       return condition;
    }
    
    // ===================================================
    // make an element visible/invisible
    // ===================================================
    function fuwSetVisible(tg, condition) {
       target = fuwGet(tg);
       if (target) {
          if (condition) {
             $(target).show();
          }
          else {
             $(target).hide();
          }
       }
       else {
          alert("Element not found: " +  (tg.nodeType ? tg.id : tg));
       }
    }
    
    // ===================================================
    // set enabled/disabled status for an element and/or
    // all input controls contained in it.
    // ===================================================
    function fuwSetEnabled(tg, condition) {
       target = fuwGet(tg);
       try {
          var elements = (target.tagName.match(/^(input|textarea|select|button|a)$/i) ? 
             [target] :
             $('#' + target.id + ' *'));
          for (i = 0; i<elements.length; i++) {
             if (elements[i].tagName.match(/^(input|textarea|select|button|a)$/i)) {
                elements[i].disabled = (condition ? null : "disabled");
             }
          }
       } catch (e) { alert("Element not found: " +  (tg.nodeType ? tg.id : tg)); }
    }
    
    // ===================================================
    // convenience function to check whether a given
    // element is currenly visible. Needs to check display
    // property of the element and its ancestors
    // ===================================================
    function fuwIsVisible(el) {
       element = fuwGet(el);
       if (!element) return false;
       el = element.id;
       
       var visible = true;
       while (! (element === document.body)) {
          if (element.style.display == "none") {
             visible = false;
             break;
          }
          element = element.parentNode;
       }
       return visible;
    }
    
    // ===================================================
    // cleanup filename
    // ===================================================
    function fuwCleanFilename() {
       var nameBox = window.fuw.ScriptForm.InputName;
       var oldname = name = $.trim(nameBox.value);
    
       if (name) {
          // strip accidentally added [[   ]] or [[:  ]] brackets
          name = name.replace(/(^\[\[:?)|(\]\]$)/g, "");
          // strip accidentally added "File:" prefix
          name = name.replace(/^(File|Image):/, "");
          // replace underscores with spaces
          name = name.replace(/_/g, " ");
          // uppercase first letter
          name = name[0].toUpperCase() + name.slice(1);
       }
       if (oldname != name) {
          nameBox.value = name;
       }
       // always return true so the next validation step will proceed:
       return true;
    }
    
    
    // ==================================================
    // check filename for technically illegal 
    // characters, trying to fix them automatically
    // ==================================================
    function fuwCheckLegalFilename() {
       var nameBox = window.fuw.ScriptForm.InputName;
       var oldname = name = $.trim(nameBox.value);
    
       if (name) {
          // resolve accidentally entered html entities and URI-encoded %XX character codes
          name = name.replace(/\&[a-z]+;/g, fuwHtmlEntityDecode);
          name = name.replace(/(\%[A-F0-9]{2,2})/g, decodeURI);
          // remove illegal characters # < > [ ] | { } /:
          // using a best guess for an acceptable replacement
          name = name.replace(/[<\[\{]/g, "(");
          name = name.replace(/[>\]\}]/g, ")");
          name = name.replace(/[#:\|]/g,  ",");
          name = name.replace(/\//g, "-");
          // remove sequences of tildes
          name = name.replace(/\~{3,}/g, "---");
          // remove initial slash
          name = name.replace(/^\//, "");
       }
       
       if (oldname != name) {
          window.fuw.warn.IllegalChars = true;
          nameBox.value = name;
          return false;
       }
       else {
          window.fuw.warn.IllegalChars = false;
          return true;
       }
    }
    function fuwHtmlEntityDecode(str) {
       // hack to translate accidentally entered html entity code
       // into actual characters
       var ta=document.createElement('textarea');
       ta.innerHTML=str.replace(/</g,'&lt;').replace(/>/g,'&gt;');
       return ta.value;
    }
    
    // =======================================================
    // Check against various common patterns of poorly chosen
    // filenames (too short / too generic)
    // =======================================================
    function fuwCheckPoorFilename() {
       var nameBox = window.fuw.ScriptForm.InputName;
       var name = $.trim(nameBox.value);
    	name = name.replace(/\.(png|gif|jpg|jpeg|xcf|pdf|mid|ogg|ogv|svg|djvu|tiff|tif|oga|mp3|webp|webm|opus|mpg|mpeg|wav|wave|flac)$/i, "");
    
       // name should be at least 10 characters long, excluding file type extension
       var tooShort = (name.length < 10);
       
       // common generic filename patterns: 
       // IMG......jpg
       // Image....jpg
       // DSC......jpg
       // Picture......jpg
       // Pic..........jpg
       // anything that has fewer than 3 alphabetic letters and then just numbers
       var pattern = /^(img|image|dsc|picture|pic)?(\\s*|\\_*|[a-z]{,3})?\\d+$/i;
       var auto = name.match(pattern);
    
       window.fuw.warn.BadFilename = (tooShort || auto);
       return !tooShort && !auto;
    }
    
    // =======================================================
    // check if file extensions match between local filename
    // and target filename input box. Automatically append 
    // appropriate extension to target filename if they don't.
    // =======================================================
    function fuwCheckFileExtension() {
       var nameBox = window.fuw.ScriptForm.InputName;
       var name = $.trim(nameBox.value);
       var fileBox = window.fuw.TargetForm.file;
       var file = fileBox.value;
       
       // cancel check if no filename has been provided yet
       if (!file || !name) return true;
       
       var extensions = /.+\.(png|gif|jpg|jpeg|xcf|pdf|mid|ogg|ogv|svg|djvu|tiff|tif|oga|mp3|webp|webm|opus|mpg|mpeg|wav|wave|flac)$/i;
       var mimetypes = {
          "png"  : "image/png",
          "gif"  : "image/gif",
          "jpg"  : "image/jpeg",
          "jpeg" : "image/jpeg",
          "xcf"  : "image/x-xcf",
          "webp" : "image/webp",
          "pdf"  : "application/pdf",
          "mid"  : "audio/rtp-midi",
          "ogg"  : "audio/ogg",
          "mp3"	 : "audio/mp3",
          "opus" : "audio/opus",
          "wav"  : "audio/wav",
          "wave" : "audio/wav",
          "flac" : "audio/flac",
          "ogv"  : "video/ogg",
          "svg"  : "image/svg+xml",
          "djvu" : "image/vnd.djvu",
          "tiff" : "image/tiff",
          "tif"  : "image/tiff",
          "oga"  : "video/ogg",
          "webm" : "video/webm",
          "mpg"  : "video/mpeg",
          "mpeg" : "video/mpeg"
       };   
    
       var found = extensions.exec(file);
       var fileExt = found ? found[1].toLowerCase() : "";
       found = extensions.exec(name);
       var nameExt = found ? found[1].toLowerCase() : "";
       var mime = mimetypes[fileExt]; 
       
       if (fileExt && mime && (mimetypes[nameExt] != mime)) {
          nameBox.value = name.replace(/\.?$/, ('.' + fileExt));
       }
       return true;
    }
    
    // ============================================================
    // Check if a file under the chosen name already exists,
    // either locally or on Commons.
    // Store results in the fuw.warn object, so warnings will
    // be displayed on the next fuwUpdateOptions() call
    // ============================================================
    function fuwCheckFileExists() {
       // this is an asynchronous AJAX function.
       // results won't yet be present when this function returns.
    
       var nameBox = window.fuw.ScriptForm.InputName;
       var name = $.trim(nameBox.value);
    
       // using the jQuery wrapper for the Ajax functionality:
       $.ajax({
          url     : mw.util.wikiScript( 'api' ),
          type    : 'GET',
          dataType: 'xml',
          traditional : true,
          data:   {
                   format: 'xml',
                   action: 'query',
                   titles: 'File:' + name,
                   prop  : 'imageinfo',
                   iiprop: 'url|user',
                   iiurlwidth: 120
                  },      
          success: function(resp) {
          // callback function, called when API query has succeeded:
             // see if the request has returned info from an existing image:
             var foundlist = resp.getElementsByTagName('ii');
             var exists = (foundlist.length >= 1);
             var isCommons = false;
             if (exists) {
    
                // extract description data from http response.
                // see https://www.mediawiki.org/wiki/API:Properties#imageinfo_.2F_ii 
                // for structure of API response
                var foundImg = foundlist[0];
                isCommons = (foundImg.parentNode.parentNode.getAttribute('imagerepository')=='shared');
    
                // need this data for creating our own image thumb link
                var width = foundImg.getAttribute('thumbwidth');
                var height = foundImg.getAttribute('thumbheight');
                var thumbURL = foundImg.getAttribute('thumburl');
                var lastUser = foundImg.getAttribute('user');
                var descURL = foundImg.getAttribute('descriptionurl');
    
                // API returns link to local description page even for Commons images.
                // However, we want a direct link to Commons.
                if (isCommons) {
                   descURL = descURL.replace(/en\.wikipedia\.org/, "commons.wikimedia.org");
                   descURL = descURL.replace(/\/\/secure\.wikimedia\.org\/wikipedia\/en/, "commons.wikimedia.org");
                }
    
                // build the image info into the warning section of our page:
                thumbDiv = fuwGet('warningImageThumb');
                if (thumbDiv) {
                
                   // make all links point to description page:
                   var thumbA = thumbDiv.getElementsByTagName('a');
                   for (i = 0; i<thumbA.length; i++) {
                      thumbA[i].setAttribute('href', descURL);
                   }
                   // insert the image itself:
                   var thumbImg = thumbDiv.getElementsByTagName('img');
                   if (thumbImg.length > 0) {
                      thumbImg = thumbImg[0];
                      thumbImg.setAttribute('src', thumbURL);
                      thumbImg.setAttribute('width', width);
                      thumbImg.setAttribute('height', height);
                   }
                   // insert the name of the last uploader:
                   var thumbSpan = fuwGet('existingImageUploader');
                   // TO DO: turn this into a proper link
                   if (thumbSpan) thumbSpan.innerHTML = lastUser;
                }
    
                
             }
             warn = window.fuw.warn;
             warn.ImageOnCommons = exists && isCommons;
             warn.ImageExists    = exists && !isCommons;
    
             fuwUpdateOptions();
          }
       });
    }
    
    // ===========================================================
    // onchange event handler for the local filename box
    // ===========================================================
    fuwValidateFile = function() {
       fuwCheckFileExtension();
       fuwUpdateOptions();
    }
    
    // ===========================================================
    // onchange event handler for the name input box
    // ===========================================================
    fuwValidateFilename = function() {
       fuwCleanFilename();
       if (
          fuwCheckLegalFilename() &&
          fuwCheckPoorFilename() &&
          fuwCheckFileExtension()) {
          // after fuwCheckFileExists(),
          // fuwUpdateOptions will be triggered
          // by the callback function after Ajax completion
          fuwCheckFileExists();
       }
       else {
          // if there's been no Ajax call.
          fuwUpdateOptions();
       }
    };
    
    // ==========================================================
    // function fuwValidateNFArticle()
    // ==========================================================
    // This is the validation routine for the obligatory
    // article-to-be-used-in field for non-free files. It queries
    // api.php about the target article through an Ajax call.
    // It will store error info in the fuw.warn object,
    // triggering the following error on the next updateOptions():
    // * warningNFArticleNotFound : target page doesn't exist.
    // * warningNFArticleNotMainspace : target is not an article. 
    // * warningNFArticleDab : target is a disambiguation page.
    // Redirects will automatically be substituted.
    // ==========================================================
    fuwValidateNFArticle = function() {
       
       var nameBox = window.fuw.ScriptForm.NFArticle;
       oldname = name = nameBox.value;
       
       // cleanup article name:
       // automatically fix accidentally added [[ ... ]] and
       // regularize underscores
       name = $.trim(name);
       name = name.replace(/(^\[\[)|(\]\]$)/g, "");
       // automatically fix article names entered as full urls:
       name = name.replace(/^https?:\/\/en\.wikipedia\.org\/wiki\//, "");
       name = name.replace(/^https?:\/\/en\.wikipedia\.org\/w\/index\.php\?title=/, "");
       name = name.replace(/_/g, " ");
       if (name != oldname) nameBox.value = name;
       
       // do nothing more if field was blank
       if (!name) return;
    
       // using the jQuery wrapper for the Ajax functionality:
       $.ajax({
          url     : mw.util.wikiScript( 'api' ),
          type    : 'GET',
          dataType: 'xml',
          traditional : true,
          data:   {
                   format: 'xml',
                   action: 'query',
                   titles: name,
                   prop  : 'info|categories|links'
                  },      
          success: function(resp) {
          // callback function, called when API query has succeeded:
             var errorType = 0;
             var pg = resp.getElementsByTagName('page')[0];
             var title = pg.getAttribute('title');
             var target = title;
             if (pg.getAttribute('missing') != null) {
                // no page found under this title.
                errorType = 1;
             }           
             else {
                var userspace = false;
                var ns = pg.getAttribute('ns');
                var rd = pg.getAttribute('redirect');
                if (ns != 0) {
                   // not a mainspace page!
                   errorType = 2;
    
                   // try to detect if the target might be a user space draft:               
                   if (title.match(new RegExp("User( talk)?:" + mw.config.get('wgUserName')))) {
                      userspace = true;
                   }
                }
                else if (rd != null) {
                   // redirect page
                   // API returns an empty redirect="" attribute if
                   // the page is a redirect
                   var targets = pg.getElementsByTagName('pl');
                   for (i=0; i<targets.length; i++) {
                      var link = targets[i];
                      if (link.getAttribute('ns')==0) {
                         target = link.getAttribute('title');
                         errorType = 3;
                         break;
                      }
                   }
                }
                else {
                   // check for disambiguation categories
                   var cats = pg.getElementsByTagName('cl');
                   for (i=0; i<cats.length; i++) {
                      var cat = cats[i];
                      if (cat.getAttribute('title') == "Category:All disambiguation pages") {
                         errorType = 4;
                         break;
                      }
                   }  
                }
             }
             warn = window.fuw.warn;
             warn.NFArticleNotFound = (errorType==1);
             warn.NFArticleNotMainspace = (errorType==2);
             warn.UserspaceDraft = ((errorType==2) && userspace);
             warn.NFArticleDab = (errorType==4);
             warn.NFArticleOK  = (errorType==0);
    
             // fix links in error messages:
             if (warn.NFArticleNotFound) {
                fuwMakeWikilink(fuwGet('warningNFArticleNotFound').getElementsByTagName('A')[0], target, true);
             }
             else if (warn.NFArticleNotMainspace) {
                fuwMakeWikilink(fuwGet('warningNFArticleNotMainspace').getElementsByTagName('A')[0], target);
             }
             else if (warn.NFArticleDab) {
                fuwMakeWikilink(fuwGet('warningNFArticleDab').getElementsByTagName('A')[0], target);
             }
             else if (warn.NFArticleOK) {
                fuwMakeWikilink(fuwGet('NFArticleOK').getElementsByTagName('A')[0], target);
             }
                           
             if (errorType==3) {
                // automatically replace title with redirect target
                window.fuw.ScriptForm.NFArticle.value = target;
                // need to recursively call validation again now
                //if (confirm(name + " is a redirect. Follow it to " + target + "?")) {
                   fuwValidateNFArticle();
                //}
             }
             else {
                fuwUpdateOptions();
             }          
          }
       });
    };
    
    // ================================================
    // manually reload script (just for testing)
    // ================================================
    function fuwReload() {
       mw.loader.load( 'http://localhost/script/uploadscript.js' );
       fuwReset();
    }
    
    // ================================================
    // reset forms
    // TO DO: add a button that actually triggers this.
    // ================================================
    function fuwReset() {
       var forms = mw.util.$content[0].getElementsByTagName('form');
       for (i = 0; i < forms.length; i++) {
          forms[i].reset();
          window.fuw.warn = { };
          window.fuw.opts = { };
       }
       fuwSetVisible('UploadScriptArea', true);
       fuwSetVisible('fuwSuccess', false);
       fuwSetVisible('fuwWaiting', false);
       fuwUpdateOptions();
    }
    
    // ===============================================
    // convenience functions for string handling
    // ===============================================
    function fuwAppendLines(parts) {
       // assemble a string from an array of strings.
       // treat every second element as a conditional
       // separator that will be included only if 
       // surrounding elements are non-empty.
       var build = "";
       for (var i = 0; i < parts.length; i += 2) {
          if (parts[i]) {
             if (build) build += parts[i - 1];
             build += parts[i];
          }
       }
       return build;
    }
    function fuwSurroundString(prefix, content, suffix) {
       // put a prefix and a suffix on a string, 
       // if the input string is non-empty.
       if (content) 
          return (prefix ? prefix : "") + content + (suffix ? suffix : ""); 
       else return "";
    }
    
    // ========================================================
    // handler for the API response.
    // TO DO: expand stub to add real notification of success,
    // link to new file page, instructions about how to include
    // file in articles, etc.
    // ========================================================
    function fuwUploadCompleted(doc) {
       if (doc) {
          //alert(doc);
          fuwSetVisible('successThumb', false);
    
          var fuw = window.fuw;
          var name = fuw.opts.InputName;
    
          var uploads = doc.getElementsByTagName('upload');
          var success = false;
          for (i = uploads.length-1; i>=0; i--) {
             if (uploads[i].getAttribute('result') == 'Success') {
                success = true;
                // need to get the real resulting filename here; might be different from the requested one in some cases.
                name = uploads[i].getAttribute('filename');
                break;
             }
          }
          if (success) {
    
             // need another ajax call to check the file is actually there,
             // and to retrieve its direct thumb img url:
             $.ajax({
                url     : mw.util.wikiScript( 'api' ),
                type    : 'GET',
                dataType: 'xml',
                traditional : true,
                data:   {
                         format: 'xml',
                         action: 'query',
                         titles: 'File:' + name,
                         prop  : 'imageinfo',
                         iiprop: 'url',
                         iiurlwidth: 120
                        },      
                success: function(resp) {
                   // callback function, called when API query has succeeded:
                   // see if the request has returned info from an existing image:
    
                   var foundImg = resp.getElementsByTagName('ii')[0];
                   if (foundImg) {
    
                      // need this data for creating our own image thumb link
                      var width = foundImg.getAttribute('thumbwidth');
                      var height = foundImg.getAttribute('thumbheight');
                      var thumbURL = foundImg.getAttribute('thumburl');
                      var lastUser = foundImg.getAttribute('user');
                      var descURL = foundImg.getAttribute('descriptionurl');
    
                      // build the thumbnail in the success message:
                      thumbDiv = fuwGet('successThumb');
                      
                      // make link point to description page:
                      var thumbA = thumbDiv.getElementsByTagName('a')[0];
                      thumbA.href = descURL;
    
                      // insert the image itself:
                      var thumbImg = thumbDiv.getElementsByTagName('img')[0];
                      thumbImg.setAttribute('src', thumbURL);
                      thumbImg.setAttribute('width', width);
                      thumbImg.setAttribute('height', height);
                      
                      fuwSetVisible(thumbDiv, true);
                   }
                }
             });
             fuwMakeWikilink(
                fuwGet('fuwSuccessLink2').getElementsByTagName('a')[0],
                'File:' + name);
             fuwGet('placeholderExFilename1').innerHTML = name;
             fuwGet('placeholderExFilename2').innerHTML = name;
             fuwSetVisible('fuwSuccess', true);
             fuwSetVisible('fuwWaiting', false);
          }
          else {
             var err = doc.getElementsByTagName('error');
             if (err) {
                var info = err[0].getAttribute('info');
                var details = err[0].getElementsByTagName('detail');
                var add = "";
                for (i = 0; i < details.length; i++) {
                   if (add.length > 0) add += ", ";
                   add += details[i].textContent;
                }
                if (add) {
                   info = info + " (" + add + ")";
                }
                alert("Upload failed: " + info);
             }
             else {
                alert("Unknown error: upload may have failed.");
             }
          }
       }
    }
    
    // ========================================================
    // Event handler for the real submit button
    // ========================================================
    function fuwSubmitUpload() {
       
       var fuw = window.fuw;
    
       fuw.collectInput();
       fuw.formatOutput(false);
    
       if (fuwTesting) {
          fuwSetVisible('placeholderTestForm', false);
       }
       fuwSetVisible('UploadScriptArea', false);
    
       fuwMakeWikilink(
         fuwGet('fuwSuccessLink').getElementsByTagName('a')[0], 'File:' + fuw.opts.InputName);
       fuwSetVisible('fuwWaiting', true);
    
       // Upload the file, then add success notification etc.
       // Note that mw.Api doesn't have special support for the XML format, so even when the API
       // returns an error, this won't be detected and it will call the success handler.
       var api = new mw.Api({
          parameters : { format   : 'xml' },
          ajax       : { dataType : 'xml' }
       });
       api.upload(fuw.TargetForm.file, fuw.UploadOptions).then(fuwUploadCompleted);
    
       var opts = window.fuw.opts;
       // the API won't overwrite the description page text while overwriting
       // a file, which is really, really, really annoying and stupid.
       // So in the opts.OverwriteDifferent scenario, we need to edit
       // the description page through a separate ajax call. Dang.
       if (opts.OverwriteDifferent) {
          $.ajax({
             url   : mw.util.wikiScript('api'),
             type  : 'POST',
             dataType : 'xml',
             data  : {
                      format : 'xml',
                      action : 'edit',
                      title  : 'File:' + opts.InputName,
                      token  : mw.user.tokens.get('csrfToken'),
                      summary : opts.EditSummary,
                      text   : fuw.UploadOptions.text
    
                     }
          });
       }
    }
    
    // =======================================================
    // Event handler for the Commons submit button
    // =======================================================
    function fuwSubmitCommons() {
       var fuw = window.fuw;
       fuw.collectInput();
       var url = fuw.formatOutput(true);
       alert("You will now be redirected to Commons. \nPlease use the Commons upload form to add categories to your file description, and then complete the upload.");
       window.location = url;
    }
    
    // =======================================================
    // Event handler for the test submit button
    // (write description string to sandbox only)
    // =======================================================
    function fuwSubmitSandbox() {
       var frm = window.fuw.TestForm;
       $.ajax({
          url     : mw.util.wikiScript( 'api' ),
          type    : 'POST',
          dataType: 'xml',
          data:   {
                   format: 'xml',
                   action: 'edit',
                   title : mw.config.get('wgPageName') + "/sandbox",
                   token : mw.user.tokens.get('csrfToken'),
                   recreate : 1,
                   summary  : frm.SandboxSummary.value,
                   text     : frm.SandboxText.value
                  },      
          success: function(resp) {
             alert("Sandbox page edited!");
          }
       });
    }
    
    
    
    // ========================================================
    // convenience wrapper function to replace calls to
    // document.getElementById() 
    // to avoid browser incompatibility
    // ========================================================
    function fuwGet(target) {
       if (target && target.nodeType) return target;
       else {
          var found = $('#' + target);
          if (found) return found[0];
       }
       return undefined;
    }
    
    // ========================================================
    // onload hook function, loading this script
    // ========================================================
    mw.loader.using(['mediawiki.api']).then(function() {
       $(function() {
          if (fuwGet('UploadScriptArea')) {
             window.fuw = new fuwGlobal();
             if (! window.fuw.disabled) {
                fuwUpdateOptions();
             }
          }
       });
    });