1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 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) 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 /**
 25  * @overview
 26  */
 27 
 28 /**
 29  * Creates an attachment dialog.
 30  * @class
 31  * This class represents an attachment dialog.
 32  * 
 33  * @param	{DwtControl}	shell		the parent
 34  * @param	{String}	className		the class name
 35  * 
 36  * @extends		DwtDialog
 37  */
 38 ZmAttachDialog = function(shell, className) {
 39 
 40 	className = className || "ZmAttachDialog";
 41 	DwtDialog.call(this, {parent:shell, className:className, title:ZmMsg.attachFile});
 42 
 43 	// Initialize
 44 	this._createBaseHtml();
 45 
 46 	// Ok and Cancel Actions
 47 	this._defaultCancelCallback = new AjxCallback(this, this._defaultCancelListener);
 48 	this._cancelListener = null;
 49 
 50 	this._defaultOkCallback = new AjxCallback(this, this._defaultOkListener);
 51 	this._okListener = null;
 52 
 53 	this.setButtonListener(DwtDialog.CANCEL_BUTTON, new AjxListener(this, function() {
 54 		this._cancelButtonListener();
 55 	}));
 56 
 57 	this.setButtonListener(DwtDialog.OK_BUTTON, new AjxListener(this, function() {
 58 		this._okButtonListener();
 59 	}));
 60 
 61 
 62 	var okButton = this.getButton(DwtDialog.OK_BUTTON);
 63 	okButton.setText(ZmMsg.attach);
 64 
 65 };
 66 
 67 ZmAttachDialog.prototype = new DwtDialog;
 68 ZmAttachDialog.prototype.constructor = ZmAttachDialog;
 69 
 70 /**
 71  * Defines the "briefcase" tab key.
 72  */
 73 ZmAttachDialog.TABKEY_BRIEFCASE		= "BRIEFCASE";
 74 
 75 //Listeners
 76 
 77 /**
 78  * Adds a cancel button listener.
 79  * 
 80  * @param	{constant}		tabKey		the tab key (see <code>TABKEY_</code> constants)
 81  * @param	{AjxListener|AjxCallback}	cancelCallbackOrListener		the listener
 82  */
 83 ZmAttachDialog.prototype.setCancelListener =
 84 function(cancelCallbackOrListener) {
 85 	if (cancelCallbackOrListener &&
 86 		(cancelCallbackOrListener instanceof AjxListener ||
 87 		 cancelCallbackOrListener instanceof AjxCallback))
 88 	{
 89 		this._cancelListener = cancelCallbackOrListener;
 90 	}
 91 };
 92 
 93 
 94 ZmAttachDialog.prototype._defaultCancelListener =
 95 function() {
 96 	this.popdown();
 97 };
 98 
 99 ZmAttachDialog.prototype._cancelButtonListener =
100 function() {
101 	if (this._cancelListener) {
102 		this._cancelListener.run();
103 	} else {
104 		this._defaultCancelCallback.run();
105 	}
106 };
107 
108 /**
109  * Adds a OK button listener.
110  * 
111  * @param	{constant}		tabKey		the tab key (see <code>TABKEY_</code> constants)
112  * @param	{AjxListener|AjxCallback}	cancelCallbackOrListener		the listener
113  */
114 ZmAttachDialog.prototype.setOkListener =
115 function(okCallbackOrListener) {
116 	if (okCallbackOrListener &&
117 		(okCallbackOrListener instanceof AjxListener ||
118 		 okCallbackOrListener instanceof AjxCallback))
119 	{
120 		this._okListener = okCallbackOrListener;
121 	}
122 };
123 
124 ZmAttachDialog.prototype._defaultOkListener =
125 function() {
126 	this.popdown();
127 };
128 
129 ZmAttachDialog.prototype._okButtonListener =
130 function() {
131 
132     var okButton = this.getButton(DwtDialog.OK_BUTTON);
133     okButton.setEnabled(false);
134 
135 	if (this._okListener) {
136 		this._okListener.run(this);
137 	} else {
138 		this._defaultOkCallback.run();
139 	}
140     
141      okButton.setEnabled(true);
142 };
143 
144 // Create HTML Container
145 ZmAttachDialog.prototype._createBaseHtml =
146 function() {
147 	this._baseContainerView = new DwtComposite({parent:this, className:"ZmAttachDialog-container"});
148 	this._initializeTabView(this._baseContainerView);
149 	this.setView(this._baseContainerView);
150 };
151 
152 ZmAttachDialog.prototype._initializeTabView =
153 function(view) {
154     this._setAttachmentSizeSection(view);
155 	this._setInlineOptionSection(view);
156     this._setMsgSection(view);
157 	this._setFooterSection(view);
158 };
159 
160 /**
161  * @private
162  */
163 ZmAttachDialog.prototype.stateChangeListener =
164 function(ev) {
165 	// Reset Inline Options Here
166 	this._resetInlineOption();
167 };
168 
169 
170 ZmAttachDialog.prototype._setAttachmentSizeSection =
171 function(view) {
172 	var div = document.createElement("div");
173 	div.className = "ZmAttachDialog-note";
174     var attSize = AjxUtil.formatSize(appCtxt.get(ZmSetting.MESSAGE_SIZE_LIMIT) || 0, true)
175 	div.innerHTML = AjxMessageFormat.format(ZmMsg.attachmentLimitMsg, attSize);
176 	view.getHtmlElement().appendChild(div);
177 };
178 
179 ZmAttachDialog.prototype._setMsgSection =
180 function(view) {
181 	var div = document.createElement("div");
182 	div.className = "ZmAttachDialog-footer";
183 	div.id = Dwt.getNextId();
184 	view.getHtmlElement().appendChild(div);
185 	this._msgDiv = document.getElementById(div.id);
186 };
187 
188 ZmAttachDialog.prototype._setFooterSection =
189 function(view) {
190 	var div = document.createElement("div");
191 	div.className = "ZmAttachDialog-footer";
192 	div.id = Dwt.getNextId();
193 	view.getHtmlElement().appendChild(div);
194 
195 	this._footer = document.getElementById(div.id);
196 };
197 
198 /**
199  * Sets the footer content.
200  * 
201  * @param	{String}	html		the HTML footer content
202  */
203 ZmAttachDialog.prototype.setFooter =
204 function(html) {
205 	if (typeof html == "string") {
206 		this._footer.innerHTML = html;
207 	} else {
208 		this._footer.appendChild(html);
209 	}
210 };
211 
212 //Called when AjxEnv.supportsHTML5File is false
213 
214 ZmAttachDialog.prototype.submitAttachmentFile =
215 function(view) {
216     this.upload(this._uploadCallback, view.uploadForm);
217 };
218 
219 ZmAttachDialog.prototype.cancelUploadFiles =
220 function() {
221 	// Fix this, as this needs feature request like AjxPost.getRequestId()
222 	// We need to cancel actual request, but for now just close the window
223 	this._cancelUpload = true;
224 	this._defaultCancelListener();
225 };
226 
227 ZmAttachDialog.prototype.setUploadCallback =
228 function(callback) {
229 	this._uploadCallback = callback;
230 };
231 
232 ZmAttachDialog.prototype.getUploadCallback =
233 function() {
234 	return this._uploadCallback;
235 };
236 
237 /**
238  * Uploads the attachments.
239  * 
240  * @param	{AjxCallback}		callback		the callback
241  * @param	{Object}			uploadForm		the upload form
242  */
243 ZmAttachDialog.prototype.upload =
244 function(callback, uploadForm) {
245 	if (!callback) {
246 		callback = false;
247 	}
248 	this.setButtonEnabled(DwtDialog.OK_BUTTON, false);
249 	this.setButtonEnabled(DwtDialog.CANCEL_BUTTON, true);
250 	this.setFooter(ZmMsg.attachingFiles);
251 	this._cancelUpload = false;
252 	this._processUpload(callback, uploadForm);
253 };
254 
255 ZmAttachDialog.prototype._processUpload =
256 function(callback, uploadForm) {
257 	var ajxCallback = new AjxCallback(this, this._uploadDoneCallback, [callback]);
258 	var um = appCtxt.getUploadManager();
259 	window._uploadManager = um;
260 
261 	try {
262 		um.execute(ajxCallback, uploadForm);
263 	} catch (ex) {
264 		ajxCallback.run();
265 	}
266 };
267 
268 ZmAttachDialog.prototype._uploadDoneCallback =
269 function(callback, status, attId) {
270 	if (this._cancelUpload) { return; }
271 
272 	this.setButtonEnabled(DwtDialog.CANCEL_BUTTON, true);
273 
274 	if (status == AjxPost.SC_OK) {
275 		this.setFooter(ZmMsg.attachingFilesDone);
276 		if (callback) {
277 			callback.run(status, attId);
278 		}
279 	} else if (status == AjxPost.SC_UNAUTHORIZED) {
280 		// auth failed during att upload - let user relogin, continue with compose action
281 		var ex = new AjxException("401 response during attachment upload", ZmCsfeException.SVC_AUTH_EXPIRED);
282 		appCtxt.getAppController()._handleException(ex, {continueCallback:callback});
283 	} else {
284 		// bug fix #2131 - handle errors during attachment upload.
285 		appCtxt.getAppController().popupUploadErrorDialog(ZmItem.MSG, status);
286 		this.setFooter(ZmMsg.attachingFilesError);
287 	}
288 
289 	this.setButtonEnabled(DwtDialog.OK_BUTTON, true);
290 };
291 
292 ZmAttachDialog.prototype.removePrevAttDialogContent =
293 function(contentDiv) {
294     var elementNode =  contentDiv && contentDiv.firstChild;
295     if (elementNode && elementNode.className == "DwtComposite" ){
296         contentDiv.removeChild(elementNode);
297     }
298 };
299 
300 
301 ZmAttachDialog.prototype.getBriefcaseView =
302 function(){
303 
304     this.removePrevAttDialogContent(this._getContentDiv().firstChild);
305     this.setTitle(ZmMsg.attachFile);
306 
307 	if (!this._briefcaseView) {
308 		AjxDispatcher.require(["BriefcaseCore", "Briefcase"]);
309 		this._briefcaseView = new ZmBriefcaseTabView(this);
310 	}
311 
312     this._briefcaseView.reparentHtmlElement(this._getContentDiv().childNodes[0], 0);
313     var okCallback = new AjxCallback(this._briefcaseView, this._briefcaseView.uploadFiles);
314     this.setOkListener(okCallback);
315     this.setCancelListener((new AjxCallback(this,this.cancelUploadFiles)));
316 
317 
318 	return this._briefcaseView;
319 };
320 
321 // Inline Option for attachment Dialog.
322 ZmAttachDialog.prototype._setInlineOptionSection =
323 function(view){
324 	var div = document.createElement("div");
325 	div.className = "ZmAttachDialog-inline";
326 	div.id = Dwt.getNextId();
327 	view.getHtmlElement().appendChild(div);
328 
329 	this._inlineOption = document.getElementById(div.id);
330 };
331 
332 ZmAttachDialog.prototype.enableInlineOption =
333 function(enable) {
334 	if (enable) {
335 		var inlineCheckboxId = this._htmlElId + "_inlineCheckbox";
336 		this._inlineOption.setAttribute("option", "inline");
337 		this._inlineOption.innerHTML = [
338 			"<input type='checkbox' name='inlineimages' id='",
339 			inlineCheckboxId,
340 			"'> <label for='",
341 			inlineCheckboxId,
342 			"'>",
343 			ZmMsg.inlineAttachmentOption,
344 			"</label>"
345 		].join("");
346 		this._tabGroup.addMember(this._inlineOption.getElementsByTagName('input')[0],0);
347 	} else {
348 		this._inlineOption.innerHTML = "";
349 	}
350 };
351 
352 ZmAttachDialog.prototype._resetInlineOption =
353 function() {
354 	var inlineOption = document.getElementById(this._htmlElId+"_inlineCheckbox");
355 	if (inlineOption) {
356 		inlineOption.checked = false;
357 	}
358 };
359 
360 ZmAttachDialog.prototype.isInline =
361 function() {
362 	var inlineOption = document.getElementById(this._htmlElId+"_inlineCheckbox");
363 	return (inlineOption && inlineOption.checked);
364 };
365 
366 ZmAttachDialog.prototype.setInline =
367 function(checked) {
368 	var inlineOption = document.getElementById(this._htmlElId+"_inlineCheckbox");
369 
370 	if (inlineOption)
371 		inlineOption.checked = checked;
372 };
373 
374 
375 /**
376  * Attachment Upload View
377  *
378  * @param parent
379  * @param className
380  * @param posStyle
381  *
382  * @class
383  * @private
384  */
385 ZmAttachDialog.prototype.getMyComputerView =
386 function(){
387     var newElm = false;
388     this.removePrevAttDialogContent(this._getContentDiv().firstChild);
389     this.setTitle(ZmMsg.attachFile);
390 
391 	if (!this._myComputerView) {
392 		this._myComputerView = new ZmMyComputerTabViewPage(this);
393         newElm = true;
394 	}
395 
396     this._myComputerView.reparentHtmlElement(this._getContentDiv().childNodes[0], 0);
397 
398     if (!newElm) {
399         this._myComputerView.resetAttachments()
400     }
401 
402     var okCallback = new AjxCallback(this, this.submitAttachmentFile,[this._myComputerView]);
403     this.setOkListener(okCallback);
404     this.setCancelListener((new AjxCallback(this,this.cancelUploadFiles)));
405 
406 	return this._myComputerView;
407 };
408 
409 
410 ZmMyComputerTabViewPage = function(parent, className, posStyle) {
411 	if (arguments.length == 0) { return; }
412 
413 	DwtComposite.call(this, parent, className, Dwt.STATIC_STYLE);
414     this._createHtml();
415     this.showMe();
416 	this.setScrollStyle(Dwt.SCROLL);
417 };
418 
419 ZmMyComputerTabViewPage.prototype = new DwtComposite;
420 ZmMyComputerTabViewPage.prototype.constructor = ZmMyComputerTabViewPage;
421 
422 ZmMyComputerTabViewPage.SHOW_NO_ATTACHMENTS	= 5;
423 ZmMyComputerTabViewPage.MAX_NO_ATTACHMENTS	= 10;
424 ZmMyComputerTabViewPage.UPLOAD_FIELD_NAME	= "_attFile_";
425 
426 
427 ZmMyComputerTabViewPage.prototype.showMe =
428 function() {
429 	this.resetAttachments();
430 	this.setSize(Dwt.DEFAULT, "240");
431 	this._focusAttEl();
432 };
433 
434 ZmMyComputerTabViewPage.prototype.hideMe =
435 function() {
436 	DwtTabViewPage.prototype.hideMe.call(this);
437 };
438 
439 // Create UI for MyComputer
440 ZmMyComputerTabViewPage.prototype._createHtml =
441 function() {
442 
443 	var subs = {
444 		id: this._htmlElId,
445 		uri: (appCtxt.get(ZmSetting.CSFE_ATTACHMENT_UPLOAD_URI) + "?fmt=extended")
446 	};
447 	this.setContent(AjxTemplate.expand("share.Dialogs#ZmAttachDialog-MyComputerTab", subs));
448 
449 	this.attachmentTable = document.getElementById(this._htmlElId+"_attachmentTable");
450 	this.uploadForm = document.getElementById(this._htmlElId+"_uploadForm");
451 	this.attachmentButtonTable = document.getElementById(this._htmlElId+"_attachmentButtonTable");
452 
453 	this._addAttachmentFieldButton();
454 	this._attachCount = 0;
455 };
456 
457 // Attachments
458 ZmMyComputerTabViewPage.prototype._addAttachmentField =
459 function() {
460 	if (this._attachCount >= ZmMyComputerTabViewPage.MAX_NO_ATTACHMENTS) { return; }
461 
462 	this._attachCount++;
463 
464 	var row = this.attachmentTable.insertRow(-1);
465 	var cell = row.insertCell(-1);
466 	var fieldId = Dwt.getNextId();
467 
468 	var subs = {
469 		id: fieldId,
470 		uploadName: ZmMyComputerTabViewPage.UPLOAD_FIELD_NAME
471 	};
472 	cell.innerHTML = AjxTemplate.expand("share.Dialogs#ZmAttachDialog-MyComputerTab-AddAttachment", subs);
473 
474 	var removeEl = document.getElementById(fieldId+"_remove");   
475 	removeEl.onclick = AjxCallback.simpleClosure(this._removeAttachmentField, this, row);
476 
477     var inputId = fieldId+"_input";
478 	if (this._focusElId == -1) {
479 		this._focusElId = inputId;
480 	}    
481     var inputEl = document.getElementById(inputId);
482     var sizeEl = document.getElementById(fieldId+"_size");
483 
484     //HTML5
485     if(AjxEnv.supportsHTML5File){
486         Dwt.setHandler(inputEl, "onchange", AjxCallback.simpleClosure(this._handleFileSize, this, inputEl, sizeEl));
487     }
488 
489 	// trap key presses in IE for input field so we can ignore ENTER key (bug 961)
490 	if (AjxEnv.isIE) {
491 		inputEl.onkeydown = AjxCallback.simpleClosure(this._handleKeys, this);
492 	}
493 };
494 
495 ZmMyComputerTabViewPage.prototype._handleFileSize =
496 function(inputEl, sizeEl){
497 
498     var files = inputEl.files;
499     if(!files) return;
500 
501     var sizeStr = [], className, totalSize =0;
502     for(var i=0; i<files.length;i++){
503         var file = files[i];
504         var size = file.size || file.fileSize /*Safari*/ || 0;
505         if ((-1 /* means unlimited */ != appCtxt.get(ZmSetting.MESSAGE_SIZE_LIMIT)) &&
506             (size > appCtxt.get(ZmSetting.MESSAGE_SIZE_LIMIT))) {
507             className = "RedC";
508         }
509         totalSize += size;
510     }
511 
512     if(sizeEl) {
513         sizeEl.innerHTML = "  ("+AjxUtil.formatSize(totalSize, true)+")";
514         if(className)
515             Dwt.addClass(sizeEl, "RedC");
516         else
517             Dwt.delClass(sizeEl, "RedC");
518     }
519 };
520 
521 
522 
523 ZmMyComputerTabViewPage.prototype._removeAttachmentField =
524 function(row) {
525 	this.attachmentTable.deleteRow(row.rowIndex);
526 	this._attachCount--;
527 
528 	if (this._attachCount == 0) {
529 		this._addAttachmentField();
530 	}
531 };
532 
533 ZmMyComputerTabViewPage.prototype._addAttachmentFieldButton =
534 function() {
535 	var row = this.attachmentButtonTable.insertRow(-1);
536 	var cell = row.insertCell(-1);
537 
538 	var button = new DwtButton({parent:this, parentElement:cell});
539 	button.setText(ZmMsg.addMoreAttachments);
540 	button.addSelectionListener(new AjxListener(this, this._addAttachmentField));
541 };
542 
543 ZmMyComputerTabViewPage.prototype.gotAttachments =
544 function() {
545 	var atts = document.getElementsByName(ZmMyComputerTabViewPage.UPLOAD_FIELD_NAME);
546 
547 	for (var i = 0; i < atts.length; i++)
548 		if (atts[i].value.length) {
549 			return true;
550 		}
551 	return false;
552 };
553 
554 ZmMyComputerTabViewPage.prototype.resetAttachments =
555 function() {
556 	// CleanUp
557 	this._cleanTable(this.attachmentTable);
558 	this._attachCount = 0;
559 	if (ZmMyComputerTabViewPage.SHOW_NO_ATTACHMENTS > ZmMyComputerTabViewPage.MAX_NO_ATTACHMENTS) {
560 		ZmMyComputerTabViewPage.SHOW_NO_ATTACHMENTS = ZmMyComputerTabViewPage.MAX_NO_ATTACHMENTS;
561 	}
562 
563 	// Re-initialize UI
564 	this._focusElId = -1;
565 	var row = this.attachmentTable.insertRow(-1);
566 	var cell = row.insertCell(-1);
567 	cell.appendChild(document.createElement("br"));
568 	cell.appendChild(document.createElement("br"));
569 
570 	for (var i = 0; i < ZmMyComputerTabViewPage.SHOW_NO_ATTACHMENTS; i++) {
571 		this._addAttachmentField();
572 	}
573 	delete i;
574 };
575 
576 ZmMyComputerTabViewPage.prototype._focusAttEl =
577 function() {
578 	var el = document.getElementById(this._focusElId);
579 	if (el) el.focus();
580 };
581 
582 // Utilities
583 ZmMyComputerTabViewPage.prototype._cleanTable =
584 function(table) {
585 	if (!table || !table.rows) { return; }
586 	while (table.rows.length > 0) {
587 		table.deleteRow(0);
588 	}
589 };
590 
591 ZmMyComputerTabViewPage.prototype._handleKeys =
592 function(ev) {
593 	var key = DwtKeyEvent.getCharCode(ev);
594 	return !DwtKeyEvent.IS_RETURN[key];
595 };
596 
597 ZmMyComputerTabViewPage.prototype._validateFileSize =
598 function(){
599 
600     var atts = document.getElementsByName(ZmMyComputerTabViewPage.UPLOAD_FIELD_NAME);
601     var file, size;
602 	for (var i = 0; i < atts.length; i++){
603         file = atts[i].files;
604         if(!file || file.length == 0) continue;
605         for(var j=0; j<file.length;j++){
606             var f = file[j];
607             size = f.size || f.fileSize /*Safari*/;
608             if ((-1 /* means unlimited */ != appCtxt.get(ZmSetting.MESSAGE_SIZE_LIMIT)) &&
609                 (size > appCtxt.get(ZmSetting.MESSAGE_SIZE_LIMIT))) {
610                 return false;
611             }
612         }
613     }
614 	return true;
615 };
616 
617 ZmMyComputerTabViewPage.prototype.validate =
618 function(){
619     var status, errorMsg;
620     if(AjxEnv.supportsHTML5File){
621         status = this._validateFileSize();
622         errorMsg = AjxMessageFormat.format(ZmMsg.attachmentSizeError, AjxUtil.formatSize(appCtxt.get(ZmSetting.MESSAGE_SIZE_LIMIT)));
623     }else{
624         status = true;
625     }
626 
627     return {status: status, error:errorMsg};
628 };
629