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  * This file contains the briefcase application class.
 27  */
 28 
 29 /**
 30  * Creates and initializes the briefcase application.
 31  * @class
 32  * The briefcase application manages the creation and display of briefcase items.
 33  * 
 34  * @param	{DwtControl}	container		the container
 35  * @param	{ZmController}	parentController	the parent controller
 36  * 
 37  * @author Conrad Damon
 38  * 
 39  * @extends		ZmApp
 40  */
 41 ZmBriefcaseApp = function(container, parentController) {
 42 	ZmApp.call(this, ZmApp.BRIEFCASE, container, parentController);
 43 };
 44 
 45 ZmBriefcaseApp.prototype = new ZmApp;
 46 ZmBriefcaseApp.prototype.constructor = ZmBriefcaseApp;
 47 
 48 ZmBriefcaseApp.prototype.isZmBriefcaseApp = true;
 49 ZmBriefcaseApp.prototype.toString = function() { return "ZmBriefcaseApp"; };
 50 
 51 
 52 // Constants
 53 
 54 // Organizer and item-related constants
 55 ZmEvent.S_BRIEFCASE_ITEM			= ZmId.ITEM_BRIEFCASE;
 56 ZmItem.BRIEFCASE_ITEM				= ZmEvent.S_BRIEFCASE_ITEM;
 57 ZmItem.BRIEFCASE					= ZmItem.BRIEFCASE_ITEM;	// back-compatibility
 58 
 59 ZmEvent.S_BRIEFCASE_REVISION_ITEM			= ZmId.ITEM_BRIEFCASE_REV;
 60 ZmItem.BRIEFCASE_REVISION_ITEM				= ZmEvent.S_BRIEFCASE_REVISION_ITEM;
 61 ZmItem.BRIEFCASE_REVISION					= ZmItem.BRIEFCASE_REVISION_ITEM;	// back-compatibility
 62 
 63 /**
 64  * Defines the "briefcase" organizer.
 65  */
 66 ZmOrganizer.BRIEFCASE				= ZmId.ORG_BRIEFCASE;
 67 
 68 // App-related constants
 69 /**
 70  * Defines the "briefcase" application.
 71  */
 72 ZmApp.BRIEFCASE						= ZmId.APP_BRIEFCASE;
 73 ZmApp.CLASS[ZmApp.BRIEFCASE]		= "ZmBriefcaseApp";
 74 ZmApp.SETTING[ZmApp.BRIEFCASE]		= ZmSetting.BRIEFCASE_ENABLED;
 75 ZmApp.LOAD_SORT[ZmApp.BRIEFCASE]	= 65;
 76 ZmApp.QS_ARG[ZmApp.BRIEFCASE]		= "briefcase";
 77 ZmApp.BUTTON_ID[ZmApp.BRIEFCASE]	= ZmId.BRIEFCASE_APP;
 78 
 79 
 80 ZmBriefcaseApp.prototype._defineAPI =
 81 function() {
 82 	AjxDispatcher.setPackageLoadFunction("BriefcaseCore", new AjxCallback(this, this._postLoadCore));
 83 	AjxDispatcher.setPackageLoadFunction("Briefcase", new AjxCallback(this, this._postLoad, ZmOrganizer.BRIEFCASE));
 84 	AjxDispatcher.registerMethod("GetBriefcaseController", ["BriefcaseCore", "Briefcase"], new AjxCallback(this, this.getBriefcaseController));
 85 };
 86 
 87 ZmBriefcaseApp.prototype._registerOperations =
 88 function() {
 89 	ZmOperation.registerOp(ZmId.OP_NEW_BRIEFCASE, {textKey:"newBriefcase", image:"NewFolder", tooltipKey:"newBriefcaseTooltip", shortcut:ZmKeyMap.NEW_BRIEFCASE});
 90 	ZmOperation.registerOp(ZmId.OP_NEW_FILE, {textKey:"uploadNewFile", tooltipKey:"uploadNewFile", textPrecedence:70, showImageInToolbar:true, showTextInToolbar:true});
 91 	ZmOperation.registerOp(ZmId.OP_NEW_DOC, {textKey:"newDocument", tooltipKey:"newDocument", image:"NewDoc", shortcut:ZmKeyMap.NEW_DOC, textPrecedence:12});
 92 	ZmOperation.registerOp(ZmId.OP_SHARE_BRIEFCASE, {textKey:"shareFolder", image:"SharedMailFolder"}, ZmSetting.SHARING_ENABLED);
 93 	ZmOperation.registerOp(ZmId.OP_OPEN_FILE, {textKey:"openFile", tooltipKey:"openFileTooltip", image:"NewDoc"});
 94 	ZmOperation.registerOp(ZmId.OP_SAVE_FILE, {textKey:"saveFile", tooltipKey:"saveFileTooltip", image:"DownArrow"});
 95 	ZmOperation.registerOp(ZmId.OP_VIEW_FILE_AS_HTML, {textKey:"viewFileAsHtml", tooltipKey:"viewAsHtml", image:"HtmlDoc"});
 96 	ZmOperation.registerOp(ZmId.OP_SEND_FILE, {textKey:"sendLink", tooltipKey:"sendLink", image:"Send"});
 97 	ZmOperation.registerOp(ZmId.OP_SEND_FILE_AS_ATT, {textKey:"sendAsAttachment", tooltipKey:"sendAsAttachment", image:"Attachment"});
 98 	ZmOperation.registerOp(ZmId.OP_SEND_FILE_MENU, {textKey:"send", image:"Send", textPrecedence:75});
 99     ZmOperation.registerOp(ZmId.OP_EDIT_FILE, {textKey: "edit", image:"Edit"});
100     ZmOperation.registerOp(ZmId.OP_RENAME_FILE, {textKey: "rename", image:"FileRename"});
101     ZmOperation.registerOp(ZmId.OP_CHECKIN, {textKey: "checkInFile", image:"Checkin"});
102     ZmOperation.registerOp(ZmId.OP_CHECKOUT, {textKey: "checkOutFile", image:"Checkout"});
103     ZmOperation.registerOp(ZmId.OP_DISCARD_CHECKOUT, {textKey: "checkOutFileDiscard", image:"DiscardCheckout"});    
104     ZmOperation.registerOp(ZmId.OP_VERSION_HISTORY, {textKey: "versionHistory", image:"VersionHistory"});
105     ZmOperation.registerOp(ZmId.OP_RESTORE_VERSION, {textKey: "restoreCurrentVersion", image:"RestoreVersion"});
106     ZmOperation.registerOp(ZmId.OP_DELETE_VERSION, {textKey: "deleteVersion", image:"Delete"});
107 };
108 
109 ZmBriefcaseApp.prototype._registerSettings =
110 function(settings) {
111 	settings = settings || appCtxt.getSettings();
112 	settings.registerSetting("DOCS_ENABLED",		{name:"zimbraFeatureBriefcaseDocsEnabled", type:ZmSetting.T_COS, dataType: ZmSetting.D_BOOLEAN, defaultValue:true});
113     settings.registerSetting("PREVIEW_ENABLED",		{ type:ZmSetting.T_COS, dataType: ZmSetting.D_BOOLEAN, defaultValue:false});
114 	settings.registerSetting("READING_PANE_LOCATION_BRIEFCASE",		{name:"zimbraPrefBriefcaseReadingPaneLocation", type:ZmSetting.T_PREF, dataType:ZmSetting.D_STRING, defaultValue:ZmSetting.RP_BOTTOM, isImplicit:true});
115 };
116 
117 ZmBriefcaseApp.prototype._registerItems =
118 function() {
119 	ZmItem.registerItem(ZmItem.BRIEFCASE_ITEM,
120 						{app:			ZmApp.BRIEFCASE,
121 						 nameKey:		"file",
122 						 icon:			"GenericDoc",
123 						 soapCmd:		"ItemAction",
124 						 itemClass:		"ZmBriefcaseItem",
125 						 node:			"doc",
126 						 organizer:		ZmOrganizer.BRIEFCASE,
127 						 dropTargets:	[ZmOrganizer.TAG, ZmOrganizer.BRIEFCASE],
128 						 searchType:	"document",
129 						 resultsList:
130 		AjxCallback.simpleClosure(function(search) {
131             AjxDispatcher.require("BriefcaseCore");
132 			return new ZmList(ZmItem.BRIEFCASE_ITEM, search);
133 		}, this)
134 						});
135 };
136 
137 ZmBriefcaseApp.prototype._registerOrganizers =
138 function() {
139 	ZmOrganizer.registerOrg(ZmOrganizer.BRIEFCASE,
140 							{app            : ZmApp.BRIEFCASE,
141 							 nameKey        : "folder",
142 							 defaultFolder  : ZmOrganizer.ID_BRIEFCASE,
143 							 soapCmd        : "FolderAction",
144 							 firstUserId    : 256,
145 							 orgClass       : "ZmBriefcase",
146 							 orgPackage     : "BriefcaseCore",
147 							 treeController : "ZmBriefcaseTreeController",
148 							 labelKey       : "briefcaseFolders",
149 							 itemsKey       : "files",
150 							 treeType       : ZmOrganizer.FOLDER,
151 							 views          : ["document"],
152 							 folderKey      : "briefcase",                                                      
153 							 mountKey       : "mountFolder",
154 							 createFunc     : "ZmOrganizer.create",
155 							 compareFunc    : "ZmFolder.sortCompareNonMail",
156 							 deferrable     : true,
157 							 newOp			: ZmOperation.NEW_BRIEFCASE,
158 							 displayOrder	: 100,
159 							 hasColor       : true,
160 							 defaultColor	: ZmOrganizer.C_NONE,
161 							 childWindow    : true
162 							});
163 };
164 
165 ZmBriefcaseApp.prototype._setupSearchToolbar =
166 function() {
167 	//TODO:search for page alone
168 	ZmSearchToolBar.addMenuItem(ZmItem.BRIEFCASE_ITEM,
169 								{msgKey:		"files",
170 								 tooltipKey:	"searchForFiles",
171 								 icon:			"Doc",
172 								 shareIcon:		null, // the following doesn't work now, so keep the regular icon. doesn't really matter in my opinion --> "SharedBriefcase",
173 								 setting:		ZmSetting.BRIEFCASE_ENABLED,
174 								 id:			ZmId.getMenuItemId(ZmId.SEARCH, ZmId.ITEM_BRIEFCASE),
175 								 disableOffline:true
176 								});
177 };
178 
179 ZmBriefcaseApp.prototype._registerApp =
180 function() {
181 	var newItemOps = {};
182 	newItemOps[ZmOperation.NEW_DOC]			= "document";
183 
184 	var newOrgOps = {};
185 	newOrgOps[ZmOperation.NEW_BRIEFCASE]	 = "briefcase";
186 
187 	var actionCodes = {};
188 	actionCodes[ZmKeyMap.NEW_FILE]			= ZmOperation.NEW_FILE;
189 	actionCodes[ZmKeyMap.NEW_BRIEFCASE]		= ZmOperation.NEW_BRIEFCASE;
190 	actionCodes[ZmKeyMap.NEW_DOC]			= ZmOperation.NEW_DOC;
191 
192 	ZmApp.registerApp(ZmApp.BRIEFCASE,
193 					 {mainPkg:				"Briefcase",
194 					  nameKey:				"briefcase",
195 					  icon:					"Briefcase",
196 					  textPrecedence:		30,
197 					  chooserTooltipKey:	"gotoBriefcase",
198 					  defaultSearch:		ZmItem.BRIEFCASE_ITEM,
199 					  organizer:			ZmOrganizer.BRIEFCASE,
200 					  overviewTrees:		[ZmOrganizer.BRIEFCASE, ZmOrganizer.TAG],
201 					  searchTypes:			[ZmItem.BRIEFCASE_ITEM],
202 					  newItemOps:			newItemOps,
203 					  newOrgOps:			newOrgOps,
204 					  actionCodes:			actionCodes,
205 					  gotoActionCode:		ZmKeyMap.GOTO_BRIEFCASE,
206 					  newActionCode:		ZmKeyMap.NEW_DOC,
207 					  chooserSort:			70,
208 					  defaultSort:			60,
209 					  searchResultsTab:		true
210 					  });
211 };
212 
213 // App API
214 
215 /**
216  * Checks for the creation of a briefcase or a mount point to one, or of an item
217  *
218  * @param {Hash}	creates		a hash of create notifications
219  * 
220  * @private
221  */
222 ZmBriefcaseApp.prototype.createNotify =
223 function(creates, force) {
224 
225 	if (!creates["folder"] && !creates["doc"] && !creates["link"]) { return; }
226 	if (!force && !this._noDefer && this._deferNotifications("create", creates)) { return; }
227 
228 	for (var name in creates) {
229 		var clist = creates[name];
230 		for (var i = 0; (clist != null) && i < clist.length; i++) {
231 			var create = clist[i];
232 			if (appCtxt.cacheGet(create.id)) { continue; }
233 
234 			if (name == "folder") {
235 				this._handleCreateFolder(create, ZmOrganizer.BRIEFCASE);
236 			} else if (name == "link") {
237 				this._handleCreateLink(create, ZmOrganizer.BRIEFCASE);
238 			} else if (name == "doc") {
239 				var bc = AjxDispatcher.run("GetBriefcaseController");
240                 bc.handleCreateNotify(create);
241 			}
242 		}
243 	}
244 };
245 
246 ZmBriefcaseApp.prototype.modifyNotify =
247 function(modifies, force) {
248     if (!modifies["doc"]) { return; }
249 	var bc = AjxDispatcher.run("GetBriefcaseController");
250     bc.handleModifyNotify(modifies);
251 };
252 
253 ZmBriefcaseApp.prototype.handleOp =
254 function(op) {
255 
256 	switch (op) {
257 		case ZmOperation.NEW_FILE: {
258 			var loadCallback = new AjxCallback(this, this._handleNewItem);
259 			AjxDispatcher.require(["BriefcaseCore", "Briefcase"], false, loadCallback, null, true);
260 			break;
261 		}
262 		case ZmOperation.NEW_BRIEFCASE: {
263 			var loadCallback = new AjxCallback(this, this._handleLoadNewBriefcase);
264 			AjxDispatcher.require(["BriefcaseCore", "Briefcase"], false, loadCallback, null, true);
265 			break;
266 		}
267 
268 		case ZmOperation.NEW_DOC: {
269 			var newDocCallback = new AjxCallback(this, this.newDoc, [ZmMimeTable.APP_ZIMBRA_DOC]);
270 			AjxDispatcher.require(["BriefcaseCore", "Briefcase"], true, newDocCallback, null);
271 			break;
272 		}
273 	}
274 };
275 
276 /**
277  * Creates a new document.
278  * 
279  * @param	{String}	contentType		the content type
280  * @param	{String}	new				the document name
281  * @param	{String}	winName			the name of the popup doc window
282  */
283 ZmBriefcaseApp.prototype.newDoc =
284 function(contentType, name, winName) {
285 	var overviewController = appCtxt.getOverviewController();
286 	var treeController = overviewController.getTreeController(ZmOrganizer.BRIEFCASE);
287 	var folderId = ZmOrganizer.ID_BRIEFCASE;
288 	if (treeController) {
289 		var treeView = treeController.getTreeView(this.getOverviewId());
290 		var briefcase = treeView ? treeView.getSelected() : null;
291 		folderId = briefcase ? briefcase.id : ZmOrganizer.ID_BRIEFCASE;
292 	}
293 
294     if (AjxDispatcher.run("GetBriefcaseController").chkFolderPermission(folderId)) {
295         if (contentType == ZmMimeTable.APP_ZIMBRA_DOC) {
296             var win = appCtxt.getNewWindow(false, null, null, winName);
297 	        if (win) {
298 	            win.command = "documentEdit";
299 	            win.params = { name: name, folderId: folderId };
300 	        }
301         }
302     }
303 };
304 
305 /**
306  * Gets the popup doc window features.
307  * 
308  * @return	{String}	 the window features
309  */
310 ZmBriefcaseApp.getDocWindowFeatures =
311 function() {
312     return [
313         "width=",(screen.width || 640),",",
314         "height=",(screen.height || 480),",",
315         "scrollbars=yes,",
316         "resizable=yes"
317     ].join("");
318 };
319 
320 ZmBriefcaseApp.prototype._handleNewItem =
321 function() {
322 	appCtxt.getAppViewMgr().popView(true, ZmId.VIEW_LOADING);	// pop "Loading..." page
323 	AjxDispatcher.run("GetBriefcaseController").__popupUploadDialog(ZmMsg.uploadFileToBriefcase);
324 };
325 
326 ZmBriefcaseApp.prototype._handleLoadNewBriefcase =
327 function() {
328 	appCtxt.getAppViewMgr().popView(true, ZmId.VIEW_LOADING); // pop "Loading..." page
329 
330 	if (!this._newBriefcaseCb) {
331 		this._newBriefcaseCb = this._newBriefcaseCallback.bind(this);
332 	}
333 	ZmController.showDialog(appCtxt.getNewBriefcaseDialog(), this._newBriefcaseCb);
334 };
335 
336 
337 // Public methods
338 
339 ZmBriefcaseApp.prototype.launch =
340 function(params, callback) {
341 	this._setLaunchTime(this.toString(), new Date());
342 	var loadCallback = this._handleLoadLaunch.bind(this, callback);
343 	AjxDispatcher.require(["BriefcaseCore","Briefcase"], true, loadCallback, null, true);
344 
345     // In case of external sharing we replace drop down button options with New Document button
346     if (appCtxt.isExternalAccount()) {
347         var newButton = appCtxt.getAppController().getNewButton();
348         newButton.removePullDownMenuOptions();
349     }
350 };
351 
352 ZmBriefcaseApp.prototype._handleLoadLaunch =
353 function(callback) {
354 	this.search();
355 	if (callback) { callback.run(); }
356 };
357 
358 ZmBriefcaseApp.prototype.getNewButtonProps =
359 function() {
360 	return {
361 		text:		ZmMsg.newDocument,
362 		tooltip:	ZmMsg.newDocument,
363 		icon:		"NewDoc",
364 		iconDis:	"NewDocDis",
365 		defaultId:	ZmOperation.NEW_DOC,
366         disabled:	!this.containsWritableFolder()
367 	};
368 };
369 
370 /**
371  * Performs a search.
372  * 
373  * @param {Hash}	params			a hash of parameters
374  * @param {String}	params.folderId			the ID of briefcase folder to search in
375  * @param {String}	[params.query]				the query to send (overrides folderId)
376  * @param {AjxCallback}	[params.callback]			the callback
377  * @param {String}	[params.accountName]		the account name
378  * @param {Boolean}	[params.noRender]			if <code>true</code>, do not display results
379  */
380 ZmBriefcaseApp.prototype.search =
381 function(params) {
382 
383 	params = params || {};
384     var folderId = params.folderId || (appCtxt.isExternalAccount() ? this.getDefaultFolderId() : ZmOrganizer.ID_BRIEFCASE);
385 	var folder = appCtxt.getById(folderId);
386 
387 	var searchParams = {
388 		query:			params.query || folder.createQuery(),
389 		types:			[ZmItem.BRIEFCASE_ITEM],
390 		limit:			this.getLimit(),
391 		searchFor:		ZmId.ITEM_BRIEFCASE,
392 		callback:		params.callback,
393 		accountName:	params.accountName,
394 		noRender:		params.noRender
395 	};
396 	var sc = appCtxt.getSearchController();
397 	sc.searchAllAccounts = false;
398 	sc.search(searchParams);
399 };
400 
401 /**
402  * Shows the search results.
403  * 
404  * @param	{Object}	results	the results
405  * @param	{AjxCallback}	callback		the callback
406  */
407 ZmBriefcaseApp.prototype.showSearchResults =
408 function(results, callback, searchResultsController) {
409 	var loadCallback = this._handleLoadShowSearchResults.bind(this, results, callback, searchResultsController);
410 	AjxDispatcher.require(["BriefcaseCore", "Briefcase"], false, loadCallback, null, true);
411 };
412 
413 ZmBriefcaseApp.prototype._handleLoadShowSearchResults =
414 function(results, callback, searchResultsController) {
415 	var sessionId = searchResultsController ? searchResultsController.getCurrentViewId() : ZmApp.MAIN_SESSION;
416 	var controller = AjxDispatcher.run("GetBriefcaseController", sessionId, searchResultsController);
417 	controller.show(results);
418 	this._setLoadedTime(this.toString(), new Date());
419 	if (callback) {
420 		callback.run(controller);
421 	}
422 };
423 
424 ZmBriefcaseApp.prototype.setActive =
425 function(active) {
426 };
427 
428 // return enough for us to get a scroll bar since we are pageless
429 /**
430  * Gets the limit for the search triggered by the application launch or an overview click.
431  * 
432  * @param	{Boolean}	offset	if <code>true</code> app has offset
433  * @return	{int}	the limit
434  */
435 ZmBriefcaseApp.prototype.getLimit =
436 function(offset) {
437 	var limit = appCtxt.get(ZmSetting.PAGE_SIZE);
438 	return offset ? limit : 2 * limit;
439 };
440 
441 ZmBriefcaseApp.prototype._newBriefcaseCallback =
442 function(parent, name, color) {
443 	appCtxt.getNewBriefcaseDialog().popdown();
444 	var oc = appCtxt.getOverviewController();
445 	oc.getTreeController(ZmOrganizer.BRIEFCASE)._doCreate(parent, name, color);
446 };
447 
448 ZmBriefcaseApp.prototype.getBriefcaseController =
449 function(sessionId, searchResultsController) {
450 	return this.getSessionController({controllerClass:			"ZmBriefcaseController",
451 									  sessionId:				sessionId || ZmApp.MAIN_SESSION,
452 									  searchResultsController:	searchResultsController});
453 };
454 
455 ZmBriefcaseApp.prototype.createFromAttachment =
456 function(msgId, partId,name) {
457 	var loadCallback = new AjxCallback(this, this._handleCreateFromAttachment, [msgId, partId, name]);
458 	AjxDispatcher.require(["BriefcaseCore","Briefcase"], false, loadCallback);
459 };
460 
461 ZmBriefcaseApp.prototype._handleCreateFromAttachment =
462 function(msgId, partId, name) {
463 	if (this._deferredFolders.length != 0) {
464 		this._createDeferredFolders(ZmApp.BRIEFCASE);
465 	}
466     AjxDispatcher.run("GetBriefcaseController").createFromAttachment(msgId, partId, name);
467 };
468 
469 
470 //Make sure we load BriefcaseCore before calling _creatDeferredFolders() from child window.
471 ZmBriefcaseApp.prototype._createDeferredFolders =
472 function(type) {
473 	AjxPackage.require("BriefcaseCore");
474 	ZmApp.prototype._createDeferredFolders.call(this, type);
475 };
476 
477 
478 
479 // --- Briefcase External DnD upload initiation
480 
481 ZmBriefcaseApp.prototype.initExternalDndUpload = function(files, node, isInline, selectionCallback, folderId) {
482 	var name = "";
483 
484 	if (!AjxEnv.supportsHTML5File) {
485 		// IE, FF 3.5 and lower - use the File browser
486 		if (selectionCallback) {
487 			selectionCallback.run();
488 		}
489 		return;
490 	}
491 
492 	if (!files) {
493 		files = node.files;
494 	}
495 
496 	var size = 0;
497 	if (files) {
498 		var file;
499 		var docFiles = [];
500 		var errors   = {};
501 		var aCtxt    = ZmAppCtxt.handleWindowOpener();
502 		var maxSize  = aCtxt.get(ZmSetting.DOCUMENT_SIZE_LIMIT);
503 		var briefcaseController = AjxDispatcher.run("GetBriefcaseController");
504 
505 		if (!folderId) {
506 			if (briefcaseController) {
507 				folderId = briefcaseController.getFolderId();
508 			}
509 			if(!folderId || folderId == ZmOrganizer.ID_TRASH) {
510 				folderId = ZmOrganizer.ID_BRIEFCASE;
511 			}
512 		}
513 
514 		if(this.chkFolderPermission(folderId)){
515 			var cFolder = appCtxt.getById(folderId);
516 			var uploadManager = appCtxt.getZmUploadManager();
517 
518 			var errors = [];
519 			for (var i = 0; i < files.length; i++){
520 				var newError = uploadManager.getErrors(files[i], maxSize);
521 				if (newError) {
522 					errors.push(newError);
523 				}
524 			}
525 			if (errors.length > 0) {
526 				var errorMsg = uploadManager.createUploadErrorMsg(errors, maxSize, "<br>");
527 				var msgDlg = appCtxt.getMsgDialog();
528 				msgDlg.setMessage(errorMsg, DwtMessageDialog.WARNING_STYLE);
529 				msgDlg.popup();
530 			} else {
531 				var params = {
532 					attachment:              false,
533 					uploadFolder:            cFolder,
534 					files:                   files,
535 					notes:                   "",
536 					allResponses:            null,
537 					start:                   0,
538 					curView:                 null,
539 					preAllCallback:          null,
540 					initOneUploadCallback:   null,
541 					progressCallback:        null,
542 					errorCallback:           null,
543 					completeOneCallback:     null,
544 					completeAllCallback:     this.uploadSaveDocs.bind(this),
545 					completeDocSaveCallback: this._finishUpload.bind(this, null)
546 				}
547 				uploadManager.upload(params);
548 			}
549 		}
550 	}
551 };
552 
553 ZmBriefcaseApp.prototype.chkFolderPermission = function(folderId){
554 	var briefcase = appCtxt.getById(folderId);
555 	if(briefcase.isRemote() && briefcase.isReadOnly()){
556 		var dialog = appCtxt.getMsgDialog();
557 		dialog.setMessage(ZmMsg.errorPermissionCreate, DwtMessageDialog.WARNING_STYLE);
558 		dialog.popup();
559 		return false;
560 	}
561 	return true;
562 };
563 
564 // --- Briefcase Upload Completion - SaveDocuments and Conflict resolution ------
565 
566 /**
567  * uploadSaveDocs performs SaveDocument calls, creating a document with an associated uploadId.  If the file
568  * already exists, conflict resolution is performed.
569  *
570  * @param	{object}	params		params to customize the upload flow:
571  *      uploadFolder                Folder to save associated document into
572  *      files:                      raw File object from the external HTML5 drag and drop
573  *      notes:                      Notes associated with each of the files being added
574  *      allResponses:               All the server responses.  Contains the uploadId (guid) for a file
575  *      errorCallback:              Run upon an error
576  *      conflictAction			    If specified, the action used to resolve a file conflict
577  *      preResolveConflictCallback: Standard processing (SaveDocument), Run prior to conflict resolution
578  *      completeDocSaveCallback:    Standard processing (SaveDocument), Run when all documents have been saved
579  *
580  */
581 ZmBriefcaseApp.prototype.uploadSaveDocs = function(allResponses, params, status, guids) {
582 	if (status != AjxPost.SC_OK) {
583 		var errorMessage = appCtxt.getAppController().createErrorMessage(ZmItem.BRIEFCASE, status);
584 		this._popupErrorDialog(errorMessage, params.errorCallback);
585 	} else {
586 		var docFiles;
587 		if (allResponses) {
588 			// External DnD files
589 		    docFiles = [];
590 			var files    = params.files;
591 			if (allResponses.length === files.length) {
592 				for (var i = 0; i < files.length; i++){
593 					var file = files[i];
594 					var response = allResponses[i];
595 					var aid = (response && response.aid);
596 					docFiles.push(
597 						{name:     file.name,
598 						 fullname: file.name,
599 						 notes:    params.notes,
600 						 version:  file.version,
601 						 id:	   file.id,
602 						 guid:     aid,
603 						 preventDuplicate: file.preventDuplicate});
604 				}
605 				params.docFiles = docFiles;
606 			}
607 		} else {
608 			// AjxPost callback, providing the guids separately
609 			docFiles = params.docFiles;
610 			if (guids) {
611 				guids = guids.split(",");
612 				for (var i = 0; i < docFiles.length; i++) {
613 					DBG.println("guids[" + i + "]: " + guids[i] + ", files[" + i + "]: " + docFiles[i]);
614 					docFiles[i].guid = guids[i];
615 				}
616 			}
617 		}
618 		if (params.uploadFolder) {
619 			this._uploadSaveDocs2(params);
620 		} else {
621 			this._completeUpload(params);
622 		}
623 	}
624 };
625 
626 ZmBriefcaseApp.prototype._popupErrorDialog = function(message, errorCallback) {
627 	if (errorCallback) {
628 		errorCallback.run();
629 	}
630 	var dialog = appCtxt.getMsgDialog();
631 	dialog.setMessage(message, DwtMessageDialog.CRITICAL_STYLE);
632 	dialog.popup();
633 };
634 
635 ZmBriefcaseApp.prototype._uploadSaveDocs2 = function(params) {
636 
637 	// create document wrappers
638 	var request = [];
639 	var foundOne = false;
640 	var docFiles = params.docFiles;
641 	for (var i = 0; i < docFiles.length; i++) {
642 		var file = docFiles[i];
643 		if (file.done) {
644 			continue;
645 		}
646 		foundOne = true;
647 
648 		var SaveDocumentRequest = { _jsns: "urn:zimbraMail", requestId: i, doc: {}}
649 		var doc = SaveDocumentRequest.doc;
650 		if (file.id) {
651 			doc.id = file.id;
652 			doc.ver = file.version;
653 		} else {
654 			doc.l = params.uploadFolder.id;
655 		}
656 		if (file.notes) {
657 			doc.desc = file.notes;
658 		}
659 		doc.upload = {
660 			id: file.guid
661 		}
662 		request.push(SaveDocumentRequest);
663 	}
664 
665 	if (foundOne) {
666 		var json = {
667 			BatchRequest: {
668 				_jsns: "urn:zimbra",
669 				onerror: "continue",
670 				SaveDocumentRequest: ( (request.length == 1) ? request[0] : request )
671 			}
672 		};
673 		var callback = this._uploadSaveDocsResponse.bind(this, params);
674 		var saveDocParams = {
675 			jsonObj:  json,
676 			asyncMode:true,
677 			callback: callback
678 		};
679 		var appController = appCtxt.getAppController();
680 		appController.sendRequest(saveDocParams);
681 	}
682 	else {
683 		// This calls the callback of the client - e.g. ZmHtmlEditor.prototype._imageUploaded since
684 		// _uploadSaveDocsResponse is not called in this case, we still need the client callback since the
685 		// user chose the "old" version of the image
686 		this._completeUpload(params);
687 	}
688 };
689 
690 ZmBriefcaseApp.prototype._uploadSaveDocsResponse = function(params, response) {
691 	var resp = response && response._data && response._data.BatchResponse;
692 	var docFiles = params.docFiles;
693 
694 	// mark successful uploads
695 	if (resp && resp.SaveDocumentResponse) {
696 		for (var i = 0; i < resp.SaveDocumentResponse.length; i++) {
697 			var saveDocResp = resp.SaveDocumentResponse[i];
698 			docFiles[saveDocResp.requestId].done    = true;
699 			docFiles[saveDocResp.requestId].name    = saveDocResp.doc[0].name;
700 			docFiles[saveDocResp.requestId].id      = saveDocResp.doc[0].id;
701 			docFiles[saveDocResp.requestId].ver     = saveDocResp.doc[0].ver;
702 			docFiles[saveDocResp.requestId].version = saveDocResp.doc[0].ver;
703 		}
704 	}
705 
706 	// check for conflicts
707 	var mailboxQuotaExceeded = false;
708 	var alreadyExists = false;
709 	var uploadRejected = false;
710 	var isItemLocked = false;
711 	var code = 0;
712 	var conflicts = [];
713 	if (resp && resp.Fault) {
714 		var errors = [];
715 		var uploadRejected = false, rejectedFile = "Unknown", rejectedReason = "Unknown";
716 		for (var i = 0; i < resp.Fault.length; i++) {
717 			var fault = resp.Fault[i];
718 			var error = fault.Detail.Error;
719 			code = error.Code;
720 			var attrs = error.a;
721 			isItemLocked = (code == ZmCsfeException.LOCKED);
722 			var file = docFiles[fault.requestId];
723 			if ((code == ZmCsfeException.MAIL_ALREADY_EXISTS) && file.preventDuplicate) {
724 				alreadyExists = true;
725 			} else if (code == ZmCsfeException.MAIL_ALREADY_EXISTS ||
726 				code == ZmCsfeException.MODIFY_CONFLICT) {
727 				for (var p in attrs) {
728 					var attr = attrs[p];
729 					switch (attr.n) {
730 						case "itemId" : { file.id      = attr._content; break }
731 						case "id":      { file.id      = attr._content; break; }
732 						case "ver":     { file.version = attr._content; break; }
733 						case "name":    { file.name    = attr._content; break; }
734 					}
735 				}
736 				file.version = file.version || 1;
737 				conflicts.push(file);
738 			}else {
739 				DBG.println("Unknown error occurred: " + code);
740 				if (code == ZmCsfeException.MAIL_QUOTA_EXCEEDED) {
741 					mailboxQuotaExceeded = true;
742 				}  else if (code === ZmCsfeException.UPLOAD_REJECTED) {
743 					uploadRejected = true;
744 					for (var p in attrs) {
745 						var attr = attrs[p];
746 						switch (attr.n) {
747 							case "reason" : rejectedReason = attr._content; break;
748 							case "name":    rejectedFile   = attr._content; break;
749 						}
750 					}
751 				}
752 
753 				errors[fault.requestId] = fault;
754 			}
755 		}
756 	}
757 
758 	// dismiss dialog/enable the upload button
759 	if (params.preResolveConflictCallback) {
760 		params.preResolveConflictCallback.run();
761 	}
762 
763 	// TODO: What to do about other errors?
764 	// TODO: This should handle reporting several errors at once
765 	if (mailboxQuotaExceeded) {
766 		this._popupErrorDialog(ZmMsg.errorQuotaExceeded, params.errorCallback);
767 		return;
768 	} else 	if (alreadyExists) {
769 		this._popupErrorDialog(AjxMessageFormat.format(ZmMsg.itemWithFileNameExits, file.name), params.errorCallback);
770 		return;
771 	} else if (isItemLocked) {
772 		this._popupErrorDialog(ZmMsg.errorItemLocked, params.errorCallback);
773 		return;
774 	} else if (uploadRejected) {
775 		var rejectedMsg = AjxMessageFormat.format(ZmMsg.uploadRejectedError, [ rejectedFile, rejectedReason ] );
776 		this._popupErrorDialog(rejectedMsg, params.errorCallback);
777 		return;
778 	}
779 	else if (code == ZmCsfeException.SVC_PERM_DENIED) {
780 		this._popupErrorDialog(ZmMsg.errorPermissionDenied, params.errorCallback);
781 		return;
782 	}
783 
784 	// resolve conflicts
785 	var conflictCount = conflicts.length;
786 
787 	var action = params.conflictAction || ZmBriefcaseApp.ACTION_KEEP_MINE;
788 	if (conflictCount > 0 && action == ZmBriefcaseApp.ACTION_ASK) {
789 		var dialog = appCtxt.getUploadConflictDialog();
790 		dialog.popup(params.uploadFolder, conflicts, this._uploadSaveDocs2.bind(this, params));
791 	} else if (conflictCount > 0 && action == ZmBriefcaseApp.ACTION_KEEP_MINE) {
792 		if (params.conflictAction) {
793 			this._shieldSaveDocs(params);
794 		} else {
795 			this._uploadSaveDocs2(params);
796 		}
797 	} else {
798 		this._completeUpload(params);
799 	}
800 };
801 
802 ZmBriefcaseApp.prototype._shieldSaveDocs = function(params) {
803 	var dlg = appCtxt.getYesNoMsgDialog();
804 	dlg.reset();
805 	dlg.setButtonListener(DwtDialog.YES_BUTTON, new AjxListener(this, this._shieldSaveDocsYesCallback, [dlg, params]));
806 	dlg.setMessage(ZmMsg.uploadConflictShield, DwtMessageDialog.WARNING_STYLE, ZmMsg.uploadConflict);
807 	dlg.popup();
808 };
809 
810 ZmBriefcaseApp.prototype._shieldSaveDocsYesCallback = function(dlg, params) {
811 	this._uploadSaveDocs2(params);
812 	dlg.popdown();
813 };
814 
815 ZmBriefcaseApp.prototype._completeUpload = function(params) {
816 	if (params.completeDocSaveCallback) {
817 		params.completeDocSaveCallback.run(params.docFiles, params.uploadFolder);
818 	}
819 };
820 
821 ZmBriefcaseApp.prototype._finishUpload = function(finishCallback, docFiles, uploadFolder) {
822 	var filenames = [];
823 	for (var i in docFiles) {
824 		var name = docFiles[i].name;
825 		filenames.push(name);
826 	}
827 	this._handlePostUpload(uploadFolder, filenames, docFiles);
828 
829 	if (finishCallback) {
830 		finishCallback(docFiles);
831 	}
832 };
833 
834 ZmBriefcaseApp.prototype._handlePostUpload = function(folder, filenames, files) {
835 	var msg = ZmMsg.successfullyUploaded;
836 	if(files.length > 1){
837 		msg = AjxMessageFormat.format(ZmMsg.successfullyUploadedFiles, files.length);
838 	}
839 	appCtxt.setStatusMsg(msg, ZmStatusView.LEVEL_INFO);
840 	// Remove the previous selection(s)
841 	var briefcaseController = AjxDispatcher.run("GetBriefcaseController");
842 	briefcaseController.resetSelection();
843 };
844 
845 
846 
847 ZmBriefcaseApp.ACTION_KEEP_MINE = "mine";
848 ZmBriefcaseApp.ACTION_KEEP_THEIRS = "theirs";
849 ZmBriefcaseApp.ACTION_ASK = "ask";
850 
851 
852