1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016 Synacor, Inc.
  5  *
  6  * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at: https://www.zimbra.com/license
  9  * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15
 10  * have been added to cover use of software over a computer network and provide for limited attribution
 11  * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B.
 12  *
 13  * Software distributed under the License is distributed on an "AS IS" basis,
 14  * WITHOUT WARRANTY OF ANY KIND, either express or implied.
 15  * See the License for the specific language governing rights and limitations under the License.
 16  * The Original Code is Zimbra Open Source Web Client.
 17  * The Initial Developer of the Original Code is Zimbra, Inc.  All rights to the Original Code were
 18  * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015.
 19  *
 20  * All portions of the code are Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 /**
 25  * @overview
 26  */
 27 
 28 /**
 29  * Creates an upload dialog.
 30  * @class
 31  * This class represents an upload dialog.
 32  * 
 33  * @param	{DwtComposite}	shell		the parent
 34  * @param	{String}	className		the class name
 35  *  
 36  * @extends		DwtDialog
 37  */
 38 ZmUploadDialog = function(shell, className) {
 39 	className = className || "ZmUploadDialog";
 40 	var title = ZmMsg.uploadDocs;
 41 	DwtDialog.call(this, {parent:shell, className:className, title:title});
 42 	this.setButtonListener(DwtDialog.OK_BUTTON, new AjxListener(this, this._upload));
 43 	this._createUploadHtml();
 44     this._showLinkTitleText = false;
 45     this._linkText = {};
 46 	this._controller = null;
 47 
 48 	this._inprogress = false;
 49 }
 50 
 51 ZmUploadDialog.prototype = new DwtDialog;
 52 ZmUploadDialog.prototype.constructor = ZmUploadDialog;
 53 
 54 // Constants
 55 
 56 ZmUploadDialog.UPLOAD_FIELD_NAME = "uploadFile";
 57 ZmUploadDialog.UPLOAD_TITLE_FIELD_NAME = "uploadFileTitle";
 58 
 59 // Data
 60 
 61 ZmUploadDialog.prototype._selector;
 62 
 63 ZmUploadDialog.prototype._uploadFolder;
 64 ZmUploadDialog.prototype._uploadCallback;
 65 
 66 ZmUploadDialog.prototype._extensions;
 67 
 68 // Public methods
 69 /**
 70  * Enables the link title option.
 71  * 
 72  * @param	{Boolean}	enabled		if <code>true</code>, to enbled the link title option
 73  */
 74 ZmUploadDialog.prototype.enableLinkTitleOption =
 75 function(enabled) {
 76     this._showLinkTitleText = enabled;    
 77 };
 78 
 79 /**
 80  * Sets allowed extensions.
 81  * 
 82  * @param	{Array}		array		an array of extensions
 83  */
 84 ZmUploadDialog.prototype.setAllowedExtensions =
 85 function(array) {
 86 	this._extensions = array;
 87 	if (array) {
 88 		for (var i = 0; i < array.length; i++) {
 89 			array[i] = array[i].toUpperCase();
 90 		}
 91 	}
 92 };
 93 
 94 ZmUploadDialog.prototype.getNotes =
 95 function(){
 96     return (this._notes ? this._notes.value : "");
 97 };
 98 
 99 ZmUploadDialog.prototype.setNotes =
100 function(notes){
101     if(this._notes){
102         this._notes.value = (notes || "");
103     }
104 };
105 
106 ZmUploadDialog.prototype.warnInProgress = function() {
107 	var msgDlg = appCtxt.getMsgDialog();
108 	msgDlg.setMessage(ZmMsg.uploadDisabledInProgress, DwtMessageDialog.WARNING_STYLE);
109 	msgDlg.popup();
110 }
111 
112 ZmUploadDialog.prototype.popup =
113 function(controller, folder, callback, title, loc, oneFileOnly, noResolveAction, showNotes, isImage, conflictAction) {
114 	if (this._inprogress) {
115 		this.warnInProgress();
116 		return;
117 	}
118 
119 	this._controller     = controller;
120 	this._uploadFolder   = folder;
121 	this._uploadCallback = callback;
122 	this._conflictAction = conflictAction;
123 	var aCtxt = ZmAppCtxt.handleWindowOpener();
124 
125     this._supportsHTML5 = AjxEnv.supportsHTML5File && !this._showLinkTitleText && (aCtxt.get(ZmSetting.DOCUMENT_SIZE_LIMIT) != null);
126 
127 	this.setTitle(title || ZmMsg.uploadDocs);
128 
129 	// reset input fields
130 	var table = this._tableEl;
131 	var rows = table.rows;
132 	while (rows.length) {
133 		table.deleteRow(rows.length - 1);
134 	}
135 	this._addFileInputRow(oneFileOnly);
136 
137 	// hide/show elements
138     var id = this._htmlElId;
139     var labelEl = document.getElementById(id+"_label");
140     if (labelEl) {
141         if(oneFileOnly && isImage){
142             labelEl.innerHTML = ZmMsg.uploadChooseImage;
143             Dwt.setVisible(labelEl, true);
144         }
145         else{
146             labelEl.innerHTML = ZmMsg.uploadChoose;
147             Dwt.setVisible(labelEl, !oneFileOnly);
148         }
149     }
150     var actionRowEl = document.getElementById(id+"_actionRow");
151 	if (actionRowEl) {
152 		Dwt.setVisible(actionRowEl, !noResolveAction);
153 	}
154 
155     var notesEl = document.getElementById(id+"_notesTD");
156 	if (notesEl) {
157 		Dwt.setVisible(notesEl, showNotes);
158 	}
159     // In case of a single file upload show proper info message
160 
161     var docSizeInfo = document.getElementById((id+"_info"));
162     var attSize = AjxUtil.formatSize(aCtxt.get(ZmSetting.DOCUMENT_SIZE_LIMIT) || 0, true);
163         if(docSizeInfo){
164             if(oneFileOnly){
165                 docSizeInfo.innerHTML = AjxMessageFormat.format(ZmMsg.attachmentLimitMsgSingleFile, attSize);
166             }
167             else{
168                 docSizeInfo.innerHTML = AjxMessageFormat.format(ZmMsg.attachmentLimitMsg, attSize);
169             }
170         }
171 
172 
173 	// show
174 	DwtDialog.prototype.popup.call(this, loc);
175 };
176 
177 ZmUploadDialog.prototype.popdown =
178 function() {
179 	/***
180 	// NOTE: Do NOT set these values to null! The conflict dialog will
181 	//       call back to this dialog after it's hidden to process the
182 	//       files that should be replaced.
183 	this._uploadFolder = null;
184 	this._uploadCallback = null;
185 	/***/
186 
187     this._extensions = null;
188 
189     //Cleanup
190     this._enableStatus = false;
191 
192     this._notes.removeAttribute("disabled");
193     this.setNotes("");
194     this._msgInfo.innerHTML = "";
195 	this._conflictAction = null;
196     
197 	DwtDialog.prototype.popdown.call(this);
198 };
199 
200 ZmUploadDialog.prototype._popupErrorDialog = function(message) {
201 	var dialog = appCtxt.getMsgDialog();
202 	dialog.setMessage(message, DwtMessageDialog.CRITICAL_STYLE, this._title);
203 	dialog.popup();
204 };
205 
206 //to give explicitly the uploadForm, files to upload and folderId used for briefcase
207 ZmUploadDialog.prototype.uploadFiles =
208 function(files, uploadForm, folder) {
209 	if (this._inprogress) {
210 		this.warnInProgress();
211 		return;
212 	}
213 
214     if (files.length == 0) {
215 		return;
216 	}
217     this._uploadFolder = folder;
218 
219     var popDownCallback = this.popdown.bind(this);
220     var uploadParams = {
221         uploadFolder: folder,
222         preResolveConflictCallback: popDownCallback,
223         errorCallback: popDownCallback,
224         finalCallback: this._finishUpload.bind(this),
225         docFiles: files
226     }
227 
228 	var aCtxt = appCtxt.isChildWindow ? parentAppCtxt : appCtxt;
229 	var briefcaseApp = aCtxt.getApp(ZmApp.BRIEFCASE);
230     var callback =  briefcaseApp.uploadSaveDocs.bind(briefcaseApp, null, uploadParams);
231 
232     var uploadMgr = appCtxt.getUploadManager();
233 	  window._uploadManager = uploadMgr;
234 
235     try {
236 		uploadMgr.execute(callback, uploadForm);
237 	} catch (ex) {
238 		if (ex.msg) {
239 			this._popupErrorDialog(ex.msg);
240 		} else {
241 			this._popupErrorDialog(ZmMsg.unknownError);
242 		}
243 	}
244 };
245 
246 // Protected methods
247 ZmUploadDialog.prototype._upload = function(){
248     var form         	= this._uploadForm;
249     var uploadFiles  	= [];
250     var errors       	= {};
251     this._linkText   	= {};
252     var aCtxt        	= ZmAppCtxt.handleWindowOpener();
253     var maxSize      	=  aCtxt.get(ZmSetting.DOCUMENT_SIZE_LIMIT);
254     var elements     	= form.elements;
255     var notes           = this.getNotes();
256 	var fileObj 		= [];
257 	var zmUploadManager = appCtxt.getZmUploadManager();
258 	var file;
259     var msgFormat;
260     var errorFilenames;
261 	var newError;
262     for (var i = 0; i < elements.length; i++) {
263         var element = form.elements[i];
264         if ((element.name != ZmUploadDialog.UPLOAD_FIELD_NAME) || !element.value)  continue;
265 
266         this._msgInfo.innerHTML = "";
267 		var errors = [];
268         if(this._supportsHTML5){
269             var files = element.files;
270 			var errors = [];
271             for (var j = 0; j < files.length; j++){
272                 file = files[j];
273                 fileObj.push(file);
274 				newError = zmUploadManager.getErrors(file, maxSize);
275 				if (newError) {
276 					errors.push(newError);
277 				} else {
278                     uploadFiles.push({name: file.name, fullname: file.name, notes: notes});
279                 }
280             }
281         } else {
282 			var fileName = element.value.replace(/^.*[\\\/:]/, "");
283             file = { name: fileName };
284 			newError = zmUploadManager.getErrors(file, maxSize);
285 			if (newError) {
286 				errors.push(newError);
287 			} else {
288                 uploadFiles.push({ fullname: element.value, name: fileName, notes: notes});
289 			}
290         }
291 		if(this._showLinkTitleText) {
292 			var id = element.id;
293 			id = id.replace("_input", "") + "_titleinput";
294 			var txtElement = document.getElementById(id);
295 			if(txtElement) {
296 				this._linkText[file.name] = txtElement.value;
297 			}
298 		}
299 
300     }
301 
302 	if (errors.length > 0) {
303 		this._msgInfo.innerHTML = zmUploadManager.createUploadErrorMsg(errors, maxSize, "<br>");
304 	} else if (uploadFiles.length > 0) {
305 		var briefcaseApp    = aCtxt.getApp(ZmApp.BRIEFCASE);
306 		var shutDownCallback = null;
307 		var uploadButton     = null;
308 		if (this._controller == null) {
309 			shutDownCallback = this.popdown.bind(this);
310 		} else {
311 			var toolbar = this._controller.getCurrentToolbar();
312 			if (toolbar) {
313 				uploadButton = toolbar.getOp(ZmOperation.NEW_FILE);
314 			}
315 			this.popdown();
316 			shutDownCallback = this._enableUpload.bind(this, uploadButton);
317 		}
318         var uploadParams = {
319 			attachment:                 false,
320             files:                      fileObj,
321             notes:                      notes,
322             allResponses:               null,
323             start:                      0,
324             uploadFolder:               this._uploadFolder,
325 			completeAllCallback:        briefcaseApp.uploadSaveDocs.bind(briefcaseApp),
326 			conflictAction:				this._conflictAction || this._selector.getValue(),
327             preResolveConflictCallback: shutDownCallback,
328             errorCallback:              shutDownCallback,
329             completeDocSaveCallback:    this._finishUpload.bind(this, uploadButton),
330             docFiles:                   uploadFiles
331         }
332 		uploadParams.progressCallback = this._uploadFileProgress.bind(this, uploadButton, uploadParams);
333 
334         try {
335             if (this._supportsHTML5) {
336                 zmUploadManager.upload(uploadParams);
337             } else {
338 				var callback  = briefcaseApp.uploadSaveDocs.bind(briefcaseApp, null, uploadParams);
339                 var uploadMgr = appCtxt.getUploadManager();
340                 window._uploadManager = uploadMgr;
341                 uploadMgr.execute(callback, this._uploadForm);
342             }
343 
344 			if (uploadButton) {
345 				// The 16x16 upload image has ImgUpload0 (no fill) .. ImgUpload12 (completely filled) variants to give a
346 				// gross idea of the progress.
347 				ZmToolBar._setButtonStyle(uploadButton, null, ZmMsg.uploading, "Upload0");
348 				this._inprogress = true;
349 			}
350         } catch (ex) {
351 			this._enableUpload(uploadButton);
352             if (ex.msg) {
353                 this._popupErrorDialog(ex.msg);
354             } else {
355                 this._popupErrorDialog(ZmMsg.unknownError);
356             }
357         }
358     }
359 };
360 
361 ZmUploadDialog.prototype._uploadFileProgress =
362 	function(uploadButton, params, progress) {
363 		if (!uploadButton || !params || !progress.lengthComputable || !params.totalSize) return;
364 
365 		// The 16x16 upload image has ImgUpload0 (no fill) .. ImgUpload12 (completely filled) variants to give a
366 		// gross idea of the progress.  A tooltip indicating the progress will be added too.
367 		var progressFraction = (progress.loaded / progress.total);
368 		var uploadedSize = params.uploadedSize + (params.currentFileSize * progressFraction);
369 		var fractionUploaded = uploadedSize/params.totalSize;
370 		if (fractionUploaded > 1) {
371 			fractionUploaded = 1;
372 		}
373 		var progressBucket = Math.round(fractionUploaded * 12);
374 		DBG.println(AjxDebug.DBG3,"Upload Progress: total=" + params.totalSize + ", uploadedSize=" + params.uploadedSize +
375 					", currentSize="+ params.currentFileSize + ", progressFraction=" +
376 					progressFraction + ", fractionUploaded=" + fractionUploaded + ", progressBucket=" + progressBucket);
377 		ZmToolBar._setButtonStyle(uploadButton, null, ZmMsg.uploadNewFile, "Upload" + progressBucket.toString());
378 
379 		var tooltip = AjxMessageFormat.format(ZmMsg.uploadPercentComplete, [ Math.round(fractionUploaded * 100) ] )
380 		uploadButton.setToolTipContent(tooltip, true);
381 	};
382 
383 ZmUploadDialog.prototype._enableUpload = function(uploadButton) {
384 	if (!uploadButton) return;
385 
386 	ZmToolBar._setButtonStyle(uploadButton, null, ZmMsg.uploadNewFile, null);
387 	uploadButton.setToolTipContent(ZmMsg.uploadNewFile, true);
388 	this._inprogress = false;
389 };
390 
391 ZmUploadDialog.prototype._finishUpload = function(uploadButton, docFiles, uploadFolder) {
392     var filenames = [];
393     for (var i in docFiles) {
394         var name = docFiles[i].name;
395         if(this._linkText[name]) {
396             docFiles[i].linkText = this._linkText[name];
397         }
398         filenames.push(name);
399     }
400 	this._enableUpload(uploadButton);
401 
402     this._uploadCallback.run(uploadFolder, filenames, docFiles);
403 };
404 
405 ZmUploadDialog.prototype._addFileInputRow = function(oneInputOnly) {
406 	var id = Dwt.getNextId();
407 	var inputId = id + "_input";
408 	var removeId = id + "_remove";
409 	var addId = id + "_add";
410     var sizeId = id + "_size";
411 
412 	var table = this._tableEl;
413 	var row = table.insertRow(-1);
414 
415     var cellLabel = row.insertCell(-1);
416     cellLabel.innerHTML = ZmMsg.fileLabel;
417 
418 	var cell = row.insertCell(-1);
419 	// bug:53841 allow only one file upload when oneInputOnly is set
420 	cell.innerHTML = [
421 		"<input id='",inputId,"' type='file' name='",ZmUploadDialog.UPLOAD_FIELD_NAME,"' size=30 ",(this._supportsHTML5 ? (oneInputOnly ? "" : "multiple") : ""),">"
422 	].join("");
423 
424 	var cell = row.insertCell(-1);
425     cell.id = sizeId;
426 	cell.innerHTML = " ";
427 
428     //HTML5
429     if(this._supportsHTML5){
430         var inputEl = document.getElementById(inputId);
431         var sizeEl = cell;
432         Dwt.setHandler(inputEl, "onchange", AjxCallback.simpleClosure(this._handleFileSize, this, inputEl, sizeEl));
433     }
434 
435     if(oneInputOnly){
436         cell.colSpan = 3;
437     }else{    
438         var cell = row.insertCell(-1);
439         cell.innerHTML = [
440             "<span ",
441             "id='",removeId,"' ",
442             "onmouseover='this.style.cursor=\"pointer\"' ",
443             "onmouseout='this.style.cursor=\"default\"' ",
444             "style='color:blue;text-decoration:underline;'",
445             ">", ZmMsg.remove, "</span>"
446         ].join("");
447         var removeSpan = document.getElementById(removeId);
448         Dwt.setHandler(removeSpan, DwtEvent.ONCLICK, ZmUploadDialog._removeHandler);
449 
450         var cell = row.insertCell(-1);
451         cell.innerHTML = " ";
452         var cell = row.insertCell(-1);
453         cell.innerHTML = [
454             "<span ",
455             "id='",addId,"' ",
456             "onmouseover='this.style.cursor=\"pointer\"' ",
457             "onmouseout='this.style.cursor=\"default\"' ",
458             "style='color:blue;text-decoration:underline;'",
459             ">", ZmMsg.add, "</span>"
460         ].join("");
461         var addSpan = document.getElementById(addId);
462         Dwt.setHandler(addSpan, DwtEvent.ONCLICK, ZmUploadDialog._addHandler);
463     }
464 
465 
466     if(this._showLinkTitleText) {
467         var txtInputId = id + "_titleinput";
468         var txtRow = table.insertRow(-1);
469         var txtCell = txtRow.insertCell(-1);
470         txtCell.innerHTML = [
471     		ZmMsg.linkTitleOptionalLabel
472     	].join("");
473 
474         txtCell = txtRow.insertCell(-1);
475 	    txtCell.innerHTML = [
476     		"<input id='",txtInputId,"' type='text' name='",ZmUploadDialog.UPLOAD_TITLE_FIELD_NAME,"' size=40>"
477     	].join("");
478         txtCell.colSpan = 3;
479     }
480 };
481 
482 ZmUploadDialog.prototype._handleFileSize =
483 function(inputEl, sizeEl){
484 
485     var files = inputEl.files;
486     if(!files) return;
487 
488     var sizeStr = [], className, totalSize =0;
489     for(var i=0; i<files.length;i++){
490         var file = files[i];
491         var size = file.size || file.fileSize /*Safari*/ || 0;
492 	    var aCtxt = ZmAppCtxt.handleWindowOpener();
493         if(size > aCtxt.get(ZmSetting.DOCUMENT_SIZE_LIMIT))
494             className = "RedC";
495         totalSize += size;
496     }
497 
498     if(sizeEl) {
499         sizeEl.innerHTML = "  ("+AjxUtil.formatSize(totalSize, true)+")";
500         if(className)
501             Dwt.addClass(sizeEl, "RedC");
502         else
503             Dwt.delClass(sizeEl, "RedC");
504     }
505     
506 };
507 
508 ZmUploadDialog._removeHandler = function(event) {
509 	var span = DwtUiEvent.getTarget(event || window.event);
510 	var cell = span.parentNode;
511 	var row = cell.parentNode;
512 
513     var endRow = row;
514 
515     if(span.id) {
516        var id = span.id;
517        id = id.replace("_remove", "") + "_titleinput";
518        var txtInput = document.getElementById(id);
519        if(txtInput) {
520            var txtCell = txtInput.parentNode;
521            var txtRow = txtCell.parentNode;
522            endRow = txtRow;
523        }
524     }
525     
526 	if (row.previousSibling == null && endRow.nextSibling == null) {
527 		var comp = DwtControl.findControl(span);
528 		comp._addFileInputRow();
529 	}
530 
531     if(endRow != row) {
532         endRow.parentNode.removeChild(endRow);
533     }
534 
535 	row.parentNode.removeChild(row);
536 };
537 
538 ZmUploadDialog._addHandler = function(event) {
539 	var span = DwtUiEvent.getTarget(event || window.event);
540 	var comp = DwtControl.findControl(span);
541 	comp._addFileInputRow();
542 };
543 
544 ZmUploadDialog.prototype._createUploadHtml = function() {
545 	var id = this._htmlElId;
546 	var aCtxt = ZmAppCtxt.handleWindowOpener();
547     var uri = aCtxt.get(ZmSetting.CSFE_UPLOAD_URI);
548 
549     var subs = {
550         id: id,
551         uri: uri
552     };
553     this.setContent(AjxTemplate.expand("share.Dialogs#UploadDialog", subs));
554 
555     //variables
556     this._uploadForm = document.getElementById((id+"_form"));
557     this._tableEl = document.getElementById((id + "_table"));
558     this._msgInfo = document.getElementById((id+"_msg"));
559     this._notes = document.getElementById((id+"_notes"));
560 
561     //Conflict Selector
562 	this._selector = new DwtSelect({parent:this});
563 	this._selector.addOption(ZmMsg.uploadActionKeepMine, false, ZmBriefcaseApp.ACTION_KEEP_MINE);
564 	this._selector.addOption(ZmMsg.uploadActionKeepTheirs, false, ZmBriefcaseApp.ACTION_KEEP_THEIRS);
565 	this._selector.addOption(ZmMsg.uploadActionAsk, true, ZmBriefcaseApp.ACTION_ASK);
566 	this._selector.reparentHtmlElement((id+"_conflict"));
567     
568     //Info Section
569     var docSizeInfo = document.getElementById((id+"_info"));
570     if(docSizeInfo){
571         var attSize = AjxUtil.formatSize(aCtxt.get(ZmSetting.DOCUMENT_SIZE_LIMIT) || 0, true)
572         docSizeInfo.innerHTML = AjxMessageFormat.format(ZmMsg.attachmentLimitMsg, attSize);
573     }
574     	
575 };
576