1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2004, 2005, 2006, 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) 2004, 2005, 2006, 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  * This file defines an application controller.
 28  *
 29  */
 30 
 31 /**
 32  * Creates a controller. 
 33  * @class
 34  * This class represents an application controller.
 35  * 
 36  * @param	{DwtShell}		container		the application container
 37  * @param	{ZmApp}			app				the application
 38  * @param	{constant}		type			type of controller (typically a view type)				
 39  * @param	{string}		sessionId		the session id
 40  */
 41 ZmController = function(container, app, type, sessionId) {
 42 
 43 	if (arguments.length == 0) { return; }
 44 
 45 	this.setCurrentViewType(this.getDefaultViewType());
 46 	this.setCurrentViewId(this.getDefaultViewType());
 47 	if (sessionId) {
 48 		this.setSessionId(sessionId, type);
 49 	}
 50 	
 51 	this._container = container;
 52 	this._app = app;
 53 		
 54 	this._shell = appCtxt.getShell();
 55 	this._appViews = {};
 56 	
 57 	this._authenticating = false;
 58 	this.isHidden = (sessionId == ZmApp.HIDDEN_SESSION);
 59 	this._elementsToHide = null;
 60 };
 61 
 62 ZmController.prototype.isZmController = true;
 63 ZmController.prototype.toString = function() { return "ZmController"; };
 64 
 65 
 66 ZmController.SESSION_ID_SEP = "-";
 67 
 68 // Abstract methods
 69 
 70 ZmController.prototype._setView = function() {};
 71 
 72 /**
 73  * Returns the default view type
 74  */
 75 ZmController.getDefaultViewType = function() {};	// needed by ZmApp::getSessionController
 76 ZmController.prototype.getDefaultViewType = function() {};
 77 
 78 // _defaultView is DEPRECATED in 8.0
 79 ZmController.prototype._defaultView = ZmController.prototype.getDefaultViewType;
 80 
 81 
 82 
 83 // Public methods
 84 
 85 /**
 86  * Gets the session ID.
 87  * 
 88  * @return	{string}	the session ID
 89  */
 90 ZmController.prototype.getSessionId =
 91 function() {
 92 	return this._sessionId;
 93 };
 94 
 95 /**
 96  * Sets the session id, view id, and tab id (using the type and session id).
 97  * Controller for a view that shows up in a tab within the app chooser bar.
 98  * Examples include compose, send confirmation, and msg view.
 99  *
100  * @param {string}						sessionId					the session id
101  * @param {string}						type						the type
102  * @param {ZmSearchResultsController}	searchResultsController		owning controller
103  */
104 ZmController.prototype.setSessionId =
105 function(sessionId, type) {
106 
107 	this._sessionId = sessionId;
108 	if (type) {
109 		this.setCurrentViewType(type);
110 		this.setCurrentViewId(sessionId ? [type, sessionId].join(ZmController.SESSION_ID_SEP) : type);
111 		this.tabId = sessionId ? ["tab", this.getCurrentViewId()].join("_") : "";
112 	}
113 	
114 	// this.sessionId and this.viewId are DEPRECATED in 8.0;
115 	// use getSessionId() and getCurrentViewId() instead
116 	this.sessionId = this._sessionId;
117 	this.viewId = this.getCurrentViewId();
118 };
119 
120 /**
121  * Gets the current view type.
122  * 
123  * @return	{constant}			the view type
124  */
125 ZmController.prototype.getCurrentViewType =
126 function(viewType) {
127 	return this._currentViewType;
128 };
129 // _getViewType is DEPRECATED in 8.0
130 ZmController.prototype._getViewType = ZmController.prototype.getCurrentViewType;
131 
132 /**
133  * Sets the current view type.
134  * 
135  * @param	{constant}	viewType		the view type
136  */
137 ZmController.prototype.setCurrentViewType =
138 function(viewType) {
139 	this._currentViewType = viewType;
140 };
141 
142 /**
143  * Gets the current view ID.
144  * 
145  * @return	{DwtComposite}	the view Id
146  */
147 ZmController.prototype.getCurrentViewId =
148 function() {
149 	return this._currentViewIdOverride || this._currentViewId;
150 };
151 
152 /**
153  * Sets the current view ID.
154  * 
155  * @param	{string}	viewId		the view ID
156  */
157 ZmController.prototype.setCurrentViewId =
158 function(viewId) {
159 	this._currentViewId = viewId;
160 	
161 	// this._currentView is DEPRECATED in 8.0; use getCurrentViewId() instead
162 	this._currentView = this._currentViewId;
163 };
164 
165 /**
166  * Gets the application.
167  * 
168  * @return	{ZmApp}		the application
169  */
170 ZmController.prototype.getApp = function() {
171 	return this._app;
172 };
173 
174 /**
175  * return the view elements. Currently a toolbar, app content, and "new" button.
176  * 
177  * @param view (optional if provided toolbar)
178  * @param appContentView
179  * @param toolbar (used only if view param is null)
180  *
181  */
182 ZmController.prototype.getViewElements =
183 function(view, appContentView, toolbar) {
184 	var elements = {};
185 	toolbar = toolbar || this._toolbar[view];
186 	elements[ZmAppViewMgr.C_TOOLBAR_TOP] = toolbar;
187 	elements[ZmAppViewMgr.C_APP_CONTENT] = appContentView;
188 
189 	return elements;
190 };
191 
192 /**
193  * Pops-up the error dialog.
194  * 
195  * @param	{String}	msg		the error msg
196  * @param	{ZmCsfeException}	ex		the exception
197  * @param	{Boolean}	noExecReset		(not used)
198  * @param	{Boolean}	hideReportButton		if <code>true</code>, hide the "Send error report" button
199  * @param	{Boolean}	expanded		if <code>true</code>, contents are expanded by default
200  */
201 ZmController.prototype.popupErrorDialog = 
202 function(msg, ex, noExecReset, hideReportButton, expanded, noEncoding) {
203 	// popup alert
204 	var errorDialog = appCtxt.getErrorDialog();
205 	var detailStr = "";
206 	if (typeof ex == "string") {
207 		// in case an Error makes it here
208 		detailStr = ex;
209 	} else if (ex instanceof Object) {
210 		ex.msg = ex.msg || msg;
211 		var fields = ["method", "msg", "code", "detail", "trace", "request",
212 					"fileName", "lineNumber", "message", "name", "stack" ];
213 		var html = [], i = 0;
214 		html[i++] = "<table>";
215 		for (var j = 0; j < fields.length; j++) {
216 			var fld = fields[j];
217 			var value = AjxStringUtil.htmlEncode(ex[fld]);
218 			if (value) {
219 				if (fld == "request") {
220 					value = ["<pre>", value, "</pre>"].join("");
221 					var msgDiv = document.getElementById(errorDialog._msgCellId);
222 					if (msgDiv) {
223 						msgDiv.className = "DwtMsgDialog-wide";
224 					}
225 				}
226 				html[i++] = ["<tr><td valign='top'>", fields[j], ":</td><td valign='top'>", value, "</td></tr>"].join("");
227 			}
228 		}
229 		html[i++] = "</table>";
230 		detailStr = html.join("");
231 	}
232 	errorDialog.registerCallback(DwtDialog.OK_BUTTON, this._errorDialogCallback, this);
233 	if (!noEncoding) {
234 		msg = AjxStringUtil.htmlEncode(msg);
235 	}
236 	errorDialog.setMessage(msg, detailStr, DwtMessageDialog.CRITICAL_STYLE, ZmMsg.zimbraTitle);
237 	errorDialog.popup(null, hideReportButton);
238 	if (expanded)
239 		errorDialog.showDetail();
240 };
241 
242 /**
243  * Pops-up an error dialog describing an upload error.
244  *
245  * @param	{constant}	type		the type of the uploaded item, e.g. <code>ZmItem.MSG</code>.
246  * @param	{Number}	respCode		the HTTP reponse status code
247  * @param	{String}	extraMsg		optional message to append to the status
248  */
249 ZmController.prototype.popupUploadErrorDialog =
250 function(type, respCode, extraMsg) {
251     var warngDlg = appCtxt.getMsgDialog();
252     var style = DwtMessageDialog.CRITICAL_STYLE;
253     var msg = this.createErrorMessage(type, respCode, extraMsg);
254 	if (msg.length > 0) {
255 		warngDlg.setMessage(msg, style);
256 		warngDlg.popup();
257 	}
258 };
259 
260 ZmController.prototype.createErrorMessage = function(type, respCode, extraMsg) {
261 	var msg = "";
262 
263 	switch (respCode) {
264 		case AjxPost.SC_OK:
265 			break;
266 
267 		case AjxPost.SC_REQUEST_ENTITY_TOO_LARGE:
268 			var basemsg =
269 				type && ZmMsg['attachmentSizeError_' + type] ||
270 					ZmMsg.attachmentSizeError;
271 			var sizelimit =
272 				AjxUtil.formatSize(appCtxt.get(ZmSetting.MESSAGE_SIZE_LIMIT));
273 			msg = AjxMessageFormat.format(basemsg, sizelimit);
274 			break;
275 
276 		default:
277 			var basemsg =
278 				type && ZmMsg['errorAttachment_' + type] ||
279 					ZmMsg.errorAttachment;
280 			msg = AjxMessageFormat.format(basemsg, respCode || AjxPost.SC_NO_CONTENT);
281 			break;
282 	}
283 
284 	if ((msg.length > 0) && extraMsg) {
285 		msg += '<br /><br />';
286 		msg += extraMsg;
287 	}
288 	return msg;
289 };
290 
291 ZmController.handleScriptError =
292 function(ex, debugWindowOnly) {
293 
294 	var text = [];
295 	var eol = "<br/>";
296 	if (ex) {
297 		var msg = ZmMsg.scriptError + ": " + ex.message;
298 		var m = ex.fileName && ex.fileName.match(/(\w+\.js)/);
299 		if (m && m.length) {
300 			msg += " - " + m[1] + ":" + ex.lineNumber;
301 		}
302 		if (ex.fileName)	{ text.push("File: " + ex.fileName); }
303 		if (ex.lineNumber)	{ text.push("Line: " + ex.lineNumber); }
304 		if (ex.name)		{ text.push("Error: " + ex.name); }
305 		if (ex.stack)		{ text.push("Stack: " + ex.stack.replace("\n", eol, "g")); }
306 	}
307 	var content = text.join(eol);
308 	var errorMsg = [msg, content].join(eol + eol);
309 	if (debugWindowOnly) {
310 		// Display the error in the debug window
311 		DBG.println(AjxDebug.DBG1, errorMsg);
312 	} else {
313 		// Record the error in a log buffer and display a script error popup
314 		AjxDebug.println(AjxDebug.EXCEPTION, errorMsg);
315 		appCtxt.getAppController().popupErrorDialog(msg, content, null, false, true);
316 	}
317 };
318 
319 /**
320  * Gets the key map name.
321  * 
322  * @return	{String}	the key map name
323  */
324 ZmController.prototype.getKeyMapName =
325 function() {
326 	return ZmKeyMap.MAP_GLOBAL;
327 };
328 
329 /**
330  * Handles the key action.
331  * 
332  * @param	{constant}		actionCode		the action code
333  * @return	{Boolean}	<code>true</code> if the key action is handled
334  * 
335  * @see		ZmApp.ACTION_CODES_R
336  * @see		ZmKeyMap
337  */
338 ZmController.prototype.handleKeyAction =
339 function(actionCode, ev) {
340 	DBG.println(AjxDebug.DBG3, "ZmController.handleKeyAction");
341 	
342 	// tab navigation shortcut
343 	var tabView = this.getTabView ? this.getTabView() : null;
344 	if (tabView && tabView.handleKeyAction(actionCode)) {
345 		return true;
346 	}
347 
348 	// shortcuts tied directly to operations
349     var isExternalAccount = appCtxt.isExternalAccount();
350 	var app = ZmApp.ACTION_CODES_R[actionCode];
351 	if (app) {
352 		var op = ZmApp.ACTION_CODES[actionCode];
353 		if (op) {
354             if (isExternalAccount) { return true; }
355 			appCtxt.getApp(app).handleOp(op);
356 			return true;
357 		}
358 	}
359 
360     switch (actionCode) {
361 
362 		case ZmKeyMap.NEW: {
363             if (isExternalAccount) { break; }
364 			// find default "New" action code for current app
365 			app = appCtxt.getCurrentAppName();
366 			var newActionCode = ZmApp.NEW_ACTION_CODE[app];
367 			if (newActionCode) {
368 				var op = ZmApp.ACTION_CODES[newActionCode];
369 				if (op) {
370 					appCtxt.getApp(app).handleOp(op);
371 					return true;
372 				}
373 			}
374 			break;
375 		}
376 
377 		case ZmKeyMap.NEW_FOLDER:
378 		case ZmKeyMap.NEW_TAG:
379             if (isExternalAccount || appCtxt.isWebClientOffline()) { break; }
380 			var op = ZmApp.ACTION_CODES[actionCode];
381 			if (op) {
382 				this._newListener(null, op);
383 			}
384 			break;
385 
386 	    case ZmKeyMap.NEW_SEARCH: {
387 		    appCtxt.getSearchController().openNewSearchTab();
388 		    break;
389 	    }
390 
391 		case ZmKeyMap.SAVED_SEARCH:
392             if (isExternalAccount) { break; }
393 			var searches = appCtxt.getFolderTree().getByType(ZmOrganizer.SEARCH);
394 			if (searches && searches.length > 0) {
395 				var dlg = appCtxt.getChooseFolderDialog();
396 				// include app name in ID so we have one overview per app to show only its saved searches
397 				var params = {treeIds:		[ZmOrganizer.SEARCH],
398 							  overviewId:	dlg.getOverviewId(ZmOrganizer.SEARCH, this._app._name),
399 							  appName:      this._app._name,
400 							  title:		ZmMsg.selectSearch};
401 				ZmController.showDialog(dlg, new AjxCallback(null, ZmController._searchSelectionCallback, [dlg]), params);
402 			}
403 			break;
404 
405 		case ZmKeyMap.VISIT:
406 			var dlg = appCtxt.getChooseFolderDialog();
407 			var orgType = ZmApp.ORGANIZER[this._app._name] || ZmOrganizer.FOLDER;
408 			var params = {treeIds:		[orgType],
409 						  overviewId:	dlg.getOverviewId(ZmOrganizer.APP[orgType]),
410 						  appName:		this._app._name,
411 						  noRootSelect: true,
412 						  title:		AjxMessageFormat.format(ZmMsg.goToFolder, ZmMsg[ZmOrganizer.MSG_KEY[orgType]])};
413 			ZmController.showDialog(dlg, new AjxCallback(null, ZmController._visitOrgCallback, [dlg, orgType]), params);
414 			break;
415 
416 		case ZmKeyMap.VISIT_TAG:
417 			if (appCtxt.getTagTree().size() > 0) {
418 				var dlg = appCtxt.getPickTagDialog();
419 				ZmController.showDialog(dlg, new AjxCallback(null, ZmController._visitOrgCallback, [dlg, ZmOrganizer.TAG]));
420 			}
421 			break;
422 
423 		default:
424 			return false;
425 	}
426 	return true;
427 };
428 
429 /**
430  * @private
431  */
432 ZmController._searchSelectionCallback =
433 function(dialog, searchFolder) {
434 	if (searchFolder) {
435 		appCtxt.getSearchController().redoSearch(searchFolder.search);
436 	}
437 	dialog.popdown();
438 };
439 
440 /**
441  * @private
442  */
443 ZmController._visitOrgCallback =
444 function(dialog, orgType, org) {
445 	if (org) {
446 		var tc = appCtxt.getOverviewController().getTreeController(orgType);
447 		if (tc && tc._itemClicked) {
448 			tc._itemClicked(org);
449 		}
450 	}
451 	dialog.popdown();
452 };
453 
454 /**
455  * Checks if shortcuts for the given map are supported for this view. For example, given the map
456  * "tabView", a controller that creates a tab view would return <code>true</code>.
457  *
458  * @param {String}	map		the name of a map (see {@link DwtKeyMap})
459  * @return	{Boolean}		<code>true</code> if shortcuts are supported
460  */
461 ZmController.prototype.mapSupported =
462 function(map) {
463 	return false;
464 };
465 
466 /**
467  * @private
468  */
469 ZmController.prototype._newListener =
470 function(ev, op) {
471 	switch (op) {
472 		// new organizers
473 		case ZmOperation.NEW_FOLDER: {
474 			// note that this shortcut only happens if mail app is around - it means "new mail folder"
475 			ZmController.showDialog(appCtxt.getNewFolderDialog(), this.getNewFolderCallback());
476 			break;
477 		}
478 		case ZmOperation.NEW_TAG: {
479 			if (!this._newTagCb) {
480 				this._newTagCb = new AjxCallback(this, this._newTagCallback);
481 			}
482 			ZmController.showDialog(appCtxt.getNewTagDialog(), this._newTagCb);
483 			break;
484 		}
485 	}
486 };
487 
488 /**
489  * @private
490  */
491 ZmController.prototype._newFolderCallback =
492 function(parent, name, color, url) {
493 	// REVISIT: Do we really want to close the dialog before we
494 	//          know if the create succeeds or fails?
495 	var dialog = appCtxt.getNewFolderDialog();
496 	dialog.popdown();
497 
498 	var oc = appCtxt.getOverviewController();
499 	oc.getTreeController(ZmOrganizer.FOLDER)._doCreate(parent, name, color, url);
500 };
501 
502 /**
503  * @private
504  */
505 ZmController.prototype._newTagCallback =
506 function(params) {
507 	appCtxt.getNewTagDialog().popdown();
508 	var oc = appCtxt.getOverviewController();
509 	oc.getTreeController(ZmOrganizer.TAG)._doCreate(params);
510 };
511 
512 /**
513  * @private
514  */
515 ZmController.prototype._createTabGroup =
516 function(name) {
517 	name = name ? name : this.toString();
518 	this._tabGroup = new DwtTabGroup(name);
519 	return this._tabGroup;
520 };
521 
522 /**
523  * @private
524  */
525 ZmController.prototype._setTabGroup =
526 function(tabGroup) {
527 	this._tabGroup = tabGroup;
528 };
529 
530 /**
531  * Gets the tab group.
532  * 
533  * @return	{Object}	the tab group
534  */
535 ZmController.prototype.getTabGroup =
536 function() {
537 	return this._tabGroup;
538 };
539 
540 /**
541  * Gets the new folder callback.
542  * 
543  * @return	{AjxCallback}	the callback
544  */
545 ZmController.prototype.getNewFolderCallback =
546 function() {
547 	if (!this._newFolderCb) {
548 		this._newFolderCb = new AjxCallback(this, this._newFolderCallback);
549 	}
550 	return this._newFolderCb;
551 };
552 
553 /**
554  * Remember the currently focused item before this view is hidden. Typically called by a preHideCallback.
555  * 
556  * @private
557  */
558 ZmController.prototype._saveFocus = 
559 function() {
560 	var currentFocusMember = appCtxt.getRootTabGroup().getFocusMember();
561 	var myTg = this.getTabGroup();
562 	this._savedFocusMember = (currentFocusMember && myTg && myTg.contains(currentFocusMember)) ? currentFocusMember : null;
563 	return this._savedFocusMember;
564 };
565 
566 /**
567  * Make our tab group the current app view tab group, and restore focus to
568  * whatever had it last time we were visible. Typically called by a postShowCallback.
569  * 
570  * @private
571  */
572 ZmController.prototype._restoreFocus = 
573 function(focusItem, noFocus) {
574 
575 	var rootTg = appCtxt.getRootTabGroup();
576 
577 	var curApp = appCtxt.getCurrentApp();
578 	var ovId = curApp && curApp.getOverviewId();
579 	var overview = ovId && appCtxt.getOverviewController().getOverview(ovId);
580 	if (rootTg && overview && (overview != ZmController._currentOverview)) {
581 		var currTg = ZmController._currentOverview &&
582 			ZmController._currentOverview.getTabGroupMember();
583 		rootTg.replaceMember(currTg, overview.getTabGroupMember(),
584 		                     false, false, null, true);
585 		ZmController._currentOverview = overview;
586 	}
587 
588 	var myTg = this.getTabGroup();
589 	focusItem = focusItem || this._savedFocusMember || this._getDefaultFocusItem() || rootTg.getFocusMember();
590 	noFocus = noFocus || ZmController.noFocus;
591 	ZmController.noFocus = false;
592 	if (rootTg && myTg && (myTg != ZmController._currentAppViewTabGroup)) {
593 		rootTg.replaceMember(ZmController._currentAppViewTabGroup, myTg, false, false, focusItem, noFocus);
594 		ZmController._currentAppViewTabGroup = myTg;
595 	} else if (focusItem && !noFocus) {
596 		appCtxt.getKeyboardMgr().grabFocus(focusItem);
597 	}
598 };
599 
600 /**
601  * @private
602  */
603 ZmController.prototype._getDefaultFocusItem = 
604 function() {
605 	var myTg = this.getTabGroup();
606 	return myTg ? myTg.getFirstMember(true) : null;
607 };
608 
609 // Callbacks to run on changes in view state
610 ZmController.prototype._preUnloadCallback	= function() { return true; };
611 ZmController.prototype._postHideCallback	= function() { return true; };
612 ZmController.prototype._postRemoveCallback	= function() { return true; };
613 ZmController.prototype._preShowCallback		= function() { return true; };
614 
615 // preserve focus state
616 ZmController.prototype._preHideCallback = 
617 function() {
618 	DBG.println(AjxDebug.DBG2, "ZmController.prototype._preHideCallback");
619 	this._saveFocus();
620 	return true;
621 };
622 
623 // restore focus state
624 ZmController.prototype._postShowCallback = 
625 function() {
626 	DBG.println(AjxDebug.DBG2, "ZmController.prototype._postShowCallback");
627 	this._restoreFocus();
628 	return true;
629 };
630 
631 /**
632  * Common exception handling entry point for sync and async commands.
633  * 
634  * @private
635  */
636 ZmController.prototype._handleError =
637 function(ex, continuation) {
638 	this._handleException(ex, continuation);
639 };
640 
641 /**
642  * Handles exceptions. There is special handling for auth-related exceptions.
643  * Other exceptions generally result in the display of an error dialog. An
644  * auth-expired exception results in the display of a login dialog. After the
645  * user logs in, we use the continuation to re-run the request that failed.
646  * 
647  * @param {AjxException}	ex				the exception
648  * @param {Hash}	continuation		the original request params
649  * 
650  * @private
651  */
652 ZmController.prototype._handleException = function(ex, continuation) {
653 
654 	if (ex.code == AjxSoapException.INVALID_PDU) {
655 		ex.code = ZmCsfeException.SVC_FAILURE;
656 		ex.detail = ["contact your administrator (", ex.msg, ")"].join("");
657 		ex.msg = "Service failure";
658 	}
659 	
660 	if (ex.code == ZmCsfeException.SVC_AUTH_EXPIRED || ex.code == ZmCsfeException.SVC_AUTH_REQUIRED || ex.code == ZmCsfeException.NO_AUTH_TOKEN) {
661 		ZmCsfeCommand.noAuth = true;
662         DBG.println(AjxDebug.DBG1, "ZmController.prototype._handleException ex.code : " + ex.code + ". Invoking logout.");
663 		ZmZimbraMail.logOff(null, true);
664 		return;
665 	}
666 
667 	// If we get this error, user is probably looking at a stale list. Let's
668 	// refetch user's search results. This is more likely to happen in zdesktop.
669 	// See bug 33760.
670 	if (ex.code == ZmCsfeException.MAIL_NO_SUCH_MSG) {
671 		var vid = appCtxt.getCurrentViewId();
672 		// only process if we're in one of these views otherwise, do the default
673 		if (vid == ZmId.VIEW_CONVLIST || vid == ZmId.VIEW_TRAD) {
674 			var mailApp = appCtxt.getApp(ZmApp.MAIL);
675 			var callback = appCtxt.isOffline ? new AjxCallback(this, this._handleMailSearch, mailApp) : null;
676 			mailApp.mailSearch(null, callback);
677 			return;
678 		}
679 	}
680 
681 	// silently ignore polling exceptions
682 	if (ex.method !== "NoOpRequest") {
683 		var args;
684 		if (ex.code === ZmCsfeException.MAIL_NO_SUCH_ITEM) {
685 			args = ex.data.itemId;
686 		}
687         else if (ex.code === ZmCsfeException.MAIL_SEND_FAILURE) {
688 			args = ex.code; // bug fix #5603 - error msg for mail.SEND_FAILURE takes an argument
689 		}
690         else if (ex.code === ZmCsfeException.MAIL_INVALID_NAME) {
691 			args = ex.data.name;
692 		}
693         else if (ex.code === ZmCsfeException.SVC_UNKNOWN_DOCUMENT) {
694             args = ex.msg.split(': ')[1];
695         }
696 
697 		if (ex.lineNumber && !ex.detail) {
698 			// JS error that was caught before our JS-specific handler got it
699 			ZmController.handleScriptError(ex);
700 		}
701         else {
702             var msg;
703 
704             if (continuation && continuation.restUri && continuation.restUri.indexOf('zimbraim') !== -1) {
705                 msg = ZmMsg.chatXMPPError;
706             }
707             else {
708                 msg = ex.getErrorMsg ? ex.getErrorMsg(args) : ex.msg || ex.message;
709             }
710 
711 			this.popupErrorDialog(msg, ex, true, this._hideSendReportBtn(ex));
712 		}
713 	}
714 };
715 
716 ZmController.prototype._handleMailSearch =
717 function(app) {
718 	if (appCtxt.get(ZmSetting.OFFLINE_SHOW_ALL_MAILBOXES)) {
719 		app.getOverviewContainer().highlightAllMboxes();
720 	}
721 };
722 
723 /**
724  * @private
725  */
726 ZmController.prototype._hideSendReportBtn =
727 function(ex) {
728 	return (ex.code == ZmCsfeException.MAIL_TOO_MANY_TERMS ||
729 		  	ex.code == ZmCsfeException.MAIL_MAINTENANCE_MODE ||
730 			ex.code == ZmCsfeException.MAIL_MESSAGE_TOO_BIG ||
731 			ex.code == ZmCsfeException.NETWORK_ERROR ||
732 		   	ex.code == ZmCsfeException.EMPTY_RESPONSE ||
733 		   	ex.code == ZmCsfeException.BAD_JSON_RESPONSE ||
734 		   	ex.code == ZmCsfeException.TOO_MANY_TAGS ||
735 			ex.code == ZmCsfeException.OFFLINE_ONLINE_ONLY_OP);
736 };
737 
738 //
739 // Msg dialog Callbacks
740 //
741 
742 /**
743  * @private
744  */
745 ZmController.prototype._errorDialogCallback =
746 function() {
747 	appCtxt.getErrorDialog().popdown();
748 };
749 
750 /**
751  * Shows a dialog. Since the dialog is a shared resource, a dialog reset is performed.
752  * 
753  * @param	{DwtDialog}		dialog		the dialog
754  * @param	{AjxCallback}	callback	the callback
755  * @param	{Hash}		params		a hash of parameters
756  * @param	{ZmAccount}	account		the account
757  * 
758  * @see DwtDialog#reset
759  * @see DwtDialog#popup
760  */
761 ZmController.showDialog = 
762 function(dialog, callback, params, account) {
763 	dialog.reset(account);
764 	dialog.registerCallback(DwtDialog.OK_BUTTON, callback);
765 	dialog.popup(params, account);
766 };
767 
768 /**
769  * Pop down the dialog and clear any pending actions (initiated from an action menu).
770  * 
771  * @private
772  */
773 ZmController.prototype._clearDialog =
774 function(dialog) {
775 	dialog.popdown();
776 	this._pendingActionData = null;
777 };
778 
779 /**
780  * @private
781  */
782 ZmController.prototype._menuPopdownActionListener = function() {};
783 
784 /**
785  * Checks if the view is transient.
786  * 
787  * @param	{Object}	oldView		the old view
788  * @param	{Object}	newView		the new view
789  * @return	{Boolean}		<code>true</code> if the controller is transient.
790  */
791 ZmController.prototype.isTransient =
792 function(oldView, newView) {
793 	return false;
794 };
795 
796