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  * This file defines the application context class.
 27  *
 28  */
 29 
 30 /**
 31  * Creates an application context.
 32  * @class
 33  * This class is a container for application context information.
 34  * 
 35  */
 36 ZmAppCtxt = function() {
 37 
 38 	this._trees = {};
 39 
 40 	this.accountList = new ZmAccountList();
 41 	// create dummy account for startup
 42 	this.accountList.add(new ZmZimbraAccount(ZmAccountList.DEFAULT_ID, null, false));
 43 
 44 	// public properties
 45 	this.inStartup = false;				// true if we are starting app (set in ZmZimbraMail)
 46 	this.currentRequestParams = null;	// params of current SOAP request (set in ZmRequestMgr)
 47 	this.rememberMe = null;
 48 	this.userDomain = "";
 49 
 50 	// account-specific
 51 	this.isFamilyMbox = false;
 52 	this.multiAccounts = false;
 53     this.sendAsEmails = [];
 54     this.sendOboEmails = [];
 55 
 56 	this._evtMgr = new AjxEventMgr();
 57 
 58 	this._itemCache			= {};
 59 	this._itemCacheDeferred	= {};
 60 	this._acCache			= {};	// autocomplete
 61 	this._isExpandableDL	= {};	// distribution lists
 62 
 63 	this._checkAuthTokenWarning();
 64 };
 65 
 66 ZmAppCtxt.ONE_MINUTE  = 60 * 1000;
 67 ZmAppCtxt.MAX_TIMEOUT_VALUE = 2147483647;
 68 
 69 ZmAppCtxt._ZIMLETS_EVENT = 'ZIMLETS';
 70 ZmAppCtxt._AUTHTOKEN_EVENT = 'AUTHTOKEN';
 71 
 72 //Regex constants
 73 //Bug fix # 79986, #81095. Invalid file names are < > , ? | / \ * :
 74 ZmAppCtxt.INVALID_NAME_CHARS = "[\\|?<>:*\"\\\\\/]";
 75 ZmAppCtxt.INVALID_NAME_CHARS_RE = new RegExp(ZmAppCtxt.INVALID_NAME_CHARS);
 76 
 77 /**
 78  * Returns a string representation of the application context.
 79  * 
 80  * @return		{String}		a string representation of the application context
 81  */
 82 ZmAppCtxt.prototype.toString =
 83 function() {
 84 	return "ZmAppCtxt";
 85 };
 86 
 87 ZmAppCtxt.prototype._checkAuthTokenWarning =
 88 function() {
 89 	this._authIntervalId = window.setInterval(this._authTokenWarningTimeout.bind(this), ZmAppCtxt.ONE_MINUTE);
 90 };
 91 ZmAppCtxt.prototype._setAuthTokenWarning =
 92 function(delay) {
 93     window.setTimeout(this._authTokenWarningTimeout.bind(this), delay);
 94 };
 95 
 96 /**
 97  * Adds a listener to the auth token warning event. This listener is fired once
 98  * per minute when less than five minutes remain before token expiry.
 99  *
100  * @param	{AjxCallback}	listener		the listener
101  * @param	{int}		index		the index to where to add the listener
102  * @return	{Boolean}	<code>true</code> if the listener is added; <code>false</code> otherwise
103  */
104 ZmAppCtxt.prototype.addAuthTokenWarningListener =
105 function(listener, index) {
106 	return this._evtMgr.addListener(ZmAppCtxt._AUTHTOKEN_EVENT, listener, index);
107 };
108 
109 /**
110  * Removes a listener for the auth token warning event.
111  *
112  * @param	{AjxCallback}	listener		the listener
113  * @return	{Boolean}	<code>true</code> if the listener is removed; <code>false</code> otherwise
114  */
115 ZmAppCtxt.prototype.removeAuthTokenWarningListener =
116 function(listener) {
117 	return this._evtMgr.removeListener(ZmAppCtxt._AUTHTOKEN_EVENT, listener);
118 };
119 
120 ZmAppCtxt.prototype._authTokenWarningTimeout =
121 function () {
122 
123 	if (!window.authTokenExpires) {
124 		return; //for cases we the auth token expires is not available. (e.g. some new windows we didn't set it for yet, or for saved rest URLs
125 	}
126 
127 	var now = new Date().getTime();
128 	var millisToLive = window.authTokenExpires - now;
129     var minutesToLive = Math.round(millisToLive / ZmAppCtxt.ONE_MINUTE);
130     var delay;
131 
132 	if (minutesToLive > 5 || millisToLive <= 0) {
133         // Outside the times to issue warnings
134         if (minutesToLive === 6) {
135             // Line up the timer to go off at exactly 5 minutes (or as exact as we can make it), which is
136             // when we start issuing warnings
137             window.clearInterval(this._authIntervalId);
138             delay = millisToLive - (5 * ZmAppCtxt.ONE_MINUTE);
139             this._setAuthTokenWarning(delay);
140         }
141 		return;
142 	}
143 
144 	if (this._evtMgr.isListenerRegistered(ZmAppCtxt._AUTHTOKEN_EVENT)) {
145 		var event = new ZmEvent(ZmAppCtxt._AUTHTOKEN_EVENT);
146 		this._evtMgr.notifyListeners(ZmAppCtxt._AUTHTOKEN_EVENT, event);
147 	}
148 
149 	var msg;
150     var decaSecondsToLive = 0;
151     var toastDuration;
152     if (minutesToLive > 1) {
153         msg = AjxMessageFormat.format(ZmMsg.authTokenExpirationWarning, [minutesToLive, ZmMsg.minutes]);
154         toastDuration = ZmAppCtxt.ONE_MINUTE / 4;
155     } else {
156         // Get the number of 10-second intervals remaining - used once we are within 1 minute
157         decaSecondsToLive =  Math.round(millisToLive / 10000);
158         toastDuration = 8000;
159         if (decaSecondsToLive >= 6) {
160             // 1 minute+ to go.  But should be pretty close to 1 minute
161             msg = AjxMessageFormat.format(ZmMsg.authTokenExpirationWarning, [1, ZmMsg.minute]);
162         } else {
163             // Seconds remain
164             msg = AjxMessageFormat.format(ZmMsg.authTokenExpirationWarning, [decaSecondsToLive * 10, ZmMsg.seconds]);
165         }
166     }
167 
168 	var params = {
169 		msg:    msg,
170 		level:  ZmStatusView.LEVEL_WARNING,
171 		transitions: [{type: "fade-in", duration: 500}, {type: "pause", duration: toastDuration}, {type: "fade-out", duration: 500} ]
172 	};
173 	this.setStatusMsg(params);
174 
175     if (minutesToLive > 1) {
176         var floorMinutesToLive = Math.floor(millisToLive / ZmAppCtxt.ONE_MINUTE);
177         if (floorMinutesToLive === minutesToLive) {
178             floorMinutesToLive--;
179         }
180         delay = millisToLive - (floorMinutesToLive * ZmAppCtxt.ONE_MINUTE);
181     }  else {
182         decaSecondsToLive--;
183         delay = millisToLive - (decaSecondsToLive * 10000);
184     }
185     if (delay > 0) {
186         this._setAuthTokenWarning(delay);
187     }
188 };
189 
190 ZmAppCtxt.prototype.setZimbraMail = function(zimbraMail) {
191 	this._zimbraMail = zimbraMail;
192 }
193 
194 ZmAppCtxt.prototype.getZimbraMail = function() {
195 	return this._zimbraMail;
196 }
197 
198 /**
199  * Sets the application controller.
200  * 
201  * @param	{ZmController}	appController	the controller
202  */
203 ZmAppCtxt.prototype.setAppController =
204 function(appController) {
205 	this._appController = appController;
206 };
207 
208 /**
209  * Gets the application controller.
210  * 
211  * @return	{ZmController}		the controller
212  */
213 ZmAppCtxt.prototype.getAppController =
214 function() {
215 	return this._appController;
216 };
217 
218 /**
219  * Gets the application chooser.
220  * 
221  * @return	{ZmAppChooser}		the chooser
222  */
223 ZmAppCtxt.prototype.getAppChooser =
224 function() {
225 	return this._appController.getAppChooser();
226 };
227 
228 /**
229  * Sets the request manager.
230  * 
231  * @param	{ZmRequestMgr}	requestMgr	the request manager
232  */
233 ZmAppCtxt.prototype.setRequestMgr =
234 function(requestMgr) {
235 	this._requestMgr = requestMgr;
236 };
237 
238 /**
239  * Gets the request manager.
240  * 
241  * @return	{ZmRequestMgr}		the request manager
242  */
243 ZmAppCtxt.prototype.getRequestMgr =
244 function() {
245 	return this._requestMgr;
246 };
247 
248 /**
249  * Sets the status message to display.
250  * 
251  * 
252  * @param {Hash}	params	a hash of parameters
253  * @param	{String}	params.msg 		the status message
254  * @param	{constant}	params.level	the status level {@link ZmStatusView}  (may be <code>null</code>)
255  * @param	{String}	params.detail 	the details (may be <code>null</code>)
256  * @param	{Object}	params.transitions 	the transitions (may be <code>null</code>)
257  * @param	{Object}	params.toast	the toast control (may be <code>null</code>)
258  * @param   {boolean}   params.force    force any displayed toasts out of the way (dismiss them and run their dismissCallback). Enqueued messages that are not yet displayed will not be displayed
259  * @param   {AjxCallback} params.dismissCallback    callback to run when the toast is dismissed (by another message using [force], or explicitly calling ZmStatusView.prototype.dismiss())
260  * @param   {AjxCallback} params.finishCallback     callback to run when the toast finishes its transitions by itself (not when dismissed)
261  * </ul>
262  * 
263  */
264 ZmAppCtxt.prototype.setStatusMsg =
265 function(params) {
266 	params = Dwt.getParams(arguments, ZmStatusView.MSG_PARAMS);
267 	this._appController.setStatusMsg(params);
268 };
269 
270 /**
271  * Dismisses the displayed status message, if any
272  */
273 
274 ZmAppCtxt.prototype.dismissStatusMsg =
275 function(all) {
276 	this._appController.dismissStatusMsg(all);
277 };
278 
279 /**
280  * Gets the settings for the given account.
281  * 
282  * @param	{ZmZimbraAccount}	account		the account
283  * @return	{ZmSettings}	the settings
284  */
285 ZmAppCtxt.prototype.getSettings =
286 function(account) {
287 	var al = this.accountList;
288 
289 	var acct = account || al.activeAccount || al.mainAccount
290 			|| al.getAccount(ZmAccountList.DEFAULT_ID); //Probably doesn't ever happen, and if it does, returns null. Might be some historical artifact - did we ever have account with id "main"? I'm still afraid to remove it without being sure it won't cause regression.
291 
292 	return acct && acct.settings;
293 };
294 
295 /**
296  * Sets the settings for the given account.
297  * 
298  * @param	{ZmSettings}	settings		the settings
299  * @param	{ZmZimbraAccount}		account			the account
300  */
301 ZmAppCtxt.prototype.setSettings = 
302 function(settings, account) {
303 	var al = this.accountList;
304 	var id = account
305 		? account.id
306 		: al.activeAccount ? al.activeAccount.id : ZmAccountList.DEFAULT_ID;
307 
308 	var acct = al.getAccount(id);
309 	if (acct) {
310 		acct.settings = settings;
311 	}
312 };
313 
314 /**
315  * Gets the value of the given setting.
316  *
317  * @param {constant}	id		the setting id
318  * @param {String}	key			the setting key (for settings that are of the hash type)
319  * @param {ZmZimbraAccount}	account		the account
320  * @return	{Object}		the setting value
321  */
322 ZmAppCtxt.prototype.get = function(id, key, account) {
323 
324     //use parentAppCtxt in case of new window
325     var context = this.isChildWindow ? parentAppCtxt : this;
326 
327 	// for offline, global settings always come from the "local" parent account
328 	var acct = (context.multiAccounts && ZmSetting.IS_GLOBAL[id])
329 		? context.accountList.mainAccount : account;
330 	return context.getSettings(acct).get(id, key);
331 };
332 
333 /**
334  * Sets the value of the given setting.
335  *
336  * @param {constant}	id					the setting id
337  * @param {Object}	value					the setting value
338  * @param {String}	key					the setting key (for settings that are of the hash type)
339  * @param {Boolean}	setDefault			if <code>true</code>, also replace setting default value
340  * @param {Boolean}	skipNotify			if <code>true</code>, do not notify setting listeners
341  * @param {ZmZimbraAccount}	account		if set, use this account setting instead of the currently active account
342  * @param {Boolean}	skipImplicit		if <code>true</code>, do not check for change to implicit pref
343  */
344 ZmAppCtxt.prototype.set =
345 function(id, value, key, setDefault, skipNotify, account, skipImplicit) {
346 	// for offline, global settings always come from "parent" account
347 	var acct = (this.multiAccounts && ZmSetting.IS_GLOBAL[id])
348 		? this.accountList.mainAccount : account;
349 	var setting = this.getSettings(acct).getSetting(id);
350 
351 	if (setting) {
352 		setting.setValue(value, key, setDefault, skipNotify, skipImplicit);
353 	}
354 };
355 
356 /**
357  * Gets the application.
358  * 
359  * @param	{String}	appName		the application name
360  * @return	{ZmApp}	the application or <code>null</code> if not found
361  */
362 ZmAppCtxt.prototype.getApp =
363 function(appName) {
364 	return this._appController.getApp(appName);
365 };
366 
367 /**
368  * Gets the name of the current application.
369  * 
370  * @return	{String}		the application name
371  */
372 ZmAppCtxt.prototype.getCurrentAppName =
373 function() {
374 	var context = this.isChildWindow ? parentAppCtxt : this;
375 	return context._appController.getActiveApp();
376 };
377 /**
378  *
379  */
380 ZmAppCtxt.prototype.getLoggedInUsername =
381 function() {
382 	return appCtxt.get(ZmSetting.USERNAME);
383 };
384 
385 /**
386  * Gets the current application.
387  * 
388  * @return	{ZmApp}		the current application
389  */
390 ZmAppCtxt.prototype.getCurrentApp =
391 function() {
392 	return this.getApp(this.getCurrentAppName());
393 };
394 
395 /**
396  * Gets the application view manager.
397  * 
398  * @return	{ZmAppViewMgr}		the view manager
399  */
400 ZmAppCtxt.prototype.getAppViewMgr =
401 function() {
402 	return this._appController.getAppViewMgr();
403 };
404 
405 /**
406  * Gets the client command handler.
407  * 
408  * @param	{ZmClientCmdHandler}	clientCmdHdlr		not used
409  * @return	{ZmClientCmdHandler}		the command handler
410  */
411 ZmAppCtxt.prototype.getClientCmdHandler =
412 function(clientCmdHdlr) {
413 	if (!this._clientCmdHandler) {
414 		AjxDispatcher.require("Extras");
415 		this._clientCmdHandler = new ZmClientCmdHandler();
416 	}
417 	return this._clientCmdHandler;
418 };
419 
420 /**
421  * Gets the search bar controller.
422  * 
423  * @return	{ZmSearchController}	the search controller
424  */
425 ZmAppCtxt.prototype.getSearchController =
426 function() {
427 	if (!this._searchController) {
428 		this._searchController = new ZmSearchController(this._shell);
429 	}
430 	return this._searchController;
431 };
432 
433 /**
434  * Gets the overview controller. Creates a new one if not already set, unless dontCreate is true. 
435  *
436  * @param {boolean} dontCreate (optional) - don't create overviewController if not created already. (see ZmApp for usage with dontCreate == true)
437  * 
438  * @return	{ZmOverviewController}	the overview controller
439  */
440 ZmAppCtxt.prototype.getOverviewController =
441 function(dontCreate) {
442 	if (!this._overviewController) {
443 		if (dontCreate) {
444 			return null;
445 		}
446 		this._overviewController = new ZmOverviewController(this._shell);
447 	}
448 	return this._overviewController;
449 };
450 
451 /**
452  * Gets the import/export controller.
453  * 
454  * @return	{ZmImportExportController}	the controller
455  */
456 ZmAppCtxt.prototype.getImportExportController = function() {
457 	if (!this._importExportController) {
458 		AjxDispatcher.require("ImportExport");
459 		this._importExportController = new ZmImportExportController();
460 	}
461 	return this._importExportController;
462 };
463 
464 /**
465  * Gets the message dialog.
466  * 
467  * @return	{DwtMessageDialog}	the message dialog
468  */
469 ZmAppCtxt.prototype.getMsgDialog =
470 function() {
471 	if (!this._msgDialog) {
472 		this._msgDialog = new DwtMessageDialog({parent:this._shell, id: "ZmMsgDialog"});
473 	}
474 	return this._msgDialog;
475 };
476 
477 /**
478  * Gets the message dialog with a help button.
479  *
480  * @return	{DwtMessageDialog}	the message dialog
481  */
482 ZmAppCtxt.prototype.getHelpMsgDialog =
483 	function() {
484 		if (!this._helpMsgDialog) {
485 			this._helpMsgDialog = new DwtMessageDialog({parent:this._shell, helpText:ZmMsg.help, id: "ZmHelpMsgDialog"});
486 		}
487 		return this._helpMsgDialog;
488 	};
489 
490 /**
491  * Gets the yes/no message dialog.
492  * 
493  * @return	{DwtMessageDialog}	the message dialog
494  */
495 ZmAppCtxt.prototype.getYesNoMsgDialog =
496 function(id) {
497 	if (!this._yesNoMsgDialog) {
498 		this._yesNoMsgDialog = new DwtMessageDialog({parent:this._shell, buttons:[DwtDialog.YES_BUTTON, DwtDialog.NO_BUTTON], id: "YesNoMsgDialog"});
499 	}	
500 	return this._yesNoMsgDialog;
501 };
502 
503 /**
504  * Gets the yes/no/cancel message dialog.
505  * 
506  * @return	{DwtMessageDialog}	the message dialog
507  */
508 ZmAppCtxt.prototype.getYesNoCancelMsgDialog =
509 function() {
510 	if (!this._yesNoCancelMsgDialog) {
511 		this._yesNoCancelMsgDialog = new DwtMessageDialog({parent:this._shell, buttons:[DwtDialog.YES_BUTTON, DwtDialog.NO_BUTTON, DwtDialog.CANCEL_BUTTON], id:"YesNoCancel"});
512 	}	
513 	return this._yesNoCancelMsgDialog;
514 };
515 
516 /**
517  * Gets the ok/cancel message dialog.
518  * 
519  * @return	{DwtMessageDialog}	the message dialog
520  */
521 ZmAppCtxt.prototype.getOkCancelMsgDialog =
522 function() {
523 	if (!this._okCancelMsgDialog) {
524 		this._okCancelMsgDialog = new DwtMessageDialog({parent:this._shell, buttons:[DwtDialog.OK_BUTTON, DwtDialog.CANCEL_BUTTON], id:"OkCancel"});
525 	}	
526 	return this._okCancelMsgDialog;
527 };
528 
529 /**
530  * Gets the cancel message dialog.
531  * 
532  * @return	{DwtMessageDialog}	the message dialog
533  */
534 ZmAppCtxt.prototype.getCancelMsgDialog =
535 function() {
536 	if (!this._cancelMsgDialog) {
537 		this._cancelMsgDialog = new DwtMessageDialog({parent:this._shell, buttons:[DwtDialog.CANCEL_BUTTON]});
538 	}
539 	return this._cancelMsgDialog;
540 };
541 
542 /**
543  * Gets the error dialog.
544  * 
545  * @return	{ZmErrorDialog}	the error dialog
546  */
547 ZmAppCtxt.prototype.getErrorDialog = 
548 function() {
549 	if (!this._errorDialog) {
550 		AjxDispatcher.require("Startup2");
551 		this._errorDialog = new ZmErrorDialog(this._shell, ZmMsg);
552 	}
553 	return this._errorDialog;
554 };
555 
556 /**
557  * Gets the new tag dialog.
558  * 
559  * @return	{ZmNewTagDialog}	the new tag dialog
560  */
561 ZmAppCtxt.prototype.getNewTagDialog =
562 function() {
563 	if (!this._newTagDialog) {
564 		this._newTagDialog = new ZmNewTagDialog(this._shell);
565 	}
566 	return this._newTagDialog;
567 };
568 
569 /**
570  * Gets the new contact group dialog.
571  *
572  * @return	{ZmNewContactGroupDialog}	the new contact group dialog
573  */
574 ZmAppCtxt.prototype.getNewContactGroupDialog =
575 function() {
576 	if (!this._newContactGroupDialog) {
577 		this._newContactGroupDialog = new ZmNewContactGroupDialog(this._shell);
578 	}
579 	return this._newContactGroupDialog;
580 };
581 
582 /**
583  * Gets the rename tag dialog.
584  * 
585  * @return	{ZmRenameTagDialog}		the rename tag dialog
586  */
587 ZmAppCtxt.prototype.getRenameTagDialog =
588 function() {
589 	if (!this._renameTagDialog) {
590 		AjxDispatcher.require("Extras");
591 		this._renameTagDialog = new ZmRenameTagDialog(this._shell);
592 	}
593 	return this._renameTagDialog;
594 };
595 
596 /**
597  * Gets the password update dialog.
598  *
599  * @return	{ZmPasswordUpdateDialog}		the rename tag dialog
600  */
601 ZmAppCtxt.prototype.getPasswordChangeDialog =
602 function() {
603 	if (!this._passwordUpdateDialog) {
604 		AjxDispatcher.require("Extras");
605 		this._passwordUpdateDialog = new ZmPasswordUpdateDialog(this._shell);
606 	}
607 	return this._passwordUpdateDialog;
608 };
609 
610 /**
611  * Gets the new folder dialog.
612  * 
613  * @return	{ZmNewFolderDialog}		the new folder dialog
614  */
615 ZmAppCtxt.prototype.getNewFolderDialog =
616 function() {
617 	if (!this._newFolderDialog) {
618         var title = ZmMsg.createNewFolder;
619         var type = ZmOrganizer.FOLDER;
620         this._newFolderDialog = new ZmNewOrganizerDialog(this._shell, null, title, type)
621 	}
622 	return this._newFolderDialog;
623 };
624 
625 /**
626  * Gets the new address book dialog.
627  * 
628  * @return	{ZmNewAddrBookDialog}		the new address book dialog
629  */
630 ZmAppCtxt.prototype.getNewAddrBookDialog = 
631 function() {
632 	if (!this._newAddrBookDialog) {
633 		AjxDispatcher.require("Contacts");
634 		this._newAddrBookDialog = new ZmNewAddrBookDialog(this._shell);
635 	}
636 	return this._newAddrBookDialog;
637 };
638 
639 /**
640  * Gets the new calendar dialog.
641  * 
642  * @return	{ZmNewCalendarDialog}		the new calendar dialog
643  */
644 ZmAppCtxt.prototype.getNewCalendarDialog =
645 function() {
646 	if (!this._newCalendarDialog) {
647 		AjxDispatcher.require(["MailCore", "CalendarCore", "Calendar", "CalendarAppt"]);
648 		this._newCalendarDialog = new ZmNewCalendarDialog(this._shell);
649 	}
650 	return this._newCalendarDialog;
651 };
652 
653 /**
654  * Gets the new task folder dialog.
655  * 
656  * @return	{ZmNewTaskFolderDialog}		the new task folder dialog
657  */
658 ZmAppCtxt.prototype.getNewTaskFolderDialog =
659 function() {
660 	if (!this._newTaskFolderDialog) {
661 		AjxDispatcher.require(["TasksCore", "Tasks"]);
662 		this._newTaskFolderDialog = new ZmNewTaskFolderDialog(this._shell);
663 	}
664 	return this._newTaskFolderDialog;
665 };
666 
667 /**
668  * Gets the new suggestion Preferences dialog
669  *
670  * @return	{ZmTimeSuggestionPrefDialog}
671  */
672 ZmAppCtxt.prototype.getSuggestionPreferenceDialog =
673 function() {
674 	if (!this._suggestionPrefDialog) {
675 		AjxDispatcher.require(["MailCore", "CalendarCore", "Calendar"]);
676         this._suggestionPrefDialog = new ZmTimeSuggestionPrefDialog(this._shell);
677     }
678     return this._suggestionPrefDialog;
679 };
680 
681 /**
682  * Gets the dialog.
683  * 
684  * @return	{DwtDialog}		the dialog
685  */
686 ZmAppCtxt.prototype.getDialog =
687 function(){
688 	if(!this._dialog){
689 		this._dialog = new DwtDialog({parent:this._shell});
690 	}
691 	return this._dialog;
692 };
693 
694 /**
695  * Gets the new search dialog.
696  * 
697  * @return	{ZmNewSearchDialog}		the new search dialog
698  */
699 ZmAppCtxt.prototype.getNewSearchDialog =
700 function() {
701 	this._newSearchDialogs = this._newSearchDialogs || {};
702 	this.searchAppName = this.searchAppName || ZmApp.MAIL;
703 	if (!this._newSearchDialogs[this.searchAppName]) {
704 		this._newSearchDialogs[this.searchAppName] = new ZmNewSearchDialog(this._shell);
705 	}
706 	this._newSearchDialog = this._newSearchDialogs[this.searchAppName];
707 	return this._newSearchDialog;
708 };
709 
710 /**
711  * Gets the rename folder dialog.
712  * 
713  * @return	{ZmRenameFolderDialog}		the rename folder dialog
714  */
715 ZmAppCtxt.prototype.getRenameFolderDialog =
716 function() {
717 	if (!this._renameFolderDialog) {
718 		AjxDispatcher.require("Extras");
719 		this._renameFolderDialog = new ZmRenameFolderDialog(this._shell);
720 	}
721 	return this._renameFolderDialog;
722 };
723 
724 /**
725  * Gets the choose folder dialog.
726  * 
727  * @return	{ZmChooseFolderDialog}		the choose folder dialog
728  */
729 ZmAppCtxt.prototype.getChooseFolderDialog =
730 function(appName) {
731 	var app = appName ? this.getApp(appName) : this.getCurrentApp();
732 	// this.getCurrentAppName() returns "Search" for search apps. Let's re-use dialogs from regular apps.
733 	appName = app.isZmSearchApp ? this.searchAppName : app.getName();
734 	this._chooseFolderDialogs = this._chooseFolderDialogs || {};
735 	if (!this._chooseFolderDialogs[appName]) {
736 		AjxDispatcher.require("Extras");
737 		this._chooseFolderDialogs[appName] = new ZmChooseFolderDialog(this._shell);
738 	}
739 	this._chooseFolderDialog = this._chooseFolderDialogs[appName];
740 	return this._chooseFolderDialog;
741 };
742 
743 ZmAppCtxt.prototype.getChooseAccountDialog =
744 function() {
745 	if (!this._chooseAccountDialog) {
746 		AjxDispatcher.require("Extras");
747 		this._chooseAccountDialog = new ZmChooseAccountDialog(this._shell);
748 	}
749 	return this._chooseAccountDialog;
750 };
751 
752 /**
753  * Gets the pick tag dialog.
754  * 
755  * @return	{ZmPickTagDialog}		the pick tag dialog
756  */
757 ZmAppCtxt.prototype.getPickTagDialog =
758 function() {
759 	if (!this._pickTagDialog) {
760 		AjxDispatcher.require("Extras");
761 		this._pickTagDialog = new ZmPickTagDialog(this._shell);
762 	}
763 	return this._pickTagDialog;
764 };
765 
766 /**
767  * Gets the folder notify dialog.
768  * 
769  * @return	{ZmFolderNotifyDialog}		the folder notify dialog
770  */
771 ZmAppCtxt.prototype.getFolderNotifyDialog =
772 function() {
773 	if (!this._folderNotifyDialog) {
774 		this._folderNotifyDialog = new ZmFolderNotifyDialog(this._shell);
775 	}
776 	return this._folderNotifyDialog;
777 };
778 
779 /**
780  * Gets the folder properties dialog.
781  * 
782  * @return	{ZmFolderPropsDialog}		the folder properties dialog
783  */
784 ZmAppCtxt.prototype.getFolderPropsDialog =
785 function() {
786 	if (!this._folderPropsDialog) {
787 		this._folderPropsDialog = new ZmFolderPropsDialog(this._shell);
788 	}
789 	return this._folderPropsDialog;
790 };
791 
792 /**
793  * Gets the share properties dialog.
794  * 
795  * @return	{ZmSharePropsDialog}		the share properties dialog
796  */
797 ZmAppCtxt.prototype.getSharePropsDialog =
798 function() {
799 	if (!this._sharePropsDialog) {
800 		AjxDispatcher.require("Share");
801 		this._sharePropsDialog = new ZmSharePropsDialog(this._shell);
802 	}
803 	return this._sharePropsDialog;
804 };
805 
806 ZmAppCtxt.prototype.getShareSearchDialog = function() {
807 	if (!this._shareSearchDialog) {
808 		AjxDispatcher.require("Share");
809 		this._shareSearchDialog = new ZmShareSearchDialog({parent:this._shell});
810 	}
811 	return this._shareSearchDialog;
812 };
813 
814 /**
815  * Gets the accept share dialog.
816  * 
817  * @return	{ZmAcceptShareDialog}		the accept share dialog
818  */
819 ZmAppCtxt.prototype.getAcceptShareDialog =
820 function() {
821 	if (!this._acceptShareDialog) {
822 		AjxDispatcher.require("Share");
823 		this._acceptShareDialog = new ZmAcceptShareDialog(this._shell);
824 	}
825 	return this._acceptShareDialog;
826 };
827 
828 /**
829  * Gets the decline share dialog.
830  * 
831  * @return	{ZmDeclineShareDialog}		the decline share dialog
832  */
833 ZmAppCtxt.prototype.getDeclineShareDialog =
834 function() {
835 	if (!this._declineShareDialog) {
836 		AjxDispatcher.require("Share");
837 		this._declineShareDialog = new ZmDeclineShareDialog(this._shell);
838 	}
839 	return this._declineShareDialog;
840 };
841 
842 /**
843  * Gets the revoke share dialog.
844  * 
845  * @return	{ZmRevokeShareDialog}		the revoke share dialog
846  */
847 ZmAppCtxt.prototype.getRevokeShareDialog =
848 function() {
849 	if (!this._revokeShareDialog) {
850 		AjxDispatcher.require("Share");
851 		this._revokeShareDialog = new ZmRevokeShareDialog(this._shell);
852 	}
853 	return this._revokeShareDialog;
854 };
855 
856 /**
857  * Gets the timezone picker dialog.
858  * 
859  * @return	{ZmTimezonePicker}		the timezone picker dialog
860  */
861 ZmAppCtxt.prototype.getTimezonePickerDialog =
862 function() {
863 	if (!this._timezonePickerDialog) {
864 		AjxDispatcher.require("Share");
865 		this._timezonePickerDialog = new ZmTimezonePicker(this._shell);
866 	}
867 	return this._timezonePickerDialog;
868 };
869 
870 /**
871  * Gets the filter rule add/edit dialog.
872  * 
873  * @return	{ZmFilterRuleDialog}		the filter rule add/edit dialog
874  */
875 ZmAppCtxt.prototype.getFilterRuleDialog =
876 function() {
877 	if (!this._filterRuleDialog) {
878 		AjxDispatcher.require(["PreferencesCore", "Preferences"]);
879 		this._filterRuleDialog = new ZmFilterRuleDialog();
880 	}
881 	return this._filterRuleDialog;
882 };
883 
884 /**
885  * Gets the priority message filter dialog.
886  * 
887  * @return {ZmPriorityMessageFilterDialog}  the priority message filter dialog
888  */
889 ZmAppCtxt.prototype.getPriorityMessageFilterDialog = 
890 function() {
891 	if (!this._priorityMessageFilterDialog) {
892 		AjxDispatcher.require(["PreferencesCore", "Preferences"]);
893 		this._priorityMessageFilterDialog = new ZmPriorityMessageFilterDialog();
894 	}
895 	return this._priorityMessageFilterDialog;
896 };
897 
898 
899 /**
900  * Gets the activity stream prompt dialog for running activity stream filters
901  * 
902  * @return {ZmActivityStreamPromptDialog}
903 */
904 ZmAppCtxt.prototype.getActivityStreamFilterDialog = 
905 function() {
906 	if (!this._activityStreamFilterDialog) {
907 		AjxDispatcher.require(["PreferencesCore", "Preferences"]);
908 		this._activityStreamFilterDialog = new ZmActivityStreamPromptDialog();
909 	}
910 	return this._activityStreamFilterDialog;
911 };
912 
913 /**
914  * Gets the prompt for moving files from the Activity Stream to the Inbox
915  * 
916  * @return {ZmActivityToInboxPromptDialog}
917  */
918 ZmAppCtxt.prototype.getActivityToInboxFilterDialog =
919 function() {
920 	if (!this._activityToInboxFilterDialog) {
921 		AjxDispatcher.require(["PreferencesCore", "Preferences"]);
922 		this._activityToInboxFilterDialog = new ZmActivityToInboxPromptDialog();
923 	}
924 	return this._activityToInboxFilterDialog;
925 };
926 
927 /**
928  * Gets the quickadd dialog for creating a contact
929  * 
930  * @return {ZmContactQuickAddDialog}
931  */
932 ZmAppCtxt.prototype.getContactQuickAddDialog = 
933 function() {
934 	if (!this._contactQuickAddDialog) {
935 		AjxDispatcher.require(["ContactsCore", "Contacts"]);
936 		this._contactQuickAddDialog = new ZmContactQuickAddDialog();
937 	}
938 	return this._contactQuickAddDialog;
939 };
940 
941 /**
942  * Gets the confirm dialog.
943  * 
944  * @return	{DwtConfirmDialog}		the confirmation dialog
945  */
946 ZmAppCtxt.prototype.getConfirmationDialog =
947 function(id) {
948 	if (!this._confirmDialog) {
949 		this._confirmDialog = new DwtConfirmDialog(this._shell, null, "CONFIRM_DIALOG");
950 	}
951 	return this._confirmDialog;
952 };
953 
954 /**
955  * Gets the upload dialog.
956  * 
957  * @return	{ZmUploadDialog}		the upload dialog
958  */
959 ZmAppCtxt.prototype.getUploadDialog =
960 function() {
961 	if (!this._uploadDialog) {
962 		AjxDispatcher.require(["Extras"]);
963 		this._uploadDialog = new ZmUploadDialog(this._shell);
964 	}
965 	return this._uploadDialog;
966 };
967 
968 /**
969  * Gets the attach dialog.
970  * 
971  * @return	{ZmAttachDialog}		the attach dialog
972  */
973 ZmAppCtxt.prototype.getAttachDialog =
974 function() {
975 	if (!this._attachDialog) {
976 		AjxDispatcher.require("Share");
977 		this._attachDialog = new ZmAttachDialog(this._shell);
978 		this.runAttachDialogCallbacks();
979 	}
980 	return this._attachDialog;
981 };
982 
983 ZmAppCtxt.prototype.getDumpsterDialog =
984 function() {
985 	if (!this._dumpsterDialog) {
986 		AjxDispatcher.require("Extras");
987 		this._dumpsterDialog = new ZmDumpsterDialog(this._shell);
988 	}
989 	return this._dumpsterDialog;
990 };
991 
992 
993 /**
994  * Gets the mail redirect dialog.
995  *
996  * @return	{ZmMailRedirectDialog}	the new mail redirect dialog
997  */
998 ZmAppCtxt.prototype.getMailRedirectDialog =
999 function() {
1000 	if (!this._mailRedirectDialog) {
1001 		this._mailRedirectDialog = new ZmMailRedirectDialog(this._shell);
1002 	}
1003 	return this._mailRedirectDialog;
1004 };
1005 
1006 /**
1007  * Gets the mail retention warning dialog.
1008  *
1009  * @return	{ZmRetetionWarningDialog}	the new mail retention warning dialog
1010  */
1011 ZmAppCtxt.prototype.getRetentionWarningDialog =
1012 function() {
1013 	if (!this._retentionWarningDialog) {
1014 		this._retentionWarningDialog = new ZmRetentionWarningDialog(this._shell);
1015 	}
1016 	return this._retentionWarningDialog;
1017 };
1018 
1019 
1020 /**
1021  * Runs the attach dialog callbacks.
1022  *
1023  * @private
1024  */
1025 ZmAppCtxt.prototype.runAttachDialogCallbacks =
1026 function() {
1027 	while(this._attachDialogCallback && this._attachDialogCallback.length > 0) {
1028 		var callback = this._attachDialogCallback.shift();
1029 		if(callback && (callback instanceof AjxCallback)) {
1030 			callback.run(this._attachDialog);
1031 		}
1032 	}
1033 };
1034 
1035 /**
1036  * Adds the callback to the attachment dialog callbacks.
1037  *
1038  * @param	{AjxCallback}	callback		the callback
1039  */
1040 ZmAppCtxt.prototype.addAttachmentDialogCallback =
1041 function(callback) {
1042 	if(!this._attachDialogCallback) {
1043 		this._attachDialogCallback = [];
1044 	}
1045 	this._attachDialogCallback.push(callback);
1046 };                                              
1047 
1048 /**
1049  * Gets the upload conflict dialog.
1050  *
1051  * @return	{ZmUploadConflictDialog}	the upload conflict dialog
1052  */
1053 ZmAppCtxt.prototype.getUploadConflictDialog =
1054 function() {
1055 	if (!this._uploadConflictDialog) {
1056 		AjxDispatcher.require(["Extras"]);
1057 		this._uploadConflictDialog = new ZmUploadConflictDialog(this._shell);
1058 	}
1059 	return this._uploadConflictDialog;
1060 };
1061 
1062 /**
1063  * Gets the new briefcase dialog.
1064  *
1065  * @return	{ZmNewBriefcaseDialog}	the new briefcase dialog
1066  */
1067 ZmAppCtxt.prototype.getNewBriefcaseDialog =
1068 function() {
1069 	if (!this._newBriefcaseDialog) {
1070 		AjxDispatcher.require(["BriefcaseCore", "Briefcase"]);
1071 		this._newBriefcaseDialog = new ZmNewBriefcaseDialog(this._shell);
1072 	}
1073 	return this._newBriefcaseDialog;
1074 };
1075 
1076 /**
1077  * Gets the find-and-replace dialog.
1078  *
1079  * @return	{ZmFindnReplaceDialog}	the find-and-replace dialog
1080  */
1081 ZmAppCtxt.prototype.getReplaceDialog =
1082 function() {
1083 	if (!this._replaceDialog) {
1084 		AjxDispatcher.require("Share");
1085 		this._replaceDialog = new ZmFindnReplaceDialog(this._shell);
1086 	}
1087 	return this._replaceDialog;
1088 };
1089 
1090 /**
1091  * Gets the debug log dialog.
1092  *
1093  * @return	{ZmDebugLogDialog}		the debug log dialog
1094  */
1095 ZmAppCtxt.prototype.getDebugLogDialog =
1096 function() {
1097 	if (!this._debugLogDialog) {
1098 		AjxDispatcher.require("Extras");
1099 		this._debugLogDialog = new ZmDebugLogDialog(this._shell);
1100 	}
1101 	return this._debugLogDialog;
1102 };
1103 
1104 /**
1105  * Gets the root tab group.
1106  *
1107  * @return	{DwtTabGroup}	the root tab group
1108  */
1109 ZmAppCtxt.prototype.getRootTabGroup =
1110 function() {
1111 	if (this.isChildWindow) {
1112 		if (!this._childWinTabGrp) {
1113 			this._childWinTabGrp = new DwtTabGroup("CHILD_WINDOW");
1114 		}
1115 	} else {		
1116 		if (!this._rootTabGrp) {
1117 			this._rootTabGrp = new DwtTabGroup("ROOT");
1118 		}
1119 	}
1120 	return this.isChildWindow ? this._childWinTabGrp : this._rootTabGrp;
1121 };
1122 
1123 /**
1124  * Gets the shell.
1125  *
1126  * @return	{DwtShell}	the shell
1127  */
1128 ZmAppCtxt.prototype.getShell =
1129 function() {
1130 	return this._shell;
1131 };
1132 
1133 /**
1134  * Sets the shell.
1135  *
1136  * @param	{DwtShell}	the shell
1137  */
1138 ZmAppCtxt.prototype.setShell =
1139 function(shell) {
1140 	this._shell = shell;
1141 };
1142 
1143 /**
1144  * Gets the active account.
1145  *
1146  * @return	{ZmZimbraAccount}	the active account
1147  */
1148 ZmAppCtxt.prototype.getActiveAccount =
1149 function() {
1150 	return this.isChildWindow
1151 		? parentAppCtxt.accountList.activeAccount
1152 		: this.accountList.activeAccount;
1153 };
1154 
1155 /**
1156  * Gets the active account.
1157  *
1158  * @return	{ZmZimbraAccount}	the active account
1159  */
1160 ZmAppCtxt.prototype.isExternalAccount =
1161 function() {
1162 	return this.get(ZmSetting.IS_EXTERNAL);
1163 };
1164 
1165 /*
1166  * This is a list of Aspell (Ver. 0.61) support locale from the result of the following command:
1167  *   /opt/zimbra/aspell/bin/aspell dump dicts
1168  *      (use only the items whose format is "<Primary-tag> *( "_" <Subtag> )")
1169  * When Aspell is upgraded and more locales are added, please update this list too.
1170  */
1171 ZmAppCtxt.AVAILABLE_DICTIONARY_LOCALES = ["ar", "da", "de", "de_AT", "de_CH", "de_DE", "en", "en_CA", "en_GB", "en_US", "es", "fr", "fr_CH", "fr_FR", "hi", "hu", "it", "nl", "pl", "pt_BR", "ru", "sv"];
1172 
1173 /**
1174  * Gets the availability of the spell check feature based on the current locale and user's configuration
1175  *
1176  * @return     {Boolean}       <code>true</code> if the spell checker is available.
1177  */
1178 ZmAppCtxt.prototype.isSpellCheckerAvailable = function () {
1179 
1180 	if (!appCtxt.get(ZmSetting.SPELL_CHECK_ENABLED)) {
1181 		return false;
1182 	}
1183 
1184 	if (typeof this._spellCheckAvailable !== 'undefined') {
1185 		return this._spellCheckAvailable;
1186 	}
1187 
1188 	this._spellCheckAvailable = false;
1189 	var myLocale = appCtxt.get(ZmSetting.LOCALE_NAME);
1190 	var myDefaultDictionaryName = appCtxt.get(ZmSetting.SPELL_DICTIONARY);
1191 
1192 	var myLanguage = myLocale.split('_')[0];
1193 
1194 	var dictLocales = ZmAppCtxt.AVAILABLE_DICTIONARY_LOCALES;
1195 	var ln = dictLocales.length;
1196 
1197 	for (var i = 0; i < ln; i++) {
1198 		var dictLocale = dictLocales[i];
1199 		if (dictLocale === myLocale ||
1200 		    dictLocale === myLanguage ||
1201 		    dictLocale === myDefaultDictionaryName) {
1202 			this._spellCheckAvailable = true;
1203 			break;
1204 		}
1205 	}
1206 
1207 	return this._spellCheckAvailable;
1208 }
1209 
1210 /**
1211  * Gets the identity collection.
1212  * 
1213  * @param	{ZmZimbraAccount}	account		the account
1214  * @return	{ZmIdentityCollection}	the identity collection
1215  */
1216 ZmAppCtxt.prototype.getIdentityCollection = function(account) {
1217 
1218     var context = this.isChildWindow ? window && window.opener : window;
1219 	return context && context.AjxDispatcher.run("GetIdentityCollection", account);
1220 };
1221 
1222 /**
1223  * Gets the data source collection.
1224  * 
1225  * @param	{ZmZimbraAccount}	account		the account
1226  * @return	{ZmModel}	the data source collection
1227  */
1228 ZmAppCtxt.prototype.getDataSourceCollection = function(account) {
1229 
1230 	var context = this.isChildWindow ? window && window.opener : window;
1231 	return context && context.AjxDispatcher.run("GetDataSourceCollection", account);
1232 };
1233 
1234 /**
1235  * Gets the signature collection.
1236  * 
1237  * @param	{ZmZimbraAccount}	account		the account
1238  * @return	{ZmSignatureCollection}	the signature collection
1239  */
1240 ZmAppCtxt.prototype.getSignatureCollection = function(account) {
1241 
1242     var context = this.isChildWindow ? window && window.opener : window;
1243 	return context && context.AjxDispatcher.run("GetSignatureCollection", account);
1244 };
1245 
1246 
1247 ZmAppCtxt.prototype.killMarkReadTimer =
1248 function() {
1249 	if (this.markReadActionId > 0) {
1250 		AjxTimedAction.cancelAction(this.markReadActionId);
1251 		this.markReadActionId = -1;
1252 	}
1253 };
1254 /**
1255  * Gets the organizer tree.
1256  * 
1257  * @param	{ZmOrganizer.FOLDER|ZmOrganizer.TAG|ZmOrganizer.ZIMLET}	type	the type
1258  * @param	{ZmZimbraAccount}	account		the account
1259  * @return	{ZmTree}		the tree
1260  * @see		#getFolderTree
1261  * @see		#getTagTree
1262  * @see		#getZimletTree
1263  */
1264 ZmAppCtxt.prototype.getTree =
1265 function(type, account) {
1266 	if (this.isChildWindow) {
1267 		return parentAppCtxt.getTree(type, account);
1268 	}
1269 
1270 	var al = this.accountList;
1271 	var id = account
1272 		? account.id
1273 		: al.activeAccount ? al.activeAccount.id : ZmAccountList.DEFAULT_ID;
1274 
1275 	var acct = al.getAccount(id);
1276 	return acct && acct.trees[ZmOrganizer.TREE_TYPE[type]];
1277 };
1278 
1279 /**
1280  * Sets the organizer tree.
1281  * 
1282  * @param	{ZmOrganizer.FOLDER|ZmOrganizer.TAG|ZmOrganizer.ZIMLET}	type	the type
1283  * @param	{ZmTree}	tree		the tree
1284  * @param	{ZmZimbraAccount}	account		the account
1285  * @see		#getTree
1286  */
1287 ZmAppCtxt.prototype.setTree =
1288 function(type, tree, account) {
1289 	var al = this.accountList;
1290 	var id = account
1291 		? account.id
1292 		: al.activeAccount ? al.activeAccount.id : ZmAccountList.DEFAULT_ID;
1293 
1294 
1295 	var acct = this.accountList.getAccount(id);
1296 	if (acct) {
1297 		acct.trees[type] = tree;
1298 	}
1299 };
1300 
1301 /**
1302  * Gets the folder organizer tree.
1303  * 
1304  * @param	{ZmZimbraAccount}	account		the account
1305  * @return	{ZmFolderTree}		the tree
1306  * @see		#getTree
1307  */
1308 ZmAppCtxt.prototype.getFolderTree =
1309 function(account) {
1310     return this.getTree(ZmOrganizer.FOLDER, account);
1311 };
1312 
1313 /**
1314  * Gets the tag organizer tree.
1315  * 
1316  * @param	{ZmZimbraAccount}	account		the account
1317  * @return	{ZmTagTree}		the tree
1318  * @see		#getTree
1319  */
1320 ZmAppCtxt.prototype.getTagTree =
1321 function(account) {
1322 	return this.getTree(ZmOrganizer.TAG, account);
1323 };
1324 
1325 /**
1326  * Gets the tag organizer tree's root.
1327  *
1328  * @param	{ZmItem}	item		item to look up the account of for and get the account tag list.
1329  * @return	{ZmTag}		the root of the tree, which is also a list.
1330  */
1331 ZmAppCtxt.prototype.getAccountTagList =
1332 function(item) {
1333 	var account = (item && appCtxt.multiAccounts) ? item.getAccount() : null;
1334 
1335 	return this.getTagTree(account).root;
1336 };
1337 
1338 
1339 /**
1340  * Gets the zimlet organizer tree.
1341  * 
1342  * @param	{ZmZimbraAccount}	account		the account
1343  * @return	{ZmFolderTree}		the tree
1344  * @see		#getTree
1345  */
1346 ZmAppCtxt.prototype.getZimletTree =
1347 function(account) {
1348 	return this.getTree(ZmOrganizer.ZIMLET, account);
1349 };
1350 
1351 /**
1352  * Gets the username (which is an email address).
1353  * 
1354  * @param	{ZmZimbraAccount}	account		the account
1355  * @return	{String}		the username
1356  */
1357 ZmAppCtxt.prototype.getUsername =
1358 function(account) { 
1359 	return this.get(ZmSetting.USERNAME, account);
1360 };
1361 
1362 /**
1363  * Gets the user domain.
1364  * 
1365  * @param	{ZmZimbraAccount}	account		the account
1366  * @return	{String}		the user domain
1367  */
1368 ZmAppCtxt.prototype.getUserDomain =
1369 function(account) {
1370 	if (!this.userDomain) {
1371         var username = this.getUsername(account);
1372 		if (username) {
1373 			var parts = username.split("@");
1374 			this.userDomain = (parts && parts.length) ? parts[1] : "";
1375 		}
1376 	}
1377 	return this.userDomain;
1378 };
1379 
1380 /**
1381  * Gets the upload frame id.
1382  * 
1383  * @return	{String}		the frame id
1384  */
1385 ZmAppCtxt.prototype.getUploadFrameId =
1386 function() {
1387 	if (!this._uploadManagerIframeId) {
1388 		var iframeId = Dwt.getNextId();
1389 		var html = [ "<iframe name='", iframeId, "' id='", iframeId,
1390 			     "' src='", (AjxEnv.isIE && location.protocol == "https:") ? appContextPath+"/public/blank.html" : "javascript:\"\"",
1391 			     "' style='position: absolute; top: 0; left: 0; visibility: hidden'></iframe>" ];
1392 		var div = document.createElement("div");
1393 		div.innerHTML = html.join("");
1394 		document.body.appendChild(div.firstChild);
1395 		this._uploadManagerIframeId = iframeId;
1396 	}
1397 	return this._uploadManagerIframeId;
1398 };
1399 
1400 ZmAppCtxt.prototype.reloadAppCache =
1401 function(force, retryOnError) {
1402 	AjxDebug.println(AjxDebug.OFFLINE, "reloadAppCache :: " + AjxDebug._getTimeStamp());
1403     if (this.isWebClientOfflineSupported || force) {
1404 		var localOfflineBrowserKey = localStorage.getItem(ZmSetting.WEBCLIENT_OFFLINE_BROWSER_KEY);
1405 		//If application cache status is downloading browser is already downloading the resources mentioned in the manifest file. Resetting the cookie value will result in application cache error event "Manifest changed during update".
1406 		if (localOfflineBrowserKey && AjxEnv.supported.applicationcache && applicationCache.status !== applicationCache.DOWNLOADING) {
1407 			var cookieValue = localOfflineBrowserKey + "_" + new Date().getTime();
1408 			AjxCookie.setCookie(document, "ZM_OFFLINE_KEY", cookieValue, false, "/");
1409 		}
1410 		var manifestURL = appContextPath + "/appcache/images,common,dwt,msgview,login,zm,spellcheck,skin.appcache?";
1411         var urlParams = [];
1412         urlParams.push("v=" + window.cacheKillerVersion);
1413         urlParams.push("debug=" + window.appDevMode);
1414         urlParams.push("compress=" + !(window.appDevMode === true));
1415         urlParams.push("templates=only");
1416         manifestURL = encodeURIComponent(manifestURL + urlParams.join('&'));
1417         var offlineIframe = document.getElementById("offlineIframe");
1418         if (!offlineIframe) {
1419             offlineIframe = document.createElement("iframe");
1420             offlineIframe.id = "offlineIframe";
1421             offlineIframe.style.display = "none";
1422             document.body.appendChild(offlineIframe);
1423         }
1424         if (offlineIframe) {
1425 			retryOnError = AjxUtil.isBoolean(retryOnError) ? retryOnError : true;
1426 			offlineIframe.src = "public/Offline.jsp?url=" + manifestURL + "&isFirefox=" + AjxEnv.isFirefox + "&retryOnError=" + retryOnError;
1427         }
1428     }
1429 };
1430 
1431 /**
1432  * Gets the upload manager.
1433  * 
1434  * @return	{Object}		the upload manager
1435  */
1436 ZmAppCtxt.prototype.getUploadManager =
1437 function() {
1438 	if (!this._uploadManager) {
1439 		// Create upload manager (for sending attachments)
1440 		this._uploadManager = new AjxPost(this.getUploadFrameId());
1441 	}
1442 	return this._uploadManager;
1443 };
1444 
1445 ZmAppCtxt.prototype.getZmUploadManager =
1446     function() {
1447         if (!this._zmUploadManager) {
1448             // Create upload manager (for sending attachments)
1449             AjxDispatcher.require("Extras");
1450             this._zmUploadManager = new ZmUploadManager();
1451         }
1452         return this._zmUploadManager;
1453     };
1454 
1455 /**
1456  * Gets the current search.
1457  * 
1458  * @return	{ZmSearch}		the current search
1459  */
1460 ZmAppCtxt.prototype.getCurrentSearch =
1461 function() {
1462 	var app = this.getCurrentApp();
1463 	if (app && app.currentSearch) {
1464 		return app.currentSearch;
1465 	}
1466 	var ctlr = this.getCurrentController();
1467 	return ctlr && ctlr._currentSearch;
1468 };
1469 
1470 /**
1471  * Gets the current view id. If we're showing search results, returns the ID of the
1472  * view within the search results (rather than the ID of the search results).
1473  * 
1474  * @return	{String}		the current view id
1475  */
1476 ZmAppCtxt.prototype.getCurrentViewId =
1477 function() {
1478 	var viewId = this.getAppViewMgr().getCurrentViewId();
1479 	if (viewId && viewId.indexOf(ZmId.VIEW_SEARCH_RESULTS) === 0) {
1480 		viewId = this.getCurrentController().getCurrentViewId();
1481 	}
1482 	return viewId;
1483 };
1484 
1485 /**
1486  * Gets the current view type. If we're showing search results, returns the type of the
1487  * view within the search results (rather than the type of the search results).
1488  * 
1489  * @return	{String}		the current view type
1490  */
1491 ZmAppCtxt.prototype.getCurrentViewType =
1492 function() {
1493 	var viewType = this.getAppViewMgr().getCurrentViewType();
1494 	if (viewType && viewType.indexOf(ZmId.VIEW_SEARCH_RESULTS) === 0) {
1495 		viewType = this.getCurrentController().getCurrentViewType();
1496 	}
1497 	return viewType;
1498 };
1499 
1500 /**
1501  * Extracts the view type from a view ID.
1502  * 
1503  * @param	{string}	viewId		a view ID
1504  * @return	{String}	the view type
1505  */
1506 ZmAppCtxt.prototype.getViewTypeFromId =
1507 function(viewId) {
1508 	var array = viewId && viewId.split(ZmController.SESSION_ID_SEP);
1509 	return array ? array[0] : "";
1510 };
1511 
1512 /**
1513  * Gets the current view.
1514  * 
1515  * @return	{DwtComposite}		the current view
1516  */
1517 ZmAppCtxt.prototype.getCurrentView =
1518 function() {
1519 	return this.getAppViewMgr().getCurrentView();
1520 };
1521 
1522 /**
1523  * Gets the current controller.
1524  * 
1525  * @return	{ZmController}		the current controller
1526  */
1527 ZmAppCtxt.prototype.getCurrentController =
1528 function() {
1529 	var view = this.getCurrentView();
1530 	return (view && view.getController) ? view.getController() : null;
1531 };
1532 
1533 /**
1534  * Sets the current list.
1535  * 
1536  * @param	{ZmList}	list		the current list
1537  */
1538 ZmAppCtxt.prototype.setCurrentList =
1539 function(list) {
1540 	this._list = list;
1541 };
1542 
1543 /**
1544  * Gets the current list.
1545  * 
1546  * @return	{ZmList}		the current list
1547  */
1548 ZmAppCtxt.prototype.getCurrentList =
1549 function() {
1550 	var ctlr = this.getCurrentController();
1551 	return (ctlr && ctlr.getList) ? ctlr.getList() : this._list ? this._list : null;
1552 };
1553 
1554 ZmAppCtxt.prototype.getActionController =
1555 function() {
1556 	if (!this._actionController && !this.isChildWindow) {
1557 		this._actionController = new ZmActionController();
1558 	}
1559 	return this._actionController;
1560 };
1561 
1562 /**
1563  * Gets a new window.
1564  * 
1565  * @param	{Boolean}	fullView		<code>true</code> to include the full version
1566  * @param	{int}		width			the width
1567  * @param	{int}		height			the height
1568  * @param   {String}    name            window name
1569  */
1570 ZmAppCtxt.prototype.getNewWindow = 
1571 function(fullVersion, width, height, name) {
1572 	// build url
1573 	var url = [];
1574 	var i = 0;
1575 	url[i++] = document.location.protocol;
1576 	url[i++] = "//";
1577 	url[i++] = location.hostname;
1578 	url[i++] = (!location.port || location.port == "80") ? "" : (":" + location.port);
1579 	url[i++] = appContextPath;
1580 	url[i++] = "/public/launchNewWindow.jsp?skin=";
1581 	url[i++] = appCurrentSkin;
1582 	url[i++] = "&localeId=";
1583 	url[i++] = AjxEnv.DEFAULT_LOCALE || "";
1584 	if (fullVersion) {
1585 		url[i++] = "&full=1";
1586 	}
1587 	if (window.appDevMode) {
1588 		url[i++] = "&dev=1";
1589 	}
1590     if (window.appCoverageMode) {
1591         url[i++] = "&coverage=1";
1592     }
1593 	this.__childWindowId = (this.__childWindowId+1) || 0;
1594 	url[i++] = "&childId=" + this.__childWindowId;
1595 
1596     name = name || "_blank";
1597 	width = width || 705;
1598 	height = height || 465;
1599 	var args = ["height=", height, ",width=", width, ",location=no,menubar=no,resizable=yes,scrollbars=no,status=yes,toolbar=no"].join("");
1600 	if (window.appDevMode) {
1601 		args = ["height=", height, ",width=", width, ",location=yes,menubar=yes,resizable=yes,scrollbars=no,status=yes,toolbar=yes"].join("");
1602 	}
1603 	var newWin = window.open(url.join(""), name, args);
1604 	this.handlePopupBlocker(newWin);
1605 	if(newWin) {
1606 		// add this new window to global list so parent can keep track of child windows!
1607 		return this.getAppController().addChildWindow(newWin, this.__childWindowId);
1608 	}
1609 };
1610 
1611 /**
1612  * Handle Popup bloker for a given window
1613  * @param {Object}	win  A Window object
1614  */
1615 ZmAppCtxt.prototype.handlePopupBlocker =
1616 function(win) {
1617 	if (!win) {
1618 		this.setStatusMsg(ZmMsg.popupBlocker, ZmStatusView.LEVEL_CRITICAL);
1619 	} else if (win && AjxEnv.isChrome) {
1620 		setTimeout(function() { 
1621 					if (win.innerHeight == 0)
1622 						appCtxt.setStatusMsg(ZmMsg.popupBlocker, ZmStatusView.LEVEL_CRITICAL);
1623 				}, 200);
1624 		};
1625 };
1626 
1627 /**
1628  * Caches the given key/value set.
1629  * 
1630  * @param	{Object}	key		the key
1631  * @param	{Object}	value	the value
1632  * @private
1633  */
1634 ZmAppCtxt.prototype.cacheSet =
1635 function(key, value) {
1636 	this._itemCache[key] = value;
1637 	delete this._itemCacheDeferred[key];
1638 };
1639 
1640 /**
1641  * Defers caching the given key set.
1642  * 
1643  * @param	{Object}	key		the key
1644  * @param	{String}	appName	the application name
1645  * @private
1646  */
1647 ZmAppCtxt.prototype.cacheSetDeferred =
1648 function(key, appName) {
1649 	this._itemCache[key] = this._itemCacheDeferred;
1650 	this._itemCacheDeferred[key] = appName;
1651 };
1652 
1653 /**
1654  * Gets the key from cache.
1655  * 
1656  * @param	{Object}	key		the key
1657  * @return	{Object}	the value
1658  * @private
1659  */
1660 ZmAppCtxt.prototype.cacheGet =
1661 function(key) {
1662 	var value = this._itemCache[key];
1663 	if (value === this._itemCacheDeferred) {
1664 		var appName = this._itemCacheDeferred[key];
1665 		this.getApp(appName).createDeferred();
1666 		value = this._itemCache[key];
1667 	}
1668 	return value;
1669 };
1670 
1671 /**
1672  * Removes the key from cache.
1673  * 
1674  * @param	{Object}	key		the key
1675  * @private
1676  */
1677 ZmAppCtxt.prototype.cacheRemove =
1678 function(key) {
1679 	delete this._itemCache[key];
1680 	delete this._itemCacheDeferred[key];
1681 };
1682 
1683 /**
1684  * Gets the key from cache by id.
1685  * 
1686  * @param	{String}	id		the id
1687  * @return	{Object}	the value
1688  * @private
1689  */
1690 ZmAppCtxt.prototype.getById = function(id) {
1691 
1692     // Try parent cache if we're a child window and we don't have it
1693 	return this.cacheGet(id) || (this.isChildWindow && window && window.opener && window.opener.appCtxt && window.opener.appCtxt.getById(id));
1694 };
1695 
1696 /**
1697  * Gets the keyboard manager
1698  * 
1699  * @return	{DwtKeyboardMgr}		the keyboard manager
1700  */
1701 ZmAppCtxt.prototype.getKeyboardMgr =
1702 function() {
1703 	return this._shell.getKeyboardMgr();
1704 };
1705 
1706 /**
1707  * Gets the history manager.
1708  * 
1709  * @return	{AjxHistoryMgr}		the history manager
1710  */
1711 ZmAppCtxt.prototype.getHistoryMgr =
1712 function() {
1713 	if (!this._historyMgr) {
1714 		this._historyMgr = new AjxHistoryMgr();
1715 	}
1716 	return this._historyMgr;
1717 };
1718 
1719 /**
1720  * Checks if the zimlets are present.
1721  * 
1722  * @return	{Boolean}		<code>true</code> if zimlets are present
1723  */
1724 ZmAppCtxt.prototype.zimletsPresent =
1725 function() {
1726 	return this._zimletsPresent;
1727 };
1728 
1729 /**
1730  * Sets if the zimlets are present.
1731  * 
1732  * @param	{Boolean}	zimletsPresent		if <code>true</code>, zimlets are present
1733  */
1734 ZmAppCtxt.prototype.setZimletsPresent =
1735 function(zimletsPresent) {
1736 	this._zimletsPresent = zimletsPresent;
1737 };
1738 
1739 /**
1740  * Gets the zimlet manager
1741  * 
1742  * @return	{ZmZimletMgr}	the zimlet manager
1743  */
1744 ZmAppCtxt.prototype.getZimletMgr =
1745 function() {
1746 	if (!this._zimletMgr) {
1747 		AjxDispatcher.require("Zimlet");
1748 		if (!this._zimletMgr) // Must re-check here, because if this function is called a second time before the "Zimlet" package is loaded, both calls want to set this._zimletMgr, which must NEVER happen (Issue first located in bug #41338)
1749 			this._zimletMgr = new ZmZimletMgr();
1750 	}
1751 	return this._zimletMgr;
1752 };
1753 
1754 /**
1755  * Checks if zimlets are loaded.
1756  * 
1757  * @return	{Boolean}		<code>true</code> if zimlets are loaded
1758  */
1759 ZmAppCtxt.prototype.areZimletsLoaded =
1760 function() {
1761 	return this._zimletsLoaded;
1762 };
1763 
1764 /**
1765  * Adds a listener to the zimlets loaded event.
1766  * 
1767  * @param	{AjxCallback}	listener		the listener
1768  * @param	{int}		index		the index to where to add the listener
1769  * @return	{Boolean}	<code>true</code> if the listener is added; <code>false</code> otherwise
1770  */
1771 ZmAppCtxt.prototype.addZimletsLoadedListener =
1772 function(listener, index) {
1773 	if (!this._zimletsLoaded) {
1774 		return this._evtMgr.addListener(ZmAppCtxt._ZIMLETS_EVENT, listener, index);
1775 	}
1776 };
1777 
1778 /**
1779  * Checks is all zimlets are loaded.
1780  * 
1781  * @return	{Boolean}	<code>true</code> if all zimlets are loaded
1782  */
1783 ZmAppCtxt.prototype.allZimletsLoaded =
1784 function() {
1785 	this._zimletsLoaded = true;
1786 	if (this._zimletMgr && !this.isChildWindow && appCtxt.get(ZmSetting.PORTAL_ENABLED)) {
1787 		var portletMgr = this.getApp(ZmApp.PORTAL).getPortletMgr();
1788 		if (portletMgr) {
1789 			portletMgr.allZimletsLoaded();
1790 		}
1791 	}
1792 
1793 	if (this._evtMgr.isListenerRegistered(ZmAppCtxt._ZIMLETS_EVENT)) {
1794 		this._evtMgr.notifyListeners(ZmAppCtxt._ZIMLETS_EVENT, new ZmEvent());
1795 		this._evtMgr.removeAll(ZmAppCtxt._ZIMLETS_EVENT);
1796 	}
1797 };
1798 
1799 /**
1800  * Notifies zimlets if they are present and loaded.
1801  *
1802  * @param {String}		event		the zimlet event (called as a zimlet function)
1803  * @param {Array}		args		a list of args to the function
1804  * @param {Hash}	options			a hash of options
1805  * @param {Boolean}	options.noChildWindow		if <code>true</code>, skip notify if we are in a child window
1806  * @param	{Boolean}	options.waitUntilLoaded	if <code>true</code> and zimlets are not yet loaded, add a listener so that notify happens on load
1807  * @return	{Boolean} Returns <code>true</code> if at least one Zimlet handles the notification
1808  */
1809 ZmAppCtxt.prototype.notifyZimlets =
1810 function(event, args, options) {
1811 	this.notifySkin(event, args, options); // Also notify skin
1812 
1813 	var context = this.isChildWindow ? parentAppCtxt : this;
1814 
1815 	if (options && options.noChildWindow && this.isChildWindow) { return false; }
1816 
1817 	if (!context.areZimletsLoaded()) {
1818 		if (options && options.waitUntilLoaded) {
1819 			context.addZimletsLoadedListener(new AjxListener(this, this.notifyZimlets, [event, args]));
1820 		}
1821 		return false;
1822 	}
1823 
1824 	return this.getZimletMgr().notifyZimlets(event, args);
1825 };
1826 
1827 ZmAppCtxt.prototype.notifyZimlet =
1828 function(zimletName, event, args, options) {
1829 	if (options && options.noChildWindow && this.isChildWindow) { return false; }
1830 	return this.getZimletMgr().notifyZimlet(zimletName, event, args);
1831 };
1832 
1833 ZmAppCtxt.prototype.notifySkin =
1834 function(event, args, options) {
1835 	var context = this.isChildWindow ? parentAppCtxt : this;
1836 	if (options && options.noChildWindow && this.isChildWindow) { return; }
1837 	try {
1838 		return window.skin && AjxUtil.isFunction(window.skin.handleNotification) && window.skin.handleNotification(event, args);
1839 	} catch (e) {}
1840 };
1841 
1842 
1843 /**
1844  * Gets the calendar manager.
1845  * 
1846  * @return	{ZmCalMgr}	the calendar manager
1847  */
1848 ZmAppCtxt.prototype.getCalManager =
1849 function() {
1850 	if (!this._calMgr) {
1851         AjxDispatcher.require("Startup2");
1852 		this._calMgr = new ZmCalMgr(this._shell);
1853 	}
1854 	return this._calMgr;
1855 };
1856 
1857 ZmAppCtxt.prototype.updateOfflineAppt = function(msgId, field, value, nullData, callback) {
1858 	var calMgr = appCtxt.getCalManager();
1859 	if (calMgr) {
1860 		var calViewController = calMgr && calMgr.getCalViewController();
1861 		if (calViewController) {
1862 			var apptCache = calViewController.getApptCache();
1863 			if (apptCache) {
1864 				apptCache.updateOfflineAppt(msgId, field, value, nullData, callback);
1865 			}
1866 		}
1867 	}
1868 }
1869 
1870 /**
1871  * Gets the task manager.
1872  *
1873  * @return	{ZmTaskMgr}	the task manager
1874  */
1875 ZmAppCtxt.prototype.getTaskManager =
1876 function() {
1877 	if (!this._taskMgr) {
1878 		this._taskMgr = new ZmTaskMgr(this._shell);
1879 	}
1880 	return this._taskMgr;
1881 };
1882 
1883 
1884 /**
1885  * Gets the ACL.
1886  * 
1887  * @param	{ZmZimbrAccount}	account		the account
1888  * @param	{AjxCallback}	callback	the callback
1889  * @return	{ZmAccessControlList}	the ACL
1890  */
1891 ZmAppCtxt.prototype.getACL =
1892 function(account, callback) {
1893 	var al = this.accountList;
1894 	var id = account
1895 		? account.id
1896 		: al.activeAccount ? al.activeAccount.id : ZmAccountList.DEFAULT_ID;
1897 
1898 	var acct = al.getAccount(id);
1899 	return acct && acct.acl;
1900 };
1901 
1902 /**
1903  * Gets the shortcut hint.
1904  *
1905  * @param {String}		keyMap		the key map
1906  * @param {String}		shortcut	the shortcut action
1907  * @return	{String}	the hint
1908  */
1909 ZmAppCtxt.prototype.getShortcutHint =
1910 function(keyMap, shortcut) {
1911 	
1912 	var text = null;
1913 	keyMap = keyMap || "global";
1914 	while (!text && keyMap) {
1915 		var scKey = [keyMap, shortcut, "display"].join(".");
1916 		var text = AjxKeys[scKey] || ZmKeys[scKey];
1917 		if (text) {
1918 			var list = text.split(/;\s*/);
1919 			var sc = list[0];	// use first shortcut in list
1920 			if (!sc) { return null; }
1921 			sc = sc.replace(/\b[A-Z]\b/g, function(let) { return let.toLowerCase(); });
1922 			text = [" [", sc.replace(",", ""), "]"].join("");
1923 		} else {
1924 			var key = [keyMap, "INHERIT"].join(".");
1925 			keyMap = AjxKeys[key] || ZmKeys[key];
1926 		}
1927 	}
1928 
1929 	return text;
1930 };
1931 
1932 /**
1933  * Gets the shortcuts panel.
1934  * 
1935  * @return	{ZmShortcutsPanel}	the shortcuts panel
1936  */
1937 ZmAppCtxt.prototype.getShortcutsPanel =
1938 function() {
1939 	if (!this._shortcutsPanel) {
1940 		AjxDispatcher.require(["PreferencesCore", "Preferences"]);
1941 		var style = this.isChildWindow ? ZmShortcutList.WINDOW_STYLE : ZmShortcutList.PANEL_STYLE;
1942 		this._shortcutsPanel = new ZmShortcutsPanel(style);
1943 	}
1944 	return this._shortcutsPanel;
1945 };
1946 
1947 /**
1948  * Opens a new change password window
1949  *
1950  */
1951 ZmAppCtxt.prototype.getChangePasswordWindow =
1952 function(ev) {
1953     var url = appCtxt.get(ZmSetting.CHANGE_PASSWORD_URL);
1954 
1955     	if (!url) {
1956     		var isHttp	= appCtxt.get(ZmSetting.PROTOCOL_MODE) == ZmSetting.PROTO_HTTP;
1957     		var proto	= isHttp ? ZmSetting.PROTO_HTTP : ZmSetting.PROTO_HTTPS;
1958     		var port	= appCtxt.get(isHttp ? ZmSetting.HTTP_PORT : ZmSetting.HTTPS_PORT);
1959     		var path	= appContextPath+"/h/changepass";
1960 
1961     		var publicUrl = appCtxt.get(ZmSetting.PUBLIC_URL);
1962     		if (publicUrl) {
1963     			var parts = AjxStringUtil.parseURL(publicUrl);
1964     			path = parts.path + "/h/changepass";
1965     			var switchMode = (parts.protocol == "http" && proto == ZmSetting.PROTO_HTTPS);
1966     			proto = switchMode ? proto : parts.protocol;
1967     			port = switchMode ? port : parts.port;
1968     		}
1969 			var qsArgs = {skin: appCurrentSkin};
1970     		url = AjxUtil.formatUrl({protocol: proto, port: port, path: path, qsReset: true, qsArgs: qsArgs});
1971     	}
1972 
1973     	var args  = "height=465,width=705,location=no,menubar=no,resizable=yes,scrollbars=no,status=yes,toolbar=no";
1974     	window.open(url,'ChangePasswordWindow', args);
1975 };
1976 
1977 /**
1978  * Gets the skin hint for the given argument(s), which will be used to look
1979  * successively down the properties chain.
1980  * 
1981  * <p>
1982  * For example, <code>getSkinHint("a", "b")</code> will return the value of <code>skin.hints.a.b</code>.
1983  * </p>
1984  * 
1985  * @return	{String}	the skin hint
1986  */
1987 ZmAppCtxt.prototype.getSkinHint =
1988 function() {
1989 	if (arguments.length == 0) return "";
1990 	
1991 	var cur = window.skin && window.skin.hints;
1992 	if (!cur) { return ""; }
1993 	for (var i = 0; i < arguments.length; i++) {
1994 		var arg = arguments[i];
1995 		if (!cur[arg]) { return ""; }
1996 		cur = cur[arg];
1997 	}
1998 	return cur;
1999 };
2000 
2001 /**
2002  * Gets the auto completer.
2003  * 
2004  * @return	{ZmAutocomplete}	the auto completer
2005  */
2006 ZmAppCtxt.prototype.getAutocompleter =
2007 function() {
2008 	if (!this._autocompleter) {
2009 		this._autocompleter = new ZmAutocomplete(null);
2010 	}
2011 	return this._autocompleter;
2012 };
2013 
2014 /**
2015  * Checks if my address belongs to the current user (include aliases).
2016  * 
2017  * @param {String}		addr			            the address
2018  * @param {Boolean}		allowLocal		            if <code>true</code>, domain is not required
2019  * @param {Boolean}		excludeAllowFromAddress		if <code>true</code>, addresses in zimbraAllowFromAddresses are ignored
2020  * @return	{Boolean}		<code>true</code> if the given address belongs to the current user; <code>false</code> otherwise
2021  */
2022 ZmAppCtxt.prototype.isMyAddress =
2023 function(addr, allowLocal, excludeAllowFromAddress) {
2024 
2025 	if (allowLocal && (addr.indexOf('@') == -1)) {
2026 		addr = [addr, this.getUserDomain()].join("@");
2027 	}
2028 	
2029 	if (addr == this.get(ZmSetting.USERNAME)) {
2030 		return true;
2031 	}
2032 
2033 	var allAddresses;
2034     if(excludeAllowFromAddress){
2035         allAddresses= appCtxt.get(ZmSetting.MAIL_ALIASES);
2036     }
2037     else
2038     {
2039         allAddresses= appCtxt.get(ZmSetting.MAIL_ALIASES).concat(appCtxt.get(ZmSetting.ALLOW_FROM_ADDRESSES));
2040     }
2041 
2042 	if (allAddresses && allAddresses.length) {
2043 		for (var i = 0; i < allAddresses.length; i++) {
2044 			if (addr == allAddresses[i])
2045 				return true;
2046 		}
2047 	}
2048 
2049 	return false;
2050 };
2051 
2052 /**
2053  * Gets the overview ID, prepending account name if multi-account.
2054  *
2055  * @param {Array}		parts		an array of {String} id components
2056  * @param {ZmAccount}	account		the account
2057  * @return	{String}	the id
2058  */
2059 ZmAppCtxt.prototype.getOverviewId =
2060 function(parts, account) {
2061 
2062 	var id = (parts instanceof Array) ? parts.join("_") : parts;
2063 
2064 	if (appCtxt.multiAccounts && (account !== null)) {
2065 		account = account || appCtxt.getActiveAccount();
2066 		id = [account.name, id].join(":");
2067 	}
2068 
2069 	return id;
2070 };
2071 
2072 /**
2073  * Gets the autocomplete cache for the given parameters, optionally creating one.
2074  *
2075  * @param	{String}	acType		the item type
2076  * @param	{String}	str			the autocomplete string
2077  * @param	{ZmAccount}	account		the account
2078  * @param	{Boolean}	create		if <code>true</code>, create a cache if none found
2079  */
2080 ZmAppCtxt.prototype.getAutocompleteCache =
2081 function(acType, str, account, create) {
2082 
2083 	var cache = null;
2084 	var acct = account || this.getActiveAccount();
2085 	if (acct) {
2086 		if (this._acCache[acct.id] && this._acCache[acct.id][acType]) {
2087 			cache = this._acCache[acct.id][acType][str];
2088 		}
2089 	}
2090 	if (!cache && create) {
2091 		if (acct && !this._acCache[acct.id]) {
2092 			this._acCache[acct.id] = {};
2093 		}
2094 		if (!this._acCache[acct.id][acType]) {
2095 			this._acCache[acct.id][acType] = {};
2096 		}
2097 		cache = this._acCache[acct.id][acType][str] = {};
2098 	}
2099 
2100 	return cache;
2101 };
2102 
2103 /**
2104  * Clears the autocomplete cache.
2105  *
2106  * @param	{String}	type		item type
2107  * @param	{ZmAccount}	account		the account
2108  */
2109 ZmAppCtxt.prototype.clearAutocompleteCache =
2110 function(type, account) {
2111 
2112 	var acct = account || appCtxt.getActiveAccount();
2113 	if (this._acCache[acct.id]) {
2114 		if (type) {
2115 			this._acCache[acct.id][type] = {};
2116 		} else {
2117 			this._acCache[acct.id][ZmAutocomplete.AC_TYPE_CONTACT]		=	{};
2118 			this._acCache[acct.id][ZmAutocomplete.AC_TYPE_LOCATION]		=	{};
2119 			this._acCache[acct.id][ZmAutocomplete.AC_TYPE_EQUIPMENT]	=	{};
2120 		}
2121 	}
2122 };
2123 
2124 ZmAppCtxt.prototype.getOutsideMouseEventMgr =
2125 function() {
2126 	return DwtOutsideMouseEventMgr.INSTANCE;
2127 };
2128 
2129 
2130 /**
2131 * @return Returns language specific charset. Currently supports only Japanese. 
2132 * Returns "Windows-31J" for Japanese or returns "UTF-8" for everything else
2133 */
2134 ZmAppCtxt.prototype.getCharset =
2135 function() {
2136 	var lang = AjxEnv.isIE ? window.navigator.systemLanguage : window.navigator.language;
2137 	//Currently only differs for Japanese, but can extend for different languages as/if we need it.
2138 	if((AjxEnv.DEFAULT_LOCALE == "ja" || lang == "ja") && AjxEnv.isWindows) {
2139 		return "Windows-31J";
2140 	} else {
2141 		return "UTF-8";
2142 	}
2143 };
2144 
2145 /**
2146  * Returns true if an address is an expandable DL.
2147  *  
2148  * @param {string}	addr	email address
2149  */
2150 ZmAppCtxt.prototype.isExpandableDL =
2151 function(addr) {
2152 	return addr && this._isExpandableDL[addr] && this.get("EXPAND_DL_ENABLED");
2153 };
2154 
2155 /**
2156  * Cache whether an address is an expandable DL.
2157  * 
2158  * @param {string}	addr			email address
2159  * @param {boolean}	isExpandableDL	if true, address is expandable DL
2160  * 
2161  * TODO: consider caching AjxEmailAddress objects by addr so we also save display name
2162  */
2163 ZmAppCtxt.prototype.setIsExpandableDL =
2164 function(addr, isExpandableDL) {
2165 	this._isExpandableDL[addr] = isExpandableDL;
2166 };
2167 
2168 ZmAppCtxt.prototype.getToolTipMgr =
2169 function() {
2170 	if (!this._toolTipMgr) {
2171 		this._toolTipMgr = new ZmToolTipMgr();
2172 	}
2173 	return this._toolTipMgr;
2174 };
2175 
2176 /**
2177  * Returns true if Prism and the user is online
2178  *
2179  */
2180 ZmAppCtxt.prototype.isZDOnline =
2181 function() {
2182     var ac = window["appCtxt"].getAppController();
2183     return !AjxEnv.isPrism || (ac._isPrismOnline && ac._isUserOnline);
2184 };
2185 
2186 /**
2187  * When using pre-auth window.opener.appCtxt may not be accessible.  This function
2188  * handles appCtxt assignment to avoid a permission denied error
2189  * @return {Object} ZmAppCtxt
2190  */
2191 ZmAppCtxt.handleWindowOpener = 
2192 function() {
2193 	try {
2194 		return window && window.opener && window.opener.appCtxt || appCtxt;
2195 	}
2196 	catch (ex) {
2197 		return appCtxt;
2198 	}
2199 };
2200 
2201 ZmAppCtxt.prototype.isWebClientOffline =
2202 function() {
2203     if (this.isWebClientOfflineSupported) {
2204         return ZmOffline.isServerReachable === false;
2205     }
2206     return false;
2207 };
2208 
2209 ZmAppCtxt.prototype.initWebOffline =
2210 function() {
2211     this.isWebClientOfflineSupported = false;
2212 	if (!AjxEnv.isOfflineSupported || !appCtxt.get(ZmSetting.WEBCLIENT_OFFLINE_ENABLED)) {
2213 		AjxDebug.println(AjxDebug.OFFLINE, "isWebClientOfflineSupported :: false");
2214         return;
2215     }
2216     var offlineBrowserKey = appCtxt.get(ZmSetting.WEBCLIENT_OFFLINE_BROWSER_KEY);
2217     var localOfflineBrowserKey = localStorage.getItem(ZmSetting.WEBCLIENT_OFFLINE_BROWSER_KEY);
2218     if (offlineBrowserKey && offlineBrowserKey.indexOf(localOfflineBrowserKey) !== -1) {
2219         this.isWebClientOfflineSupported = true;
2220         this.webClientOfflineHandler = new ZmOffline();
2221     }
2222 	AjxDebug.println(AjxDebug.OFFLINE, "isWebClientOfflineSupported :: "+ this.isWebClientOfflineSupported);
2223 };
2224 
2225 /**
2226  * Gets the offline settings dialog.
2227  *
2228  * @return	{ZmOfflineSettingsDialog}	offline settings dialog
2229  */
2230 ZmAppCtxt.prototype.getOfflineSettingsDialog =
2231 function() {
2232     if (!this._offlineSettingsDialog) {
2233         this._offlineSettingsDialog = new ZmOfflineSettingsDialog();
2234     }
2235     return this._offlineSettingsDialog;
2236 };
2237 
2238 /**
2239  * Returns true if the given ID is not local. That's the case if the ID has
2240  * an account part that is not the active account.
2241  *
2242  * @param {String|Number}   id
2243  * @returns {Boolean}   true if the given ID is not local
2244  */
2245 ZmAppCtxt.prototype.isRemoteId = function(id) {
2246 	id = String(id);
2247 	var acct = appCtxt.getActiveAccount();
2248 	return (id.indexOf(":") !== -1) && (id.indexOf(acct.id) !== 0);
2249 };
2250 
2251 /**
2252  * Returns the singleton AjxClipboard instance, if it is supported.
2253  *
2254  * @returns {AjxClipboard}
2255  */
2256 ZmAppCtxt.prototype.getClipboard = function() {
2257 	return AjxClipboard.isSupported() ? new AjxClipboard() : null;
2258 };
2259 
2260 /**
2261  * Checks a precondition which may be in one of several forms: a boolean, a settings constant, a function, or a list.
2262  *
2263  * @param {Boolean|String|Function|Array}   precondition    something that evaluates to true or false
2264  * @param {Boolean}                         listAny         (optional) if a list is provided, whether just one (instead of all) must be true
2265  *
2266  * @return boolean  false if the precondition evaluates to false or null, otherwise true
2267  */
2268 ZmAppCtxt.prototype.checkPrecondition = function(precondition, listAny) {
2269 
2270 	// Lack of a precondition evaluates to true
2271 	if (precondition === undefined) {
2272 		return true;
2273 	}
2274 
2275 	// A precondition of null should not happen
2276 	if (precondition === null) {
2277 		return false;
2278 	}
2279 
2280 	// Boolean speaks for itself
2281 	if (AjxUtil.isBoolean(precondition)) {
2282 		return precondition;
2283 	}
2284 
2285 	// Client setting: fetch value from settings
2286 	if (AjxUtil.isString(precondition) && ZmSetting.hasOwnProperty(precondition)) {
2287 		return appCtxt.get(precondition);
2288 	}
2289 
2290 	// Function: evaluate and return result
2291 	if (AjxUtil.isFunction(precondition)) {
2292 		return precondition();
2293 	}
2294 
2295 	// Array can be treated in one of two modes, where all have to be true, or just one does
2296 	if (AjxUtil.isArray(precondition)) {
2297 		for (var i = 0; i < precondition.length; i++) {
2298 			var result = this.checkPrecondition(precondition[i]);
2299 			if (listAny && result) {
2300 				return true;
2301 			}
2302 			if (!listAny && !result) {
2303 				return false;
2304 			}
2305 		}
2306 		return !listAny;
2307 	}
2308 
2309 	return true;
2310 };
2311 
2312 /**
2313  * Displays an error message in  dialog.
2314  *
2315  * @param {string}  errMsg      error message
2316  * @param {string}  details     (optional) additional info to show using Details button
2317  * @param {string}  style       (optional) style constant DwtMessageDialog.*_STYLE
2318  * @param {string}  title       (optional) title for dialog (other than one for the style)
2319  * @apram {boolean} noReport    do not add a Report button/function to the dialog (defaults to true)
2320  */
2321 ZmAppCtxt.prototype.showError = function(params) {
2322 
2323     params = params || {};
2324     var errMsg = params.errMsg || params;
2325     var dlg = this.getErrorDialog();
2326     dlg.reset();
2327     dlg.setMessage(errMsg, params.details, params.style || DwtMessageDialog.WARNING_STYLE, params.title);
2328     dlg.popup(null, params.noReport !== false);
2329 };
2330