Changeset 1371


Ignore:
Timestamp:
02/06/18 08:37:50 (4 months ago)
Author:
gogo
Message:

Updates to MootoolsFileManager? - most importantly removal of Flash dependancy, now uses HTML5 file uploads.

Also now updated the default MooTools? to 1.6.0 - you can of course load your own MooTools? first (before Xinha), anything down to about 1.3 works I think, if you load yours then it will be used instead.

Other updates from https://github.com/sleemanj/mootools-filemanager included in this update are:

  • Remove space from allowed characters in filenames, they will be converted to _ on upload
  • Fix for a small (inconsequential) uppercase extension bug
  • Add some more php extensions to the "unsafe" list
  • Fix image resize on upload to be more space efficient
Location:
trunk/plugins/MootoolsFileManager
Files:
2 added
11 edited

Legend:

Unmodified
Added
Removed
  • trunk/plugins/MootoolsFileManager/MootoolsFileManager.ImageManager.js

    r1327 r1371  
    8888                      {                                                  
    8989                        this.info.adopt(self.ImageManagerAttributes(details));  
     90                         
     91                        // This is some meta data we don't need to see, messes up the formatting. 
     92                        if(this.info.getElement('.filemanager-preview dl+p')) 
     93                        { 
     94                          this.info.getElement('.filemanager-preview dl+p').style.display = 'none'; 
     95                        } 
    9096                        return true; 
    9197                      }, 
     
    220226          var td    = tr.appendChild(document.createElement('td')); 
    221227          td.rowSpan = 2; 
     228          td.style.verticalAlign = 'middle'; 
    222229           
    223230          var div   = td.appendChild(document.createElement('div')); 
     
    225232           
    226233          var img   = div.appendChild(document.createElement('img')); 
    227           img.src   =  Xinha.getPluginDir("ImageManager") + '/img/locked.gif'; 
     234          img.src   =  Xinha.getPluginDir("MootoolsFileManager") + '/img/locked.gif'; 
    228235          img.width = 25; 
    229236          img.height = 32; 
     
    235242          input.name = 'f_constrain';                   
    236243          input.style.position = 'absolute'; 
    237           input.style.top = '8px'; 
    238           input.style.left = '0px'; 
     244          input.style.top = '10px'; 
     245          input.style.left = '-4px'; 
    239246          input.value = 'on';         
    240247          input.checked = true; 
     
    377384          th.className = td.className = 'filemanager-f_borderColor';     
    378385        } 
     386      } 
     387       
     388      { // Size Warning 
     389         var tr    = tbody.appendChild(document.createElement('tr')); 
     390                 
     391         var td    = tr.appendChild(document.createElement('td')); 
     392         td.colSpan = 7; 
     393         td.className = 'filemanager-img-size-notice';         
    379394      } 
    380395       
     
    475490  } 
    476491   
     492  if(details.width && !isNaN(parseInt(details.width)) && parseInt(details.width) > 400) 
     493  { 
     494    this._ImageManagerAttributesTable.getElement('.filemanager-img-size-notice').set('html', '<p class="tip" style="margin-top:4px;">This original image is ' + (details.width) + ' pixels wide, you may wish to reduce the size by putting a lesser number in the "Width" field above.  It is not usually desirable to exceed 400 pixels in width on most normal pages.</p>'); 
     495  } 
     496  else 
     497  { 
     498      this._ImageManagerAttributesTable.getElement('.filemanager-img-size-notice').set('text', ''); 
     499  } 
     500   
    477501  // If details were supplied, we set the appropriate ones.   
    478502  if( 
  • trunk/plugins/MootoolsFileManager/MootoolsFileManager.js

    r1327 r1371  
    2222MootoolsFileManager._pluginInfo = { 
    2323  name          : "Mootols File Manager", 
    24   version       : "1.0", 
     24  version       : "1.5", 
    2525  developer     : "James Sleeman (Xinha), Christoph Pojer (FileManager)",   
    2626  license       : "MIT" 
     
    4141{ 
    4242  MootoolsFileManager.AssetLoader 
    43     .loadScript('mootools-core.js', 'MootoolsFileManager') 
    44     .loadScript('mootools-more.js', 'MootoolsFileManager'); 
     43    .loadScript('MooTools-Core-1.6.0.js', 'MootoolsFileManager') 
     44    .loadScript('MooTools-More-1.6.0.js', 'MootoolsFileManager'); 
    4545} 
    4646 
     
    5656  if(typeof __MFM_USE_FLASH__ == 'undefined') 
    5757  { 
    58     __MFM_USE_FLASH__ = Browser.Plugins.Flash.version ? true : false; 
     58    // Flash is now disabled by default, there is no good reason to use it 
     59    // as the NoFlash uploader works exactly the same on modern browsers 
     60    // (allows multiple file uploads, progress bars and so forth) 
     61    __MFM_USE_FLASH__ = false; 
     62     
     63    /* 
     64      __MFM_USE_FLASH__ = Browser.Plugins.Flash.version ? true : false; 
     65      if(Browser.Platform.mac && Browser.firefox3) __MFM_USE_FLASH__ = false; 
     66    */ 
    5967  } 
    6068   
     
    9098 { 
    9199   MootoolsFileManager.AssetLoader 
    92       .loadScript('mootools-filemanager/Source/Uploader/Fx.ProgressBar.js', 'MootoolsFileManager') 
    93100      .loadScript('mootools-filemanager/Source/NoFlash.Uploader.js', 'MootoolsFileManager'); 
    94101 }     
     
    140147        id        : "linkfile", 
    141148        tooltip   : Xinha._lc("Insert File Link",'ExtendedFileManager'), 
    142         image     : Xinha.getPluginDir('ExtendedFileManager') + '/img/ed_linkfile.gif', 
     149        image     : Xinha.getPluginDir('MootoolsFileManager') + '/img/ed_linkfile.gif', 
    143150        textMode  : false, 
    144151        action    : function(editor) { MootoolsFileManager.AssetLoader.whenReady(function() { self.OpenFileManager(); }); } 
  • trunk/plugins/MootoolsFileManager/backend.php

    r1327 r1371  
    5353} 
    5454 
     55if(isset($_POST['encoded_data'])) 
     56{ 
     57  $D = json_decode(str_rot13($_POST['encoded_data'])); 
     58  foreach($D as $k => $v) { $_POST[$k]=$v; if(!isset($_REQUEST[$k])) $_REQUEST[$k] = $v; } 
     59} 
     60 
    5561function size_to_bytes($s) 
    5662{ 
     
    102108    { 
    103109      // Session time out 
    104       echo '{"status" : 0, "error": "No images_dir, most likely your session has timed out."}';exit; 
     110      echo '{"status" : 0, "error": "No images_dir, most likely your session has timed out, or the file upload was too large for this server to handle, try refreshing your browser, or uploading a smaller file if it still does not work.", "data": ' . json_encode($_REQUEST).'}';exit; 
    105111    } 
    106112     
  • trunk/plugins/MootoolsFileManager/config.php

    r1327 r1371  
    120120  $IMConfig['images_allow_delete']     = false; 
    121121  $IMConfig['images_max_upload_size']  = '3M'; 
    122   $IMConfig['images_suggested_image_dimension']  = array('width' => 1024, 'height' => 768); 
     122  $IMConfig['images_suggested_image_dimension']  = $IMConfig['files_suggested_image_dimension']; 
    123123 
    124124 
  • trunk/plugins/MootoolsFileManager/mootools-filemanager/Assets/Connector/FileManager.php

    r1327 r1371  
    10721072                if (empty($document_root_fspath)) 
    10731073                { 
    1074                         $document_root_fspath = realpath($_SERVER['DOCUMENT_ROOT']); 
     1074                        $document_root_fspath = realpath(isset($_SERVER['REDIRECT_DOCUMENT_ROOT']) ? $_SERVER['REDIRECT_DOCUMENT_ROOT'] : $_SERVER['DOCUMENT_ROOT']); 
    10751075                } 
    10761076                $document_root_fspath = strtr($document_root_fspath, '\\', '/'); 
     
    23452345                                else 
    23462346                                { 
     2347          if ($this->options['safe']) 
     2348          { 
     2349            $safeext      = $this->getSafeExtension(preg_replace('/^.*\./', '', $file_arg));            
     2350            $file_arg = preg_replace('/\..*$/', '', $file_arg) . ".$safeext"; 
     2351          } 
     2352           
    23472353                                        $filename = $this->getUniqueName($file_arg, $dir); 
    23482354                                        if ($filename !== null) 
     
    41984204                case 'php4': 
    41994205                case 'php5': 
     4206                case 'php6': 
     4207                case 'php7': 
     4208                case 'php8': 
    42004209                case 'phps': 
    42014210                        return (!empty($safe_extension) ? $safe_extension : $extension); 
     
    42814290                 * an option array to pagetitle(), particularly for large directories. 
    42824291                 */ 
    4283                 $filename = FileManagerUtility::pagetitle($fileinfo['filename'], null, '-_., []()~!@+' /* . '#&' */, '-_,~@+#&'); 
     4292                $filename = FileManagerUtility::pagetitle($fileinfo['filename'], null, '-_.,[]()~!@+' /* . '#&' */, '-_,~@+#&'); 
    42844293                if (!$filename && !$dotfile) 
    42854294                        return null; 
  • trunk/plugins/MootoolsFileManager/mootools-filemanager/Assets/Connector/Image.class.php

    r1321 r1371  
    595595         * @return Image object 
    596596         */ 
    597         public function process($ext = null, $file = null, $quality = 100, $store_original_if_unaltered = false){ 
     597        public function process($ext = null, $file = null, $quality = 60, $store_original_if_unaltered = true){ 
    598598                if(empty($this->image)) return $this; 
    599599 
     
    653653         * @return Image 
    654654         */ 
    655         public function save($file = null, $quality = 100, $store_original_if_unaltered = false){ 
     655        public function save($file = null, $quality = 60, $store_original_if_unaltered = true){ 
    656656                if(empty($this->image)) return $this; 
    657657 
  • trunk/plugins/MootoolsFileManager/mootools-filemanager/Assets/js/milkbox/milkbox.js

    r1321 r1371  
    3636                onXmlGalleries:function(){}, 
    3737                onClosed:function(){}, 
    38                 onFileReady:function(){} 
     38                onFileReady:function(){}, 
     39    zIndex: 50001, 
    3940        }, 
    4041 
     
    476477                        autoSize:this.options.autoSize, 
    477478                        autoSizeMaxHeight:this.options.autoSizeMaxHeight, 
    478                         imageOfText:this.options.imageOfText 
     479                        imageOfText:this.options.imageOfText, 
     480      zIndex:this.options.zIndex 
    479481                }); 
    480482        }, 
     
    713715                                'overflow':'hidden', 
    714716                                'display':'none', 
    715                                 'z-index':50001,//overlay z-index (see css) + 1 
     717                                'z-index':this.options.zIndex,//50001,//overlay z-index (see css) + 1 
    716718                                'width':this.options.initialWidth, 
    717719                                'height':this.options.initialHeight, 
     
    11741176 
    11751177//Creating Milkbox instance: you can comment this code and instantiate Milkbox somewhere else instead. 
    1176 window.addEvent('domready', function(){  
    1177         //milkbox = new Milkbox({overlayOpacity:1, fileboxBorderWidth:'10px', fileboxBorderColor:'#ff0000', fileboxPadding:'20px', centered:true}); 
    1178         milkbox = new Milkbox({  
    1179                 //autoPlay:true 
    1180         }); 
    1181 }); 
     1178if( typeof __MILKBOX_NO_AUTOINIT__ == 'undefined' || __MILKBOX_NO_AUTOINIT__ == false) 
     1179{ 
     1180  window.addEvent('domready', function(){  
     1181    //milkbox = new Milkbox({overlayOpacity:1, fileboxBorderWidth:'10px', fileboxBorderColor:'#ff0000', fileboxPadding:'20px', centered:true}); 
     1182    milkbox = new Milkbox({  
     1183      //autoPlay:true 
     1184    }); 
     1185  }); 
     1186} 
  • trunk/plugins/MootoolsFileManager/mootools-filemanager/Source/FileManager.TinyMCE.js

    r1321 r1371  
    5252                if (src.length > 0) 
    5353                { 
    54                         src = this.documentBaseURI.toAbsolute(src); 
     54                        //src = this.documentBaseURI.toAbsolute(src); 
    5555                } 
    5656                if (src.match(/^[a-z]+:/i)) 
  • trunk/plugins/MootoolsFileManager/mootools-filemanager/Source/FileManager.js

    r1327 r1371  
    744744        mkServerRequestURL: function(fm_obj, request_code, post_data) 
    745745        { 
     746    // HACK: Encode the post_data to get around mod_security issues 
     747    function rot13(s) 
     748    { 
     749        return (s ? s : this).split('').map(function(_) 
     750        { 
     751            if (!_.match(/[A-Za-z]/)) return _; 
     752            c = Math.floor(_.charCodeAt(0) / 97); 
     753            k = (_.toLowerCase().charCodeAt(0) - 83) % 26 || 26; 
     754            return String.fromCharCode(k + ((c == 0) ? 64 : 96)); 
     755        }).join(''); 
     756    } 
     757   // console.log(rot13(JSON.encode(post_data))); 
     758     
     759    post_data = { encoded_data: rot13(JSON.encode(post_data)) }; 
     760     
    746761                return { 
    747                         url: fm_obj.options.url + (fm_obj.options.url.indexOf('?') == -1 ? '?' : '&') + Object.toQueryString({ 
     762                        url: (fm_obj.options.url + (fm_obj.options.url.indexOf('?') == -1 ? '?' : '&') + Object.toQueryString({ 
    748763                                        event: request_code 
    749                                 }), 
     764                                })).replace(/&&/, '&'), 
    750765                        data: post_data 
    751766                }; 
     
    36433658 
    36443659 
    3645 if( __MFM_USE_BACK_BUTTON_NAVIGATION__ ) 
     3660if( typeof __MFM_USE_BACK_BUTTON_NAVIGATION__ != 'undefined' && __MFM_USE_BACK_BUTTON_NAVIGATION__ ) 
    36463661{ 
    36473662  Asset.javascript(__MFM_ASSETS_DIR__+'/js/jsGET.js', { 
  • trunk/plugins/MootoolsFileManager/mootools-filemanager/Source/NoFlash.Uploader.js

    r1321 r1371  
    1 /* 
    2 --- 
    3  
    4 description: Implements Upload functionality into the FileManager without using Flash 
    5  
    6 authors: James Sleeman (@sleemanj) 
    7  
    8 license: MIT-style license. 
    9  
    10 requires: [Core/*] 
    11  
    12 provides: Filemanager.NoFlashUploader 
    13  
    14 ... 
     1/** 
     2Uploader Implementation Not Requiring Flash 
     3=============================================================================== 
     4 
     5This class implements Upload functionality into the FileManager using  
     6either HTML5 Uploads via XMLHTTPRequest if the browser supports that,  
     7or a hidden file input and submitting to an iframe otherwise. 
     8 
     9HTML5 Uploads can handle uploading multiple files with progress indications. 
     10 
     11Fallback uploads present a standard file field to facilitate picking a single 
     12file and an upload button to upload it (posts to a hidden iframe). 
     13 
     14Tested In 
     15---------- 
     16 
     17Working Fully With HTML5 Multiple Uploads 
     18 
     19  * Linux Chrome 65.0.3325.18 
     20  * Linux Firefox 58 (see note for Firefox users below) 
     21  * Linux Opera 50 
     22  * Linux Chromium 63.0.3239.132 
     23   
     24  * Windows IE 11 
     25   
     26  * Mac OSX Safari 9.1.1 (11601.6.17) 
     27 
     28Working Fallback Single Upload At A Time 
     29 
     30  * Windows IE 8 - Falls back to iframe upload 
     31  * Windows IE 9 - Falls back to iframe upload 
     32 
     33 
     34FIREFOX USERS NOTICE 
     35------------------------------------------------------------------------------- 
     36 
     37The Firefox file selection dialog can be HORRIBLY broken, unusable, this is not  
     38a bug in MFM, it's firefox.  If you start firefox with the MOZ_USE_XINPUT2=1  
     39environment variable, it fixes it! 
     40 
     41@author James Sleeman <james@gogo.co.nz> 
     42@license MIT-style License 
     43 
    1544*/ 
    1645 
    17 /* 
    18  * While the flash uploader is preferable, sometimes it is not possible to use it due to 
    19  * server restrictions (eg, mod_security), or perhaps users refuse to use flash. 
    20  * 
    21  * This Upload handler will allow the MTFM to continue to function, without multiple-upload-at-once 
    22  * function and without progress bars.  But otherwise, it should work. 
    23  */ 
    2446FileManager.implement({ 
    2547 
     
    5274        }, 
    5375 
    54         // Writing to file input values is not permitted, we replace the field to blank it. 
     76  /** Create the file input field and inject it into the given form element 
     77   */ 
     78   
    5579        make_file_input: function(form_el) 
    5680        { 
     
    5882                        type: 'file', 
    5983                        name: 'Filedata', 
    60                         id: 'filemanager_upload_Filedata' 
     84                        id: 'filemanager_upload_Filedata', 
     85      multiple: 'multiple' 
    6186                }); 
     87     
     88    // fileinput.style.visibility = 'hidden'; 
     89     
    6290                if (form_el.getElement('input[type=file]')) 
    6391                { 
     
    7199        }, 
    72100 
     101  /** Cleanup after ourselves when the filemanager window is closed 
     102   *  
     103   *  @TODO This may not be entirely necessary now, leaving it anyway. 
     104   */ 
     105   
    73106        hideUpload: function() 
    74107        { 
     
    103136                        this.upload.resizer = null; 
    104137                } 
    105  
    106                 // discard old iframe, if it exists: 
    107                 if (this.upload.dummyframe) 
    108                 { 
    109                         // remove from the menu (dispose) and trash it (destroy) 
    110                         this.upload.dummyframe.dispose().destroy(); 
    111                         this.upload.dummyframe = null; 
    112                 } 
     138                 
     139    // discard old iframe, if it exists: 
     140    if (this.upload.dummyframe) 
     141    { 
     142      // remove from the menu (dispose) and trash it (destroy) 
     143      this.upload.dummyframe.dispose().destroy(); 
     144      this.upload.dummyframe = null; 
     145    } 
    113146        }, 
    114147 
     148  /** Setup out upload interface. 
     149   *  
     150   *  Creates the upload button, the container for the form field, calls out to create the form field 
     151   *  the area for the upload list, and the resizing checkbox. 
     152   * 
     153   */ 
     154   
    115155        startUpload: function() 
    116156        { 
     
    124164                        inputs: {}, 
    125165                        resizer: null, 
    126                         dummyframe: null, 
    127                         dummyframe_active: false,     // prevent premature firing of the load event (hello, MSIE!) to cause us serious trouble in there 
    128  
     166      dummyframe: null, 
     167      dummyframe_active: false,     // prevent premature firing of the load event (hello, MSIE!) to cause us serious trouble in there 
     168      can_support_xhr:   typeof ((new Element('input')).set({ 
     169        type: 'file', 
     170        name: 'Filedata', 
     171        multiple: 'multiple' 
     172      })).files == 'undefined' ? false : true, 
     173       
    129174                        form: (new Element('form')) 
    130                                 //.set('action', tx_cfg.url) 
     175                                // .set('action', tx_cfg.url) 
    131176                                .set('method', 'post') 
    132177                                .set('enctype', 'multipart/form-data') 
    133                                 .set('target', 'dummyframe') 
     178        .set('target', 'dummyframe') 
    134179                                .setStyles({ 
    135180                                        'float': 'left', 
     
    138183                        }), 
    139184 
    140                         uploadButton: this.addMenuButton('upload').inject(this.menu, 'bottom').addEvents({ 
    141                                 click:  function(e) { 
    142                                         e.stop(); 
    143                                         self.browserLoader.fade(1); 
    144                                         self.upload.form.action = tx_cfg.url; 
    145  
    146                                         // Update curent dir path to form hidden field 
    147                                         self.upload.inputs['directory'].setProperty('value', self.CurrentDir.path); 
    148  
    149                                         self.upload.dummyframe_active = true; // NOW you may fire when ready... 
    150  
    151                                         self.upload.form.submit(); 
    152                                 }, 
     185                        uploadButton: this.addMenuButton('upload').inject(this.menu, 'bottom').addEvents({                               
    153186                                mouseenter: function() { 
    154187                                        this.addClass('hover'); 
     
    163196                        }), 
    164197 
    165                         lastFileUploaded: null,  // name of the last successfully uploaded file; will be preselected in the list view 
    166                         error_count: 0 
     198      list: new Element('ul', {'class': 'filemanager-uploader-list'}), 
     199      uploader: new Element('div', {opacity: 0, 'class': 'filemanager-uploader-area'}).adopt( 
     200        new Element('h2', {text: this.language.upload}), 
     201        new Element('div', {'class': 'filemanager-uploader'}) 
     202      ) 
    167203                }; 
    168  
    169                 var tx_cfg = this.options.mkServerRequestURL(this, 'upload', Object.merge({}, 
    170                                                 this.options.propagateData, 
    171                                                 (this.options.uploadAuthData || {}), { 
    172                                                         directory: (this.CurrentDir ? this.CurrentDir.path : null), 
    173                                                         filter: this.options.filter, 
    174                                                         resize: this.options.resizeImages, 
    175                                                         reportContentType: 'text/plain'        // Safer for iframes: the default 'application/json' mime type would cause FF3.X to pop up a save/view dialog! 
    176                                                 })); 
    177  
    178                 // Create hidden input for each form data 
    179                 Object.each(tx_cfg.data, function(v, k){ 
    180                         var input = new Element('input').set({type: 'hidden', name: k, value: v, id: 'filemanager_upload_' + k }); 
    181                         self.upload.form.adopt(input); 
    182                         self.upload.inputs[k] = input; 
    183                 }); 
    184  
     204    this.upload.uploader.getElement('div').adopt(this.upload.list); 
     205 
     206    if(this.upload.can_support_xhr) 
     207    { 
     208      this.upload.form.setStyle('visibility', 'hidden'); 
     209      this.upload.uploadButton.addEvent('click',  function(e) { 
     210          e.stop(); 
     211 
     212          self.upload.form.getElement('input[type=file]').removeEvents('change'); 
     213          self.upload.form.getElement('input[type=file]').addEvent('change', self.doUpload.bind(self)); 
     214           
     215          self.upload.form.getElement('input[type=file]').click(); 
     216           
     217      });  
     218    } 
     219    else 
     220    { 
     221      this.upload.uploadButton.addEvent('click',  function(e) { 
     222          e.stop() 
     223          self.doUploadFallback(); 
     224      });  
     225    } 
     226     
    185227                if (this.options.resizeImages) 
    186228                { 
     
    189231                        { 
    190232                                this.toggleClass('checkboxChecked'); 
    191  
    192                                 // Update the resize hidden field 
    193                                 self.upload.inputs['resize'].setProperty('value', (this.hasClass('checkboxChecked')) ? 1 : 0); 
    194233                        }).bind(this.upload.resizer); 
    195234                        check(); 
     
    205244                //this.menu.setStyle('height', '60px'); 
    206245 
    207                 // discard old iframe, if it exists: 
    208                 if (this.upload.dummyframe) 
    209                 { 
    210                         // remove from the menu (dispose) and trash it (destroy) 
    211                         this.upload.dummyframe.dispose().destroy(); 
    212                         this.upload.dummyframe = null; 
    213                 } 
    214  
    215                 this.upload.dummyframe = (new IFrame).set({src: 'about:blank', name: 'dummyframe'}).setStyles({display: 'none'}); 
    216                 this.menu.adopt(this.upload.dummyframe); 
    217  
    218                 this.upload.dummyframe.addEvent('load', function() 
    219                 { 
    220                         var iframe = this; 
    221                         self.diag.log('NoFlash upload response: ', this, ', iframe: ', self.upload.dummyframe, ', ready:', (1 * self.upload.dummyframe_active)); 
    222  
    223                         // make sure we don't act on premature firing of the event in MSIE browsers: 
    224                         if (!self.upload.dummyframe_active) 
    225                                 return; 
    226  
    227                         self.browserLoader.fade(0); 
    228  
    229                         var response = null; 
    230                         Function.attempt(function() { 
    231                                         response = iframe.contentDocument.documentElement.textContent; 
    232                                 }, 
    233                                 function() { 
    234                                         response = iframe.contentWindow.document.innerText; 
    235                                 }, 
    236                                 function() { 
    237                                         response = iframe.contentDocument.innerText; 
    238                                 }, 
    239                                 function() { 
    240                                         // Maybe this.contentDocument.documentElement.innerText isn't where we need to look? 
    241                                         //debugger; 
    242                                         response = "{status: 0, error: \"noFlashUpload: document innerText grab FAIL: Can't find response.\"}"; 
    243                                 } 
    244                         ); 
    245  
    246                         var j = JSON.decode(response); 
    247  
    248                         if (j && !j.status) 
    249                         { 
    250                                 self.showError('' + j.error); 
    251                                 self.load(self.CurrentDir.path); 
    252                         } 
    253                         else if (j) 
    254                         { 
    255                                 self.load(self.CurrentDir.path, j.name); 
    256                         } 
    257                         else 
    258                         { 
    259                                 // IE9 fires the load event on init! :-( 
    260                                 if (self.CurrentDir) 
    261                                 { 
    262                                         self.showError('bugger! No or faulty JSON response! ' + response); 
    263                                         self.load(self.CurrentDir.path); 
    264                                 } 
    265                         } 
    266  
    267                         self.make_file_input(self.upload.form); 
    268                 }); 
    269         } 
     246  }, 
     247 
     248   
     249  /** Change handler for the form field, actually do the uploads. 
     250   *  
     251   *  Note that if you don't select a different file in the form field, no change, so no re-upload 
     252   *  unless you actually pick a different file (as well or instead). 
     253   *  
     254   */ 
     255   
     256  doUpload: function() 
     257  { 
     258    if(this.upload.form.getElement('input[type=file]').files.length == 0) return; 
     259 
     260    // Notice here that propagateData is not passed into mkServerRequestURL 
     261    //   this is how the rest of the system works too so it can't really be changed 
     262    //   because mkServerRequestURL is passed into FileManager.Request normally 
     263    //   and the propagateData is added there. 
     264                       
     265    var tx_cfg = this.options.mkServerRequestURL(this, 'upload', Object.merge({}, 
     266        /*this.options.propagateData, */ 
     267        (this.options.uploadAuthData || {}), { 
     268          directory: (this.CurrentDir ? this.CurrentDir.path : '/'), 
     269          filter: this.options.filter, 
     270          resize: this.options.resizeImages ? this.upload.resizer.hasClass('checkboxChecked') : false, 
     271          reportContentType: 'text/plain' 
     272        })); 
     273     
     274    var files = this.upload.form.getElement('input[type=file]').files; 
     275    var fieldName = this.upload.form.getElement('input[type=file]').name; 
     276    var i     = 0; 
     277    var self = this; 
     278     
     279    // Construct the entries in the upload list for each file 
     280    var fileUIs = [ ]; 
     281    for(var i = 0; i < files.length; i++) 
     282    { 
     283      fileUIs[i] = new FileManager.UploadListEntry(files[i], self); 
     284             
     285      // @TODO Validate client side here 
     286      // fileUIs[i].invalidate("Testing"); 
     287       
     288    } 
     289     
     290    i = 0; 
     291     
     292    // Show the upload list of files 
     293    this.show_our_info_sections(false); 
     294    this.info.adopt(this.upload.uploader.setStyle('display', 'block')); 
     295    this.upload.uploader.fade(1); 
     296     
     297    // Display spinner 
     298    self.browserLoader.fade(1); 
     299     
     300    // When the list becomes empty (all files uploaded) remove it and update 
     301    //  the selected file to the first valid one, that is, the first one that 
     302    //  uploaded OK. 
     303    var hideList = function() 
     304    { 
     305      if(self.upload.uploader.getElements('li').length) 
     306      { 
     307        hideList.delay(1000); 
     308      } 
     309      else 
     310      { 
     311        self.upload.uploader.fade(0).get('tween').chain(function() { 
     312          self.upload.uploader.setStyle('display', 'none'); 
     313          self.show_our_info_sections(true); 
     314             
     315          // Hide spinner 
     316          self.browserLoader.fade(0); 
     317           
     318          // Update 
     319          for(var x = 0; x < fileUIs.length; x++) 
     320          { 
     321            if(fileUIs[x].valid) 
     322            { 
     323              self.load(self.CurrentDir.path, fileUIs[x].nameOnServer); 
     324            } 
     325          } 
     326        });             
     327      } 
     328    }; 
     329    hideList(); 
     330       
     331     
     332    // This is where the actual upload of the files happens: 
     333    //  take file i, 
     334    //    if it's not valid, skip it 
     335    //    create a new request object 
     336    //       append the file to it 
     337    //       append tx_cfg.data (above) and the propagateData to it 
     338    //       attach a progress event to it which calls the file's UI and tells it to update 
     339    //       attach success/fail events to it which calls the file's UI and tells it to update 
     340    //    start the upload 
     341    //       wait asynchronously until it's done 
     342    //       tell the file's UI that it's done and the result 
     343    //         (the file's UI will remove itself from the list after a short delay) 
     344    //    next i 
     345 
     346     
     347    var doUploadNextFile = function() 
     348    { 
     349      if(i <= files.length-1) 
     350      { 
     351        var file   = files[i];       
     352        var fileUI = fileUIs[i]; 
     353         
     354        if(!fileUI.valid) 
     355        { 
     356          i++; 
     357          return doUploadNextFile(); 
     358        } 
     359         
     360        // For testing progess bar 
     361        if(0) 
     362        { 
     363          var p = 1; 
     364          (function(){ 
     365            if(p<100) 
     366              fileUI.progress(p++); 
     367          }).periodical(500); 
     368          return; 
     369        } 
     370         
     371        var upload = new FileManager.FileUploadRequest({ 
     372          url: tx_cfg.url 
     373        }); 
     374         
     375        upload.append(fieldName, file); 
     376        Object.each(Object.merge(tx_cfg.data, self.options.propagateData), function(v,k) { upload.append(k,v); }); 
     377         
     378        upload.addEvent('progress', function(event, a){           
     379          if(event.total && event.loaded) 
     380          { 
     381            fileUI.progress((event.loaded / event.total) * 100); 
     382          } 
     383        }); 
     384         
     385        upload.addEvent('success', function(responseText){ 
     386          fileUI.complete({'text': responseText}); 
     387        }); 
     388         
     389        upload.addEvent('failure', function(responseXhr){ 
     390          fileUI.complete(responseXhr); 
     391        }); 
     392         
     393        upload.send(); 
     394         
     395        (function waitTillDone(){ 
     396          if(!upload.isRunning()) 
     397          { 
     398            i++; 
     399            doUploadNextFile(); 
     400             
     401            fileUI.progress(100); 
     402            fileUI.complete(upload); 
     403          } 
     404          else 
     405          { 
     406            waitTillDone.delay(500); 
     407          } 
     408        }).delay(500); 
     409      } 
     410    } 
     411     
     412    doUploadNextFile(); 
     413  }, 
     414   
     415  /** Change handler for the form field that does not require HTML5, or much more than form fields. 
     416   *  
     417   *  Uses the hidden iframe. 
     418   *  
     419   *  Note that if you don't select a different file in the form field, no change, so no re-upload 
     420   *  unless you actually pick a different file (as well or instead). 
     421   *  
     422   */ 
     423   
     424  doUploadFallback: function() 
     425  { 
     426    var self = this; 
     427     
     428    // discard old iframe, if it exists: 
     429    if (this.upload.dummyframe) 
     430    { 
     431      // remove from the menu (dispose) and trash it (destroy) 
     432      this.upload.dummyframe.dispose().destroy(); 
     433      this.upload.dummyframe = null; 
     434    } 
     435 
     436    this.upload.dummyframe = (new IFrame).set({src: 'about:blank', name: 'dummyframe'}).setStyles({display: 'none'}); 
     437    this.menu.adopt(this.upload.dummyframe); 
     438     
     439    this.upload.dummyframe.addEvent('load', function() 
     440    { 
     441      var iframe = this; 
     442      self.diag.log('NoFlash upload response: ', this, ', iframe: ', self.upload.dummyframe, ', ready:', (1 * self.upload.dummyframe_active)); 
     443 
     444      // make sure we don't act on premature firing of the event in MSIE browsers: 
     445      if (!self.upload.dummyframe_active) 
     446        return; 
     447 
     448      self.browserLoader.fade(0); 
     449 
     450      var response = null; 
     451      Function.attempt(function() { 
     452          response = iframe.contentDocument.documentElement.innerText; 
     453        }, 
     454        function() { 
     455          response = iframe.contentDocument.documentElement.textContent; 
     456        }, 
     457        function() { 
     458          response = iframe.contentWindow.document.innerText; 
     459        }, 
     460        function() { 
     461          response = iframe.contentDocument.innerText; 
     462        }, 
     463        function() { 
     464          // Maybe this.contentDocument.documentElement.innerText isn't where we need to look? 
     465          //debugger; 
     466          response = "{status: 0, error: \"noFlashUpload: document innerText grab FAIL: Can't find response.\"}"; 
     467        } 
     468      ); 
     469 
     470      var j = JSON.decode(response); 
     471 
     472      if (j && !j.status) 
     473      { 
     474        self.showError('' + j.error); 
     475        self.load(self.CurrentDir.path); 
     476      } 
     477      else if (j) 
     478      { 
     479        self.load(self.CurrentDir.path, j.name); 
     480      } 
     481      else 
     482      { 
     483        // IE9 fires the load event on init! :-( 
     484        if (self.CurrentDir) 
     485        { 
     486          self.showError('No or faulty JSON response! ' + response); 
     487          self.load(self.CurrentDir.path); 
     488        } 
     489      } 
     490 
     491      // Clear the file input, to do this it is remade 
     492      self.make_file_input(self.upload.form); 
     493    }); 
     494         
     495    // Notice here that propagateData is not passed into mkServerRequestURL 
     496    //   this is how the rest of the system works too so it can't really be changed 
     497    //   because mkServerRequestURL is passed into FileManager.Request normally 
     498    //   and the propagateData is added there. 
     499                       
     500    var tx_cfg = this.options.mkServerRequestURL(this, 'upload', Object.merge({}, 
     501        /*this.options.propagateData, */ 
     502        (this.options.uploadAuthData || {}), { 
     503          directory: (this.CurrentDir ? this.CurrentDir.path : '/'), 
     504          filter: this.options.filter, 
     505          resize: this.options.resizeImages ? this.upload.resizer.hasClass('checkboxChecked') : false, 
     506          reportContentType: 'text/plain' 
     507        })); 
     508     
     509    self.upload.form.action = tx_cfg.url; 
     510    self.upload.form.getElements('input[type=hidden]').each(function(e){e.destroy();}); 
     511     
     512    Object.each(Object.merge(tx_cfg.data, self.options.propagateData), function(v,k) {  
     513      var input = new Element('input').set({type: 'hidden', name: k, value: v, id: 'filemanager_upload_' + k }); 
     514      self.upload.form.adopt(input);     
     515    }); 
     516     
     517    self.upload.dummyframe_active = true; 
     518    self.browserLoader.fade(0); 
     519    self.upload.form.submit(); 
     520     
     521  } 
     522   
     523   
    270524}); 
    271525 
     526 
     527/** The UploadListEntry class handles entries in the file upload list 
     528 *  
     529 *  Pass it an HTML5 file object (taken from input[type=file].files)  
     530 *  and the file manager to which it is being attached. 
     531 *  
     532 *  During initialisation the UploadListEntry will inject an <li> into  
     533 *    [filemanager].upload.list 
     534 *  which must already be created. 
     535 *  
     536 * You can then call  
     537 *   invalidate("Reason") to invalidate the file and produce a message 
     538 *   invalidate(false)    to invalidate the file and not produce a message 
     539 *   progress(0 .. 100) to set the progress bar for the file 
     540 *   complete({text: 'jsonencodedresponse'}) to complete the file with a json response 
     541 *   complete(XMLHTTPRequest) to fail the file with some non-json failure 
     542 */ 
     543 
     544FileManager.UploadListEntry = new Class({ 
     545 
     546  Implements: Events, 
     547   
     548  initialize: function(file, fm)  
     549  { 
     550    this.file = file; 
     551    this.base = fm;     
     552     
     553    this.valid = true; 
     554    this.validationError = null; 
     555    this.has_completed  = false; 
     556     
     557    this.id =  String.uniqueID(); 
     558     
     559    this.addEvents({ 
     560      start: this.onStart, 
     561      progress: this.onProgress, 
     562      stop: this.onStop, 
     563      complete: this.onComplete 
     564    }); 
     565     
     566    this.render(); 
     567  }, 
     568 
     569  /** Mark the upload as invalid/failed, display a message, highlight and remove the file. 
     570   *  
     571   *  With no reason, no message is displayed, with a reason a message is displayed.  
     572   *  
     573   *  @param reason String|false 
     574   */ 
     575   
     576  invalidate: function(reason)  
     577  { 
     578    this.valid = false; 
     579    this.validationError = reason; 
     580     
     581    if(reason) 
     582    { 
     583      var message = this.base.language.uploader.unknown; 
     584      var sub = { 
     585        name: this.file.name, 
     586        size: this.formatUnit(this.file.size, 'b') 
     587      }; 
     588 
     589      if (this.base.language.uploader[this.validationError]) { 
     590        message = this.base.language.uploader[this.validationError]; 
     591      } 
     592      else 
     593      { 
     594        message = this.validationError; 
     595      } 
     596 
     597      if (this.validationError === 'sizeLimitMin') 
     598        sub.size_min = this.formatUnit(this.base.options.fileSizeMin, 'b'); 
     599      else if (this.validationError === 'sizeLimitMax') 
     600        sub.size_max = this.formatUnit(this.base.options.fileSizeMax, 'b'); 
     601 
     602      this.base.showError(message.substitute(sub, /\\?\$\{([^{}]+)\}/g)); 
     603    } 
     604     
     605    this.highlightAndClear(); 
     606  }, 
     607   
     608  /** Highlight the file in the list and then remove it from the list (ie when it's finished/failed) 
     609   *  
     610   *  Used by invalidate, and complete 
     611   */ 
     612   
     613  highlightAndClear: function() 
     614  { 
     615    var self = this; 
     616     
     617    // Highlight the line 
     618    self.ui.element.set('tween', {duration: 1000}).highlight(null, (self.valid ? '#e6efc2' : '#f0c2c2')).get('tween').chain(function(){self.ui.element.style.backgroundColor=(self.valid ? '#e6efc2' : '#f0c2c2');}); 
     619     
     620    // Remove it after a delay 
     621    (function() { 
     622      self.ui.element.setStyle('overflow', 'hidden').morph({ 
     623        opacity: 0, 
     624        height: 0 
     625      }).get('morph').chain(function() { 
     626        self.ui.element.destroy(); 
     627      }); 
     628    }).delay(self.valid ? 2500 : 5000, self); 
     629  }, 
     630   
     631  /** Format a number into a human readable size  
     632   *  
     633   */ 
     634   
     635  formatUnit: function(base, type, join) { 
     636    var unitLabels =  { 
     637      b: [{min: 1, unit: 'B'}, {min: 1024, unit: 'kB'}, {min: 1048576, unit: 'MB'}, {min: 1073741824, unit: 'GB'}], 
     638      s: [{min: 1, unit: 's'}, {min: 60, unit: 'm'}, {min: 3600, unit: 'h'}, {min: 86400, unit: 'd'}] 
     639    }; 
     640    var labels = unitLabels[(type == 'bps') ? 'b' : type]; 
     641    var append = (type == 'bps') ? '/s' : ''; 
     642    var i, l = labels.length, value; 
     643 
     644    if (base < 1) return '0 ' + labels[0].unit + append; 
     645 
     646    if (type == 's') { 
     647      var units = []; 
     648 
     649      for (i = l - 1; i >= 0; i--) { 
     650        value = Math.floor(base / labels[i].min); 
     651        if (value) { 
     652          units.push(value + ' ' + labels[i].unit); 
     653          base -= value * labels[i].min; 
     654          if (!base) break; 
     655        } 
     656      } 
     657 
     658      return (join === false) ? units : units.join(join || ', '); 
     659    } 
     660 
     661    for (i = l - 1; i >= 0; i--) { 
     662      value = labels[i].min; 
     663      if (base >= value) break; 
     664    } 
     665 
     666    return (base / value).toFixed(1) + ' ' + labels[i].unit + append; 
     667  }, 
     668   
     669  /** Draw the list item  
     670   * 
     671   */ 
     672   
     673  render: function() { 
     674    var self = this; 
     675    if (!this.valid) { 
     676 
     677      return this; 
     678    } 
     679 
     680    this.ui = {}; 
     681    this.ui.icon = new Asset.image(this.base.assetBasePath+'Images/Icons/' + this.file.name.replace(/.*\./, '').toLowerCase() + '.png', { 
     682      'class': 'icon', 
     683      onerror: function() { 
     684        new Asset.image(self.base.assetBasePath + 'Images/Icons/default.png').replaces(this); 
     685      } 
     686    }); 
     687    this.ui.element = new Element('li', {'class': 'file', id: 'file-' + this.id}); 
     688    // keep filename in display box at reasonable length: 
     689    var laname = this.file.name; 
     690    if (laname.length > 36) { 
     691      laname = laname.substr(0, 36) + '...'; 
     692    } 
     693    this.ui.title = new Element('span', {'class': 'file-title', text: laname, title: this.file.name}); 
     694    this.ui.size = new Element('span', {'class': 'file-size', text: this.formatUnit(this.file.size, 'b')}); 
     695 
     696    this.ui.cancel = new Asset.image(this.base.assetBasePath+'Images/cancel.png', {'class': 'file-cancel', title: this.base.language.cancel}).addEvent('click', function() { 
     697      self.invalidate(false); // No reason 
     698      self.base.tips.hide(); 
     699      self.base.tips.detach(this); 
     700    }); 
     701    this.base.tips.attach(this.ui.cancel); 
     702 
     703    var progress = new Element('img', {'class': 'file-progress', src: this.base.assetBasePath+'Images/bar.gif'}); 
     704 
     705 
     706    this.ui.element.adopt( 
     707      this.ui.cancel, 
     708      progress, 
     709      this.ui.icon, 
     710      this.ui.title, 
     711      this.ui.size 
     712    ).inject(this.base.upload.list).highlight(); 
     713 
     714    this.ui.progress = progress; 
     715     
     716    // Initialise the progress position to zero      
     717    this.ui.progress.setStyle('background-position-x', Math.floor(100-((0/100)*20+40))+'%'); 
     718  }, 
     719 
     720  /** Update the progress bar of the list item. 
     721   *  
     722   *  @param integer 0 to 100 percent 
     723   */ 
     724   
     725  progress: function(percentLoaded){ 
     726    if(this.has_completed) return; 
     727     
     728    this.ui.element.addClass('file-running'); 
     729     
     730    // Setting the backhround to between 60% for empty and 40% for full works 
     731    //   so that is a range of 20, an offset of 40, flipped backwards (100-N)     
     732    this.ui.progress.setStyle('background-position-x', (100-((percentLoaded/100)*20+40))+'%'); 
     733  }, 
     734   
     735  /** Mark the file as completed, and then remove from the list. 
     736   *  
     737   *  @param object {text: 'jsonencodedresponse'} 
     738   */ 
     739   
     740  complete: function(response) 
     741  { 
     742    var self = this; 
     743     
     744    if(this.has_completed) return; 
     745                                
     746    this.response = response; 
     747     
     748    var jsonresponse = null; 
     749 
     750    this.has_completed = true; 
     751    this.ui.cancel = this.ui.cancel.destroy(); 
     752 
     753    try 
     754    { 
     755      jsonresponse = JSON.decode(response.text); 
     756    } 
     757    catch(e) 
     758    { 
     759      this.base.diag.log(response); 
     760    } 
     761 
     762    if (typeof jsonresponse === 'undefined' || jsonresponse == null) 
     763    { 
     764      if (response == null || !response.text) 
     765      { 
     766        // The 'mod_security' has shown to be one of the most unhelpful error messages ever; particularly when it happened on a lot on boxes which had a guaranteed utter lack of mod_security and friends. 
     767        // So we restrict this report to the highly improbable case where we get to receive /nothing/ /at/ /all/. 
     768        this.invalidate(this.base.language.uploader.mod_security); 
     769      } 
     770      else 
     771      { 
     772        this.invalidate(("Server response:\n" + this.response.text).substitute(this.base.language, /\\?\$\{([^{}]+)\}/g)); 
     773      } 
     774    } 
     775    else if (!jsonresponse.status) 
     776    { 
     777      this.invalidate(('' + jsonresponse.error).substitute(this.base.language, /\\?\$\{([^{}]+)\}/g)); 
     778    } 
     779    else 
     780    { 
     781      this.valid = true; 
     782      this.nameOnServer = jsonresponse.name; 
     783    } 
     784 
     785    this.highlightAndClear(); 
     786  } 
     787 
     788}); 
     789 
     790 
     791/** This class is used to handle the file uploads themselves (XMLHTTPRequest) 
     792 *  
     793 * It does extend Request, but you should't expect everything to work, it is 
     794 * not really general purpose. 
     795 *  
     796 * Taken originally from https://gist.github.com/mloberg/1342473 and messed  
     797 *  about a bit. 
     798 * 
     799 * Important differences to Request 
     800 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
     801 * use append() to insert your form data (key and value, which might be a file) 
     802 * add a progress event listener to get progress of the UPLOAD not the DOWNLOAD 
     803 * send does not accept options, pass them to the constructor only 
     804 *   (namely {url: '....'} ) 
     805 * I don't know if POST() or GET() etc aliases will work, just use send() 
     806 *  
     807 */ 
     808 
     809FileManager.FileUploadRequest = new Class({ 
     810 
     811  Extends: Request, 
     812   
     813  options: { 
     814    emulation: false, 
     815    urlEncoded: false 
     816  }, 
     817   
     818  initialize: function(options){ 
     819    this.xhr = new Browser.Request(); 
     820    this.formData = new FormData(); 
     821    this.setOptions(options); 
     822    this.headers = this.options.headers; 
     823  }, 
     824   
     825  /** Append "something" to the request. 
     826   *    
     827   *  In Our case that something is either a field name and value  
     828   *   ( you don't seem to need to encode it yourself) 
     829   *  or field name and a file taken from an (input[type=file]).files list 
     830   *  
     831   *  It's kinda nice how it "just works", and also kinda worrying. 
     832   */ 
     833   
     834  append: function(key, value){ 
     835    this.formData.append(key, value); 
     836    return this.formData; 
     837  }, 
     838   
     839  reset: function(){ 
     840    this.formData = new FormData(); 
     841  }, 
     842   
     843  send: function(){ 
     844    var url = this.options.url; 
     845     
     846    this.options.isSuccess = this.options.isSuccess || this.isSuccess; 
     847    this.running = true; 
     848     
     849    var xhr = this.xhr; 
     850    xhr.open('POST', url, true); 
     851    xhr.onreadystatechange = this.onStateChange.bind(this); 
     852     
     853    if (('onprogress' in xhr)) 
     854    { 
     855      xhr.onloadstart = this.loadstart.bind(this); 
     856       
     857      // By attaching to xhr.upload we get progress of that,  
     858      // rather than the unknowable response progress 
     859      xhr.upload.onprogress = this.progress.bind(this); 
     860    } 
     861     
     862    Object.each(this.headers, function(value, key){ 
     863      try{ 
     864        xhr.setRequestHeader(key, value); 
     865      }catch(e){ 
     866        this.fireEvent('exception', [key, value]); 
     867      } 
     868    }, this); 
     869     
     870 
     871     
     872    this.fireEvent('request'); 
     873    xhr.send(this.formData); 
     874     
     875    if(!this.options.async) this.onStateChange(); 
     876    if(this.options.timeout) this.timer = this.timeout.delay(this.options.timeout, this); 
     877    return this; 
     878  } 
     879 
     880}); 
  • trunk/plugins/MootoolsFileManager/mootools-filemanager/Source/Uploader.js

    r1321 r1371  
    342342                        onFileComplete: function(f) { 
    343343                                self.diag.log('FlashUploader: onFileComplete', arguments, ', fileList: ', self.swf.fileList); 
    344                                 self.upload.lastFileUploaded = f.name; 
     344                                self.upload.lastFileUploaded = f.name.replace(/\s+/g, '_').replace(/_{2,}/g, '_'); 
    345345                        }, 
    346346                        onFail: function(error) { 
Note: See TracChangeset for help on using the changeset viewer.