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 contains the mail application class.
 27  */
 28 
 29 /**
 30  * Creates and initializes the mail application.
 31  * @constructor
 32  * @class
 33  * The mail app manages and displays mail messages. Messages may be grouped
 34  * into conversations. New messages are created through a composer.
 35  *
 36  * @param	{DwtControl}	container		the container
 37  * @param	{ZmController}	parentController	the parent window controller (set by the child window)
 38  * 
 39  * @author Conrad Damon
 40  * 
 41  * @extends		ZmApp
 42  */
 43 ZmMailApp = function(container, parentController) {
 44 	ZmApp.call(this, ZmApp.MAIL, container, parentController);
 45 
 46 	this._dataSourceCollection	= {};
 47 	this._identityCollection	= {};
 48 	this._signatureCollection	= {};
 49 
 50 	this.numEntries				= 0; // offline, initial sync
 51 	this.globalMailCount		= 0; // offline, new mail count
 52 
 53 	this._throttleStats = [];
 54 	this._addSettingsChangeListeners();
 55     AjxCore.addOnloadListener(this._checkVacationReplyEnabled.bind(this));
 56 };
 57 
 58 ZmMailApp.prototype = new ZmApp;
 59 ZmMailApp.prototype.constructor = ZmMailApp;
 60 
 61 ZmMailApp.prototype.isZmMailApp = true;
 62 ZmMailApp.prototype.toString = function() {	return "ZmMailApp"; };
 63 
 64 // Organizer and item-related constants
 65 ZmEvent.S_CONV				= ZmId.ITEM_CONV;
 66 ZmEvent.S_MSG				= ZmId.ITEM_MSG;
 67 ZmEvent.S_ATT				= ZmId.ITEM_ATT;
 68 ZmEvent.S_FOLDER			= ZmId.ORG_FOLDER;
 69 ZmEvent.S_DATA_SOURCE		= ZmId.ITEM_DATA_SOURCE;
 70 ZmEvent.S_IDENTITY			= "IDENTITY";
 71 ZmEvent.S_SIGNATURE			= "SIGNATURE";
 72 ZmItem.CONV					= ZmEvent.S_CONV;
 73 ZmItem.MSG					= ZmEvent.S_MSG;
 74 ZmItem.ATT					= ZmEvent.S_ATT;
 75 ZmItem.DATA_SOURCE			= ZmEvent.S_DATA_SOURCE;
 76 ZmOrganizer.FOLDER			= ZmEvent.S_FOLDER;
 77 
 78 // App-related constants
 79 /**
 80  * Defines the "mail" application.
 81  */
 82 ZmApp.MAIL									= ZmId.APP_MAIL;
 83 ZmApp.CLASS[ZmApp.MAIL]						= "ZmMailApp";
 84 ZmApp.SETTING[ZmApp.MAIL]					= ZmSetting.MAIL_ENABLED;
 85 ZmApp.UPSELL_SETTING[ZmApp.MAIL]			= ZmSetting.MAIL_UPSELL_ENABLED;
 86 ZmApp.LOAD_SORT[ZmApp.MAIL]					= 20;
 87 ZmApp.QS_ARG[ZmApp.MAIL]					= "mail";
 88 
 89 ZmMailApp.DEFAULT_AUTO_SAVE_DRAFT_INTERVAL	= 15;
 90 ZmMailApp.AUTO_SAVE_IDLE_TIME           	= 3;
 91 ZmMailApp.DEFAULT_MAX_MESSAGE_SIZE			= 250000;
 92 
 93 ZmMailApp.POP_DOWNLOAD_SINCE_ALL			= 0;
 94 ZmMailApp.POP_DOWNLOAD_SINCE_NO_CHANGE		= 1;
 95 ZmMailApp.POP_DOWNLOAD_SINCE_FROM_NOW		= 2;
 96 
 97 ZmMailApp.POP_DELETE_OPTION_KEEP            = "keep";
 98 ZmMailApp.POP_DELETE_OPTION_READ            = "read";
 99 ZmMailApp.POP_DELETE_OPTION_TRASH           = "trash";
100 ZmMailApp.POP_DELETE_OPTION_HARD_DELETE     = "delete";
101 
102 ZmMailApp.SEND_RECEIPT_NEVER				= "never";
103 ZmMailApp.SEND_RECEIPT_ALWAYS				= "always";
104 ZmMailApp.SEND_RECEIPT_PROMPT				= "prompt";
105 
106 ZmMailApp.INC_MAP = {};
107 ZmMailApp.INC_MAP[ZmSetting.INC_NONE]			= [ZmSetting.INC_NONE, false, false];
108 ZmMailApp.INC_MAP[ZmSetting.INC_ATTACH]			= [ZmSetting.INC_ATTACH, false, false];
109 ZmMailApp.INC_MAP[ZmSetting.INC_BODY]			= [ZmSetting.INC_BODY, false, true];
110 ZmMailApp.INC_MAP[ZmSetting.INC_BODY_ONLY]		= [ZmSetting.INC_BODY, false, false];
111 ZmMailApp.INC_MAP[ZmSetting.INC_BODY_PRE]		= [ZmSetting.INC_BODY, true, false];
112 ZmMailApp.INC_MAP[ZmSetting.INC_BODY_HDR]		= [ZmSetting.INC_BODY, false, true];
113 ZmMailApp.INC_MAP[ZmSetting.INC_BODY_PRE_HDR]	= [ZmSetting.INC_BODY, true, true];
114 ZmMailApp.INC_MAP[ZmSetting.INC_SMART]			= [ZmSetting.INC_SMART, false, false];
115 ZmMailApp.INC_MAP[ZmSetting.INC_SMART_PRE]		= [ZmSetting.INC_SMART, true, false];
116 ZmMailApp.INC_MAP[ZmSetting.INC_SMART_HDR]		= [ZmSetting.INC_SMART, false, true];
117 ZmMailApp.INC_MAP[ZmSetting.INC_SMART_PRE_HDR]	= [ZmSetting.INC_SMART, true, true];
118 
119 ZmMailApp.INC_MAP_REV = {};
120 
121 AjxUtil.foreach(ZmMailApp.INC_MAP, function(v, i) {
122 	var key = (i == ZmSetting.INC_NONE || i == ZmSetting.INC_ATTACH) ?
123 		v[0] : v.join("|");
124 	ZmMailApp.INC_MAP_REV[key] = i;
125 });
126 
127 ZmMailApp.GROUP_MAIL_BY_ITEM	= {};
128 ZmMailApp.GROUP_MAIL_BY_ITEM[ZmSetting.GROUP_BY_CONV]		= ZmItem.CONV;
129 ZmMailApp.GROUP_MAIL_BY_ITEM[ZmSetting.GROUP_BY_MESSAGE]	= ZmItem.MSG;
130 
131 // Construction
132 
133 ZmMailApp.prototype._defineAPI =
134 function() {
135 	AjxDispatcher.setPackageLoadFunction("MailCore", new AjxCallback(this, this._postLoadCore));
136 	AjxDispatcher.setPackageLoadFunction("Mail", new AjxCallback(this, this._postLoad, ZmOrganizer.FOLDER));
137 	AjxDispatcher.registerMethod("Compose", ["MailCore", "Mail"], new AjxCallback(this, this.compose));
138 	AjxDispatcher.registerMethod("GetComposeController", ["MailCore", "Mail"], new AjxCallback(this, this.getComposeController));
139 	AjxDispatcher.registerMethod("GetConvController", ["MailCore", "Mail"], new AjxCallback(this, this.getConvController));
140 	AjxDispatcher.registerMethod("GetConvListController", ["MailCore", "Mail"], new AjxCallback(this, this.getConvListController));
141 	AjxDispatcher.registerMethod("GetMsgController", ["MailCore", "Mail"], new AjxCallback(this, this.getMsgController));
142 	AjxDispatcher.registerMethod("GetTradController", ["MailCore", "Mail"], new AjxCallback(this, this.getTradController));
143 	AjxDispatcher.registerMethod("GetMailListController", "MailCore", new AjxCallback(this, this.getMailListController));
144 	AjxDispatcher.registerMethod("GetIdentityCollection", "MailCore", new AjxCallback(this, this.getIdentityCollection));
145 	AjxDispatcher.registerMethod("GetSignatureCollection", "MailCore", new AjxCallback(this, this.getSignatureCollection));
146 	AjxDispatcher.registerMethod("GetDataSourceCollection", "MailCore", new AjxCallback(this, this.getDataSourceCollection));
147 	AjxDispatcher.registerMethod("GetMailConfirmController", ["MailCore","Mail"], new AjxCallback(this, this.getConfirmController));
148 };
149 
150 ZmMailApp.prototype._registerSettings =
151 function(settings) {
152 	var settings = settings || appCtxt.getSettings();
153 	settings.registerSetting("ALLOW_ANY_FROM_ADDRESS",			{name:"zimbraAllowAnyFromAddress", type:ZmSetting.T_COS, dataType:ZmSetting.D_BOOLEAN, defaultValue:false});
154     settings.registerSetting("AUTO_READ_RECEIPT_ENABLED",		{name:"zimbraPrefMailRequestReadReceipts", type:ZmSetting.T_PREF, dataType:ZmSetting.D_BOOLEAN, defaultValue:false});
155 	settings.registerSetting("AUTO_SAVE_DRAFT_INTERVAL",		{name:"zimbraPrefAutoSaveDraftInterval", type:ZmSetting.T_PREF, dataType:ZmSetting.D_LDAP_TIME, defaultValue:ZmMailApp.DEFAULT_AUTO_SAVE_DRAFT_INTERVAL, isGlobal:true});
156     settings.registerSetting("COLLAPSE_IMAP_TREES",				{type:ZmSetting.T_COS, dataType:ZmSetting.D_BOOLEAN, defaultValue:false});
157 	settings.registerSetting("COLOR_MESSAGES",					{name:"zimbraPrefColorMessagesEnabled", type:ZmSetting.T_PREF, dataType:ZmSetting.D_BOOLEAN, defaultValue: false, isGlobal:true});
158 	settings.registerSetting("COMPOSE_SAME_FORMAT",				{name:"zimbraPrefForwardReplyInOriginalFormat", type:ZmSetting.T_PREF, dataType:ZmSetting.D_BOOLEAN, defaultValue:false, isGlobal:true});
159 	settings.registerSetting("CONVERSATIONS_ENABLED",			{name:"zimbraFeatureConversationsEnabled", type:ZmSetting.T_COS, dataType:ZmSetting.D_BOOLEAN, defaultValue:false});
160 	settings.registerSetting("CONVERSATION_ORDER",				{name:"zimbraPrefConversationOrder", type:ZmSetting.T_PREF, defaultValue:ZmSearch.DATE_DESC, isImplicit:true});
161 	settings.registerSetting("CONVERSATION_PAGE_SIZE",			{type:ZmSetting.T_PREF, dataType:ZmSetting.D_INT, defaultValue:250, isGlobal:true});
162     settings.registerSetting("CONV_SHOW_CALENDAR",			    {name:"zimbraPrefConvShowCalendar", type:ZmSetting.T_PREF, dataType:ZmSetting.D_BOOLEAN, defaultValue:false, isImplicit:true});
163  	settings.registerSetting("DEDUPE_MSG_TO_SELF",				{name:"zimbraPrefDedupeMessagesSentToSelf", type:ZmSetting.T_PREF, defaultValue:ZmSetting.DEDUPE_NONE});
164     settings.registerSetting("DEDUPE_MSG_ENABLED",				{name:"zimbraPrefMessageIdDedupingEnabled", type:ZmSetting.T_PREF, dataType:ZmSetting.D_BOOLEAN, defaultValue:true});
165     settings.registerSetting("DEFAULT_DISPLAY_NAME",			{type:ZmSetting.T_PSEUDO, dataType:ZmSetting.D_BOOLEAN, defaultValue:true});
166 	settings.registerSetting("DETACH_COMPOSE_ENABLED",			{name:"zimbraFeatureComposeInNewWindowEnabled",type:ZmSetting.T_PREF,dataType:ZmSetting.D_BOOLEAN,defaultValue:true});
167 	settings.registerSetting("DETACH_MAILVIEW_ENABLED",			{name:"zimbraFeatureOpenMailInNewWindowEnabled",type:ZmSetting.T_PREF,dataType:ZmSetting.D_BOOLEAN,defaultValue:true});
168 	settings.registerSetting("DISPLAY_EXTERNAL_IMAGES",			{name:"zimbraPrefDisplayExternalImages", type:ZmSetting.T_PREF, dataType:ZmSetting.D_BOOLEAN, defaultValue:false, isGlobal:true});
169 	settings.registerSetting("END_DATE_ENABLED",				{type:ZmSetting.T_PREF, dataType:ZmSetting.D_BOOLEAN, defaultValue:false});
170 	settings.registerSetting("FILTERS_ENABLED",					{name:"zimbraFeatureFiltersEnabled", type:ZmSetting.T_COS, dataType:ZmSetting.D_BOOLEAN, defaultValue:false});
171 	settings.registerSetting("FILTERS_MAIL_FORWARDING_ENABLED",	{name:"zimbraFeatureMailForwardingInFiltersEnabled", type:ZmSetting.T_COS, dataType:ZmSetting.D_BOOLEAN, defaultValue:true});
172 	settings.registerSetting("FORWARD_INCLUDE_HEADERS",			{type:ZmSetting.T_PREF, dataType:ZmSetting.D_BOOLEAN, defaultValue:true, isGlobal:true});
173 	settings.registerSetting("FORWARD_INCLUDE_ORIG",			{name:"zimbraPrefForwardIncludeOriginalText", type:ZmSetting.T_PREF, defaultValue:ZmSetting.INC_BODY, isGlobal:true});
174 	settings.registerSetting("FORWARD_INCLUDE_WHAT",			{type:ZmSetting.T_PREF, defaultValue:ZmSetting.INC_BODY, isGlobal:true});
175 	settings.registerSetting("FORWARD_MENU_ENABLED",			{type:ZmSetting.T_COS, dataType:ZmSetting.D_BOOLEAN, defaultValue:true});
176 	settings.registerSetting("FORWARD_USE_PREFIX",				{type:ZmSetting.T_PREF, dataType:ZmSetting.D_BOOLEAN, defaultValue:false, isGlobal:true});
177 	settings.registerSetting("GROUP_MAIL_BY",					{name:"zimbraPrefGroupMailBy", type:ZmSetting.T_PREF, defaultValue:ZmSetting.GROUP_BY_MESSAGE, isImplicit:true, isGlobal:true});
178 	settings.registerSetting("HIGHLIGHT_OBJECTS",               {name:"zimbraMailHighlightObjectsMaxSize", type:ZmSetting.T_COS, dataType:ZmSetting.D_INT, defaultValue:70});
179 	settings.registerSetting("HTML_SIGNATURE_ENABLED",			{type:ZmSetting.T_PREF,dataType:ZmSetting.D_BOOLEAN,defaultValue:true});
180 	settings.registerSetting("IDENTITIES_ENABLED",				{name:"zimbraFeatureIdentitiesEnabled", type:ZmSetting.T_COS, dataType:ZmSetting.D_BOOLEAN, defaultValue:true});
181 	settings.registerSetting("INITIAL_SEARCH",					{name:"zimbraPrefMailInitialSearch", type:ZmSetting.T_PREF, defaultValue:"in:inbox"});
182 	settings.registerSetting("INITIAL_SEARCH_ENABLED",			{name:"zimbraFeatureInitialSearchPreferenceEnabled", type:ZmSetting.T_COS, dataType:ZmSetting.D_BOOLEAN, defaultValue:false});
183 	settings.registerSetting("MAIL_ATTACH_VIEW_ENABLED",		{type:ZmSetting.T_COS, dataType:ZmSetting.D_BOOLEAN, defaultValue:false});
184 	settings.registerSetting("MAIL_BLACKLIST",					{type: ZmSetting.T_PREF, dataType: ZmSetting.D_LIST});
185     settings.registerSetting("TRUSTED_ADDR_LIST",			    {name:"zimbraPrefMailTrustedSenderList", type: ZmSetting.T_COS, dataType: ZmSetting.D_LIST});
186 	settings.registerSetting("TRUSTED_ADDR_LIST_MAX_NUM_ENTRIES",	{name:"zimbraMailTrustedSenderListMaxNumEntries", type: ZmSetting.T_COS, dataType: ZmSetting.D_INT, defaultValue:100});
187 	settings.registerSetting("MAIL_ACTIVITYSTREAM_FOLDER",   	{name:"zimbraMailActivityStreamFolder", type:ZmSetting.T_METADATA, dataType:ZmSetting.D_INT, isImplicit:true, section:ZmSetting.M_IMPLICIT});
188 	settings.registerSetting("MAIL_BLACKLIST_MAX_NUM_ENTRIES",	{name:"zimbraMailBlacklistMaxNumEntries", type: ZmSetting.T_COS, dataType: ZmSetting.D_INT, defaultValue:100});
189 	settings.registerSetting("MAIL_FOLDER_COLORS_ENABLED",		{name:"zimbraPrefFolderColorEnabled", type:ZmSetting.T_COS, dataType:ZmSetting.D_BOOLEAN, defaultValue:true});
190 	settings.registerSetting("MAIL_FORWARDING_ADDRESS",			{name:"zimbraPrefMailForwardingAddress", type:ZmSetting.T_PREF});
191 	settings.registerSetting("MAIL_FORWARDING_ENABLED",			{name:"zimbraFeatureMailForwardingEnabled", type:ZmSetting.T_COS, dataType:ZmSetting.D_BOOLEAN, defaultValue:false});
192 	settings.registerSetting("MAIL_MANDATORY_SPELLCHECK",		{name:"zimbraPrefMandatorySpellCheckEnabled", type:ZmSetting.T_PREF, dataType:ZmSetting.D_BOOLEAN, defaultValue:false, isGlobal:true});
193 	settings.registerSetting("MAIL_FROM_ADDRESS",				{name:"zimbraPrefFromAddress", type:ZmSetting.T_PREF, dataType:ZmSetting.D_LIST });
194     settings.registerSetting("MAIL_FROM_ADDRESS_TYPE",			{name:"zimbraPrefFromAddressType", type:ZmSetting.T_PREF, dataType:ZmSetting.D_STRING, defaultValue:"sendAs" });
195 	settings.registerSetting("MAIL_LIFETIME_GLOBAL",			{name:"zimbraMailMessageLifetime", type:ZmSetting.T_COS, defaultValue:"0"}); // dataType: DURATION
196 	settings.registerSetting("MAIL_LIFETIME_INBOX_READ",		{name:"zimbraPrefInboxReadLifetime", type:ZmSetting.T_PREF, defaultValue:"0"}); // dataType: DURATION
197 	settings.registerSetting("MAIL_LIFETIME_INBOX_UNREAD",		{name:"zimbraPrefInboxUnreadLifetime", type:ZmSetting.T_PREF, defaultValue:"0"}); // dataType: DURATION
198 	settings.registerSetting("MAIL_LIFETIME_JUNK",				{name:"zimbraPrefJunkLifetime", type:ZmSetting.T_PREF, defaultValue:"0"}); // dataType: DURATION
199 	settings.registerSetting("MAIL_LIFETIME_JUNK_GLOBAL",		{name:"zimbraMailSpamLifetime", type:ZmSetting.T_COS, defaultValue:"0"}); // dataType: DURATION
200 	settings.registerSetting("MAIL_LIFETIME_SENT",				{name:"zimbraPrefSentLifetime", type:ZmSetting.T_PREF, defaultValue:"0"}); // dataType: DURATION
201 	settings.registerSetting("MAIL_LIFETIME_TRASH",				{name:"zimbraPrefTrashLifetime", type:ZmSetting.T_PREF, defaultValue:"0"}); // dataType: DURATION
202 	settings.registerSetting("MAIL_LIFETIME_TRASH_GLOBAL",		{name:"zimbraMailTrashLifetime", type:ZmSetting.T_COS, defaultValue:"0"}); // dataType: DURATION
203 	settings.registerSetting("MAIL_LOCAL_DELIVERY_DISABLED",	{name:"zimbraPrefMailLocalDeliveryDisabled", type:ZmSetting.T_PREF, dataType:ZmSetting.D_BOOLEAN, defaultValue:false});
204 	settings.registerSetting("MAIL_NOTIFY_ALL",				    {name:"zimbraPrefShowAllNewMailNotifications", type:ZmSetting.T_PREF, dataType:ZmSetting.D_BOOLEAN, defaultValue:false, isGlobal:true});
205 	settings.registerSetting("MAIL_NOTIFY_SOUNDS",				{name:"zimbraPrefMailSoundsEnabled", type:ZmSetting.T_PREF, dataType:ZmSetting.D_BOOLEAN, defaultValue:false, isGlobal:true});
206 	settings.registerSetting("MAIL_NOTIFY_APP",					{name:"zimbraPrefMailFlashIcon", type:ZmSetting.T_PREF, dataType:ZmSetting.D_BOOLEAN, defaultValue:false, isGlobal:true});
207 	settings.registerSetting("MAIL_NOTIFY_BROWSER",				{name:"zimbraPrefMailFlashTitle", type:ZmSetting.T_PREF, dataType:ZmSetting.D_BOOLEAN, defaultValue:false, isGlobal:true});
208 	settings.registerSetting("MAIL_NOTIFY_TOASTER",				{name:"zimbraPrefMailToasterEnabled", type:ZmSetting.T_PREF, dataType:ZmSetting.D_BOOLEAN, defaultValue:false, isGlobal:true});
209 	settings.registerSetting("MAIL_PRIORITY_ENABLED",			{name:"zimbraFeatureMailPriorityEnabled", type:ZmSetting.T_COS, dataType:ZmSetting.D_BOOLEAN, defaultValue:false});
210 	settings.registerSetting("MAIL_READ_RECEIPT_ENABLED",		{name:"zimbraFeatureReadReceiptsEnabled", type:ZmSetting.T_COS, dataType:ZmSetting.D_BOOLEAN, defaultValue:true});
211 	settings.registerSetting("MAIL_SEND_LATER_ENABLED",			{name:"zimbraFeatureMailSendLaterEnabled", type:ZmSetting.T_COS, dataType:ZmSetting.D_BOOLEAN, defaultValue:false});
212 	settings.registerSetting("MAIL_SEND_READ_RECEIPTS",			{name:"zimbraPrefMailSendReadReceipts", type:ZmSetting.T_PREF, dataType:ZmSetting.D_STRING, defaultValue:"never"});
213 	settings.registerSetting("MAIL_WHITELIST",					{type: ZmSetting.T_PREF, dataType: ZmSetting.D_LIST});
214 	settings.registerSetting("MAIL_WHITELIST_MAX_NUM_ENTRIES",	{name:"zimbraMailWhitelistMaxNumEntries", type: ZmSetting.T_COS, dataType: ZmSetting.D_INT, defaultValue:100});
215 	settings.registerSetting("MARK_MSG_READ",					{name:"zimbraPrefMarkMsgRead", type:ZmSetting.T_PREF, dataType:ZmSetting.D_INT, defaultValue:0, isGlobal:true});
216 	settings.registerSetting("MAX_MESSAGE_SIZE",				{type:ZmSetting.T_PREF, defaultValue:ZmMailApp.DEFAULT_MAX_MESSAGE_SIZE});
217 	settings.registerSetting("NEW_WINDOW_COMPOSE",				{name:"zimbraPrefComposeInNewWindow", type:ZmSetting.T_PREF, dataType:ZmSetting.D_BOOLEAN, defaultValue:true, isGlobal:true});
218 	settings.registerSetting("NOTIF_ADDRESS",					{name:"zimbraPrefNewMailNotificationAddress", type:ZmSetting.T_PREF});
219 	settings.registerSetting("NOTIF_ENABLED",					{name:"zimbraPrefNewMailNotificationEnabled", type:ZmSetting.T_PREF, dataType:ZmSetting.D_BOOLEAN, defaultValue:false});
220 	settings.registerSetting("NOTIF_FEATURE_ENABLED",			{name:"zimbraFeatureNewMailNotificationEnabled", type:ZmSetting.T_COS, dataType:ZmSetting.D_BOOLEAN, defaultValue:false});
221 	settings.registerSetting("OPEN_MAIL_IN_NEW_WIN",			{name:"zimbraPrefOpenMailInNewWindow", type:ZmSetting.T_PREF, dataType:ZmSetting.D_BOOLEAN, defaultValue:false, isGlobal:true});
222 	settings.registerSetting("POP_ENABLED",						{name:"zimbraPop3Enabled", type:ZmSetting.T_COS, dataType:ZmSetting.D_BOOLEAN, defaultValue:!appCtxt.isOffline});
223 	settings.registerSetting("POP_DOWNLOAD_SINCE_VALUE",		{type:ZmSetting.T_PREF, dataType:ZmSetting.D_STRING, defaultValue:""});
224     settings.registerSetting("POP_DOWNLOAD_SINCE",				{name:"zimbraPrefPop3DownloadSince", type:ZmSetting.T_PREF, dataType:ZmSetting.D_STRING, defaultValue:""});
225     settings.registerSetting("POP_DELETE_OPTION",				{name:"zimbraPrefPop3DeleteOption", type:ZmSetting.T_PREF, dataType:ZmSetting.D_STRING, defaultValue:ZmMailApp.POP_DELETE_OPTION_HARD_DELETE});
226     settings.registerSetting("POP_INCLUDE_SPAM",				{name:"zimbraPrefPop3IncludeSpam", type:ZmSetting.T_PREF, dataType:ZmSetting.D_BOOLEAN, defaultValue:false});
227 	settings.registerSetting("READING_PANE_LOCATION",			{name:"zimbraPrefReadingPaneLocation", type:ZmSetting.T_PREF, dataType:ZmSetting.D_STRING, defaultValue:ZmSetting.RP_BOTTOM, isImplicit:true, isGlobal:true});
228 	settings.registerSetting("READING_PANE_LOCATION_CV",		{name:"zimbraPrefConvReadingPaneLocation", type:ZmSetting.T_PREF, dataType:ZmSetting.D_STRING, defaultValue:ZmSetting.RP_BOTTOM, isImplicit:true});
229 	settings.registerSetting("READING_PANE_SASH_HORIZONTAL",    {name:"zimbraPrefReadingPaneSashHorizontal", type:ZmSetting.T_METADATA, dataType:ZmSetting.D_INT, isImplicit:true, section:ZmSetting.M_IMPLICIT});
230 	settings.registerSetting("READING_PANE_SASH_VERTICAL",      {name:"zimbraPrefReadingPaneSashVertical", type:ZmSetting.T_METADATA, dataType:ZmSetting.D_INT, isImplicit:true, section:ZmSetting.M_IMPLICIT});
231 	settings.registerSetting("REPLY_INCLUDE_HEADERS",			{type:ZmSetting.T_PREF, dataType:ZmSetting.D_BOOLEAN, defaultValue:true, isGlobal:true});
232 	settings.registerSetting("REPLY_INCLUDE_ORIG",				{name:"zimbraPrefReplyIncludeOriginalText", type:ZmSetting.T_PREF, defaultValue:ZmSetting.INC_BODY, isGlobal:true});
233 	settings.registerSetting("REPLY_INCLUDE_WHAT",				{type:ZmSetting.T_PREF, defaultValue:ZmSetting.INC_BODY, isGlobal:true});
234 	settings.registerSetting("REPLY_MENU_ENABLED",				{type:ZmSetting.T_COS, dataType:ZmSetting.D_BOOLEAN, defaultValue:true});
235 	settings.registerSetting("REPLY_PREFIX",					{name:"zimbraPrefForwardReplyPrefixChar", type:ZmSetting.T_PREF, defaultValue:">", isGlobal:true});
236 	settings.registerSetting("REPLY_TO_ADDRESS",				{name:"zimbraPrefReplyToAddress", type:ZmSetting.T_PREF, dataType:ZmSetting.D_LIST });
237 	settings.registerSetting("REPLY_TO_ENABLED",				{name:"zimbraPrefReplyToEnabled", type:ZmSetting.T_PREF}); // XXX: Is this a list or single?
238 	settings.registerSetting("REPLY_USE_PREFIX",				{type:ZmSetting.T_PREF, dataType:ZmSetting.D_BOOLEAN, defaultValue:false, isGlobal:true});
239 	settings.registerSetting("SAVE_DRAFT_ENABLED",				{type:ZmSetting.T_COS, dataType:ZmSetting.D_BOOLEAN, defaultValue:true});
240     settings.registerSetting("SAVE_TO_IMAP_SENT",				{type:ZmSetting.T_COS, dataType:ZmSetting.D_BOOLEAN, defaultValue:false});
241 	settings.registerSetting("SAVE_TO_SENT",					{name:"zimbraPrefSaveToSent", type:ZmSetting.T_PREF, dataType:ZmSetting.D_BOOLEAN, defaultValue:true, isGlobal:true});
242 	settings.registerSetting("SAVE_TO_SENT_DELEGATED_TARGET",	{name: "zimbraPrefDelegatedSendSaveTarget", type: ZmSetting.T_PREF, defaultValue: "owner", isGlobal: true});
243 	settings.registerSetting("SELECT_AFTER_DELETE",				{name:"zimbraPrefMailSelectAfterDelete", type:ZmSetting.T_PREF, defaultValue:ZmSetting.DELETE_SELECT_NEXT, isGlobal:true});
244 	settings.registerSetting("SENT_FOLDER_NAME",				{name:"zimbraPrefSentMailFolder", type:ZmSetting.T_PREF, defaultValue:"sent"});
245 	settings.registerSetting("SHOW_BCC",			            {type:ZmSetting.T_PREF, defaultValue:false});
246     settings.registerSetting("SHOW_FRAGMENTS",					{name:"zimbraPrefShowFragments", type:ZmSetting.T_PREF, dataType:ZmSetting.D_BOOLEAN, defaultValue:false, isGlobal:true});
247 	settings.registerSetting("SHOW_MAIL_CONFIRM",				{name:"zimbraFeatureConfirmationPageEnabled", type:ZmSetting.T_PREF, dataType:ZmSetting.D_BOOLEAN, defaultValue:false});
248 	settings.registerSetting("SHOW_CHATS_FOLDER",				{name:"zimbraPrefShowChatsFolderInMail", type:ZmSetting.T_PREF, dataType:ZmSetting.D_BOOLEAN, defaultValue:false});
249 	settings.registerSetting("SIGNATURE",						{name:"zimbraPrefMailSignature", type:ZmSetting.T_PREF});
250 	settings.registerSetting("SIGNATURE_ENABLED",				{name:"zimbraPrefMailSignatureEnabled", type:ZmSetting.T_PREF, dataType:ZmSetting.D_BOOLEAN, defaultValue:false});
251 	settings.registerSetting("SIGNATURE_STYLE",					{name:"zimbraPrefMailSignatureStyle", type:ZmSetting.T_PREF, defaultValue:ZmSetting.SIG_OUTLOOK});
252 	settings.registerSetting("START_DATE_ENABLED",				{type:ZmSetting.T_PREF, dataType:ZmSetting.D_BOOLEAN, defaultValue:false});
253     settings.registerSetting("TAB_IN_EDITOR",			        {name:"zimbraPrefTabInEditorEnabled", type:ZmSetting.T_PREF, dataType:ZmSetting.D_BOOLEAN, defaultValue:false});
254     settings.registerSetting("USE_SEND_MSG_SHORTCUT",			{name:"zimbraPrefUseSendMsgShortcut", type:ZmSetting.T_PREF, dataType:ZmSetting.D_BOOLEAN, defaultValue:true, isGlobal:true});
255     settings.registerSetting("USER_FOLDERS_ENABLED",			{type:ZmSetting.T_COS, dataType:ZmSetting.D_BOOLEAN, defaultValue:true});
256     settings.registerSetting("VACATION_DURATION_ENABLED",		{type:ZmSetting.T_PREF, dataType:ZmSetting.D_BOOLEAN, defaultValue:false});
257     settings.registerSetting("VACATION_DURATION_ALL_DAY",		{type:ZmSetting.T_PREF, dataType:ZmSetting.D_BOOLEAN, defaultValue:true});
258 	settings.registerSetting("VACATION_FROM",					{name:"zimbraPrefOutOfOfficeFromDate", type:ZmSetting.T_PREF, defaultValue:""});
259     settings.registerSetting("VACATION_FROM_TIME",				{type:ZmSetting.T_PREF, defaultValue:""});
260 	settings.registerSetting("VACATION_MSG",					{name:"zimbraPrefOutOfOfficeReply", type:ZmSetting.T_PREF, defaultValue:""});
261     settings.registerSetting("VACATION_EXTERNAL_TYPE",			{name:"zimbraPrefExternalSendersType", type:ZmSetting.T_PREF, defaultValue:"ALL"});
262     settings.registerSetting("VACATION_EXTERNAL_SUPPRESS",	    {name:"zimbraPrefOutOfOfficeSuppressExternalReply", type:ZmSetting.T_PREF, dataType:ZmSetting.D_BOOLEAN, defaultValue:false});
263     settings.registerSetting("VACATION_CALENDAR_TYPE",			{name:"zimbraPrefOutOfOfficeFreeBusyStatus", type:ZmSetting.T_PREF, defaultValue:"OUTOFOFFICE"});
264 	settings.registerSetting("VACATION_CALENDAR_APPT_ID",		{name:"zimbraPrefOutOfOfficeCalApptID", type:ZmSetting.T_METADATA, defaultValue:"-1", isImplicit:true, section:ZmSetting.M_IMPLICIT});
265     settings.registerSetting("VACATION_CALENDAR_ENABLED",		{type:ZmSetting.T_PREF, dataType:ZmSetting.D_BOOLEAN, defaultValue:false});
266     settings.registerSetting("VACATION_EXTERNAL_MSG",			{name:"zimbraPrefOutOfOfficeExternalReply", type:ZmSetting.T_PREF, defaultValue:""});
267 	settings.registerSetting("VACATION_MSG_ENABLED",			{name:"zimbraPrefOutOfOfficeReplyEnabled", type:ZmSetting.T_PREF, dataType:ZmSetting.D_BOOLEAN, defaultValue:false});
268     settings.registerSetting("VACATION_EXTERNAL_MSG_ENABLED",	{name:"zimbraPrefOutOfOfficeExternalReplyEnabled", type:ZmSetting.T_PREF, dataType:ZmSetting.D_BOOLEAN, defaultValue:false});
269     settings.registerSetting("VACATION_MSG_REMIND_ON_LOGIN",	{name:"zimbraPrefOutOfOfficeStatusAlertOnLogin", type:ZmSetting.T_PREF, dataType:ZmSetting.D_BOOLEAN, defaultValue:true});
270 	settings.registerSetting("VACATION_MSG_FEATURE_ENABLED",	{name:"zimbraFeatureOutOfOfficeReplyEnabled", type:ZmSetting.T_COS, dataType:ZmSetting.D_BOOLEAN, defaultValue:false});
271 	settings.registerSetting("VACATION_UNTIL",					{name:"zimbraPrefOutOfOfficeUntilDate", type:ZmSetting.T_PREF, defaultValue:""});
272     settings.registerSetting("VACATION_UNTIL_TIME",				{type:ZmSetting.T_PREF, defaultValue:""});
273 };
274 
275 ZmMailApp.prototype._registerPrefs =
276 function() {
277 	var sections = {
278 		MAIL: {
279 			title: ZmMsg.mail,
280 			icon: "MailApp",
281 			templateId: "prefs.Pages#Mail",
282 			priority: 10,
283 			precondition: ZmSetting.MAIL_PREFERENCES_ENABLED,
284 			prefs: [
285 				ZmSetting.AUTO_READ_RECEIPT_ENABLED,
286 				ZmSetting.DEDUPE_MSG_TO_SELF,
287                 ZmSetting.DEDUPE_MSG_ENABLED,
288 				ZmSetting.DISPLAY_EXTERNAL_IMAGES,
289 				ZmSetting.GET_MAIL_ACTION,
290 				ZmSetting.INITIAL_SEARCH,
291 				ZmSetting.MAIL_BLACKLIST,
292 				ZmSetting.MAIL_FORWARDING_ADDRESS,
293 				ZmSetting.MAIL_LIFETIME_INBOX_READ,
294 				ZmSetting.MAIL_LIFETIME_INBOX_UNREAD,
295 				ZmSetting.MAIL_LIFETIME_JUNK,
296 				ZmSetting.MAIL_LIFETIME_SENT,
297 				ZmSetting.MAIL_LIFETIME_TRASH,
298 				ZmSetting.MAIL_LOCAL_DELIVERY_DISABLED,
299 				ZmSetting.MAIL_NOTIFY_SOUNDS,
300 				ZmSetting.MAIL_NOTIFY_ALL,
301 				ZmSetting.MAIL_NOTIFY_APP,
302 				ZmSetting.MAIL_NOTIFY_BROWSER,
303 				ZmSetting.MAIL_NOTIFY_TOASTER,
304 				ZmSetting.MAIL_WHITELIST,
305 				ZmSetting.MAIL_SEND_READ_RECEIPTS,
306 				ZmSetting.MARK_MSG_READ,
307 				ZmSetting.NOTIF_ADDRESS,
308 				ZmSetting.OFFLINE_NOTIFY_NEWMAIL_ON_INBOX,
309 				ZmSetting.OPEN_MAIL_IN_NEW_WIN,
310 				ZmSetting.PAGE_SIZE,
311 				ZmSetting.POP_DOWNLOAD_SINCE_VALUE,
312 				ZmSetting.POP_DOWNLOAD_SINCE,
313                 ZmSetting.POP_DELETE_OPTION,
314                 ZmSetting.POP_INCLUDE_SPAM,
315 				ZmSetting.POLLING_INTERVAL,
316 				ZmSetting.SELECT_AFTER_DELETE,
317 				ZmSetting.SHOW_FRAGMENTS,
318 				ZmSetting.COLOR_MESSAGES,
319 				ZmSetting.START_DATE_ENABLED,
320                 ZmSetting.VACATION_DURATION_ENABLED,
321                 ZmSetting.VACATION_DURATION_ALL_DAY,
322 				ZmSetting.VACATION_FROM,
323                 ZmSetting.VACATION_FROM_TIME,
324                 ZmSetting.VACATION_CALENDAR_ENABLED,
325 				ZmSetting.VACATION_MSG_ENABLED,
326 				ZmSetting.VACATION_MSG,
327                 ZmSetting.VACATION_EXTERNAL_MSG_ENABLED,
328 				ZmSetting.VACATION_EXTERNAL_MSG,
329                 ZmSetting.VACATION_EXTERNAL_TYPE,
330                 ZmSetting.VACATION_EXTERNAL_SUPPRESS,
331                 ZmSetting.VACATION_CALENDAR_TYPE,
332 				ZmSetting.VACATION_UNTIL,
333                 ZmSetting.VACATION_UNTIL_TIME,
334 				ZmSetting.VIEW_AS_HTML,
335                 ZmSetting.COMPOSE_AS_FORMAT,
336 				ZmSetting.COMPOSE_INIT_FONT_COLOR,
337 				ZmSetting.COMPOSE_INIT_FONT_FAMILY,
338 				ZmSetting.COMPOSE_INIT_FONT_SIZE,
339 				ZmSetting.FORWARD_INCLUDE_WHAT,
340 				ZmSetting.FORWARD_USE_PREFIX,
341 				ZmSetting.FORWARD_INCLUDE_HEADERS,
342 				ZmSetting.NEW_WINDOW_COMPOSE,
343 				ZmSetting.AUTO_SAVE_DRAFT_INTERVAL,
344 				ZmSetting.REPLY_INCLUDE_WHAT,
345 				ZmSetting.REPLY_USE_PREFIX,
346 				ZmSetting.REPLY_INCLUDE_HEADERS,
347 				ZmSetting.REPLY_PREFIX,
348 				ZmSetting.SAVE_TO_SENT,
349 				ZmSetting.TAB_IN_EDITOR,
350 				ZmSetting.USE_SEND_MSG_SHORTCUT,
351                 ZmSetting.COMPOSE_SAME_FORMAT,
352                 ZmSetting.MAIL_MANDATORY_SPELLCHECK
353 			],
354 			manageDirty: true,
355 			createView: function(parent, section, controller) {
356 				AjxDispatcher.require("Alert");
357 				return new ZmMailPrefsPage(parent, section, controller);
358 			}
359 		}
360 	};
361 
362 	for (var id in sections) {
363 		ZmPref.registerPrefSection(id, sections[id]);
364 	}
365 
366 	ZmPref.registerPref("ACCOUNTS", {
367 		displayContainer:	ZmPref.TYPE_CUSTOM
368 	});
369 
370 	ZmPref.registerPref("AUTO_SAVE_DRAFT_INTERVAL", {
371 		displayName:		ZmMsg.autoSaveDrafts,
372 		displayContainer:	ZmPref.TYPE_CHECKBOX,
373 		options:			[0, ZmMailApp.DEFAULT_AUTO_SAVE_DRAFT_INTERVAL]
374 	});
375 
376     ZmPref.registerPref("AUTO_READ_RECEIPT_ENABLED", {
377 		displayName:		ZmMsg.autoReadReceiptRequest,
378 		displayContainer:	ZmPref.TYPE_CHECKBOX
379 	});
380 
381 	ZmPref.registerPref("USE_SEND_MSG_SHORTCUT", {
382 		displayFunc:        this.formatKeySeq.bind(this, AjxMessageFormat.format(ZmMsg.useSendMsgShortcut,[ZmKeys["compose.Send.display"]])),
383 		displayContainer:	ZmPref.TYPE_CHECKBOX
384 	});
385 
386 	ZmPref.registerPref("DEDUPE_MSG_TO_SELF", {
387 		displayName:		ZmMsg.removeDupesToSelf,
388 		displayContainer:	ZmPref.TYPE_RADIO_GROUP,
389 		displayOptions:		[ZmMsg.dedupeNone, ZmMsg.dedupeSecondCopy, ZmMsg.dedupeAll],
390 		options:			[ZmSetting.DEDUPE_NONE, ZmSetting.DEDUPE_SECOND, ZmSetting.DEDUPE_ALL]
391 	});
392 
393     ZmPref.registerPref("DEDUPE_MSG_ENABLED", {
394 		displayName:		ZmMsg.autoDeleteDedupeMsg,
395 		displayContainer:	ZmPref.TYPE_CHECKBOX
396 	});
397 
398 	ZmPref.registerPref("DISPLAY_EXTERNAL_IMAGES", {
399 		displayName:		ZmMsg.showExternalImages,
400 		displayContainer:	ZmPref.TYPE_CHECKBOX
401 	});
402 
403 	ZmPref.registerPref("END_DATE_ENABLED", {
404 		displayName:		ZmMsg.endOn,
405 		displayContainer:	ZmPref.TYPE_CHECKBOX,
406 		precondition:		ZmSetting.VACATION_MSG_FEATURE_ENABLED
407 	});
408 
409 	ZmPref.registerPref("INITIAL_SEARCH", {
410 		displayName:		ZmMsg.initialMailSearch,
411 		displayContainer:	ZmPref.TYPE_INPUT,
412 		maxLength:			ZmPref.MAX_LENGTH[ZmSetting.INITIAL_SEARCH],
413 		errorMessage:       AjxMessageFormat.format(ZmMsg.invalidInitialSearch, ZmPref.MAX_LENGTH[ZmSetting.INITIAL_SEARCH]),
414 		precondition:		ZmSetting.INITIAL_SEARCH_ENABLED
415 	});
416 
417 	ZmPref.registerPref("MAIL_BLACKLIST", {
418 		displayContainer:	ZmPref.TYPE_CUSTOM
419 	});
420 
421     ZmPref.registerPref("TRUSTED_ADDR_LIST", {
422 		displayContainer:	ZmPref.TYPE_CUSTOM
423 	});
424 
425 	ZmPref.registerPref("MAIL_FORWARDING_ADDRESS", {
426 		displayName:		ZmMsg.mailForwardingAddress,
427 		displayContainer:	ZmPref.TYPE_INPUT,
428 		validationFunction: ZmMailApp.validateForwardEmail,
429 		errorMessage:       ZmMsg.invalidEmail,
430 		precondition:		ZmSetting.MAIL_FORWARDING_ENABLED,
431 		hint:				ZmMsg.enterEmailAddress
432 	});
433 
434 	ZmPref.registerPref("MAIL_LIFETIME_INBOX_READ", {
435 		displayContainer:	ZmPref.TYPE_RADIO_GROUP,
436 		orientation:		ZmPref.ORIENT_HORIZONTAL,
437 		displayOptions:		[ ZmMsg.lifetimeDurationDays, ZmMsg.lifetimeDurationDays,
438 							  ZmMsg.lifetimeDurationDays, ZmMsg.lifetimeDurationDays,
439 							  ZmMsg.lifetimeDurationDays, ZmMsg.lifetimeDurationNever ],
440 		options:			[ 30, 45, 60, 90, 120, 0 ],
441 		approximateFunction: ZmPref.approximateLifetimeInboxRead,
442 		displayFunction:	ZmPref.durationDay2Int,
443 		valueFunction:		ZmPref.int2DurationDay,
444 		validationFunction:	ZmPref.validateLifetime
445 	});
446 
447 	ZmPref.registerPref("MAIL_LIFETIME_INBOX_UNREAD", {
448 		displayContainer:	ZmPref.TYPE_RADIO_GROUP,
449 		orientation:		ZmPref.ORIENT_HORIZONTAL,
450 		displayOptions:		[ ZmMsg.lifetimeDurationDays, ZmMsg.lifetimeDurationDays,
451 							  ZmMsg.lifetimeDurationDays, ZmMsg.lifetimeDurationDays,
452 							  ZmMsg.lifetimeDurationDays, ZmMsg.lifetimeDurationNever ],
453 		options:			[ 30, 45, 60, 90, 120, 0 ],
454 		approximateFunction: ZmPref.approximateLifetimeInboxUnread,
455 		displayFunction:	ZmPref.durationDay2Int,
456 		valueFunction:		ZmPref.int2DurationDay,
457 		validationFunction:	ZmPref.validateLifetime
458 	});
459 
460 	ZmPref.registerPref("MAIL_LIFETIME_JUNK", {
461 		displayContainer:	ZmPref.TYPE_RADIO_GROUP,
462 		orientation:		ZmPref.ORIENT_HORIZONTAL,
463 		displayOptions:		ZmMsg.lifetimeDurationDays,
464 		options:			[ 1, 3, 7, 30 ],
465 		approximateFunction: ZmPref.approximateLifetimeJunk,
466 		displayFunction:	ZmPref.durationDay2Int,
467 		valueFunction:		ZmPref.int2DurationDay,
468 		validationFunction:	ZmPref.validateLifetimeJunk
469 	});
470 
471 	ZmPref.registerPref("MAIL_LIFETIME_SENT", {
472 		displayContainer:	ZmPref.TYPE_RADIO_GROUP,
473 		orientation:		ZmPref.ORIENT_HORIZONTAL,
474 		displayOptions:		[ ZmMsg.lifetimeDurationDays, ZmMsg.lifetimeDurationDays,
475 							  ZmMsg.lifetimeDurationDays, ZmMsg.lifetimeDurationDays,
476 							  ZmMsg.lifetimeDurationDays, ZmMsg.lifetimeDurationNever ],
477 		options:			[ 30, 45, 60, 90, 120, 0 ],
478 		approximateFunction: ZmPref.approximateLifetimeSent,
479 		displayFunction:	ZmPref.durationDay2Int,
480 		valueFunction:		ZmPref.int2DurationDay,
481 		validationFunction:	ZmPref.validateLifetime
482 	});
483 
484 	ZmPref.registerPref("MAIL_LIFETIME_TRASH", {
485 		displayContainer:	ZmPref.TYPE_RADIO_GROUP,
486 		orientation:		ZmPref.ORIENT_HORIZONTAL,
487 		displayOptions:		ZmMsg.lifetimeDurationDays,
488 		options:			[ 1, 3, 7, 30 ],
489 		approximateFunction: ZmPref.approximateLifetimeTrash,
490 		displayFunction:	ZmPref.durationDay2Int,
491 		valueFunction:		ZmPref.int2DurationDay,
492 		validationFunction:	ZmPref.validateLifetimeTrash
493 	});
494 
495 	ZmPref.registerPref("MAIL_LOCAL_DELIVERY_DISABLED", {
496 		displayName:		ZmMsg.mailDeliveryDisabled,
497 		displayContainer:	ZmPref.TYPE_CHECKBOX,
498 		precondition:		ZmSetting.MAIL_FORWARDING_ENABLED,
499 		validationFunction:	ZmMailApp.validateMailLocalDeliveryDisabled,
500 		errorMessage:		ZmMsg.errorMissingFwdAddr
501 	});
502 
503 	ZmPref.registerPref("MAIL_NOTIFY_ALL", {
504 		displayName:		ZmMsg.messageNotificationFoldersLabel,
505 		displayContainer:	ZmPref.TYPE_RADIO_GROUP,
506 		orientation:		ZmPref.ORIENT_VERTICAL,
507 		displayOptions:		[ ZmMsg.messageNotificationFoldersInbox, ZmMsg.messageNotificationFoldersAll ],
508 		options:			[ false, true ]
509 	});
510 
511 	ZmPref.registerPref("MAIL_NOTIFY_SOUNDS", {
512 		displayName:		ZmMsg.playSound,
513 		displayContainer:	ZmPref.TYPE_CHECKBOX
514 	});
515 
516 	ZmPref.registerPref("MAIL_NOTIFY_APP", {
517 		displayName:		ZmMsg.flashMailAppTab,
518 		displayContainer:	ZmPref.TYPE_CHECKBOX
519 	});
520 
521 	ZmPref.registerPref("MAIL_NOTIFY_BROWSER", {
522 		displayName:		ZmMsg.flashBrowser,
523 		displayContainer:	ZmPref.TYPE_CHECKBOX
524 	});
525 
526 	ZmPref.registerPref("MAIL_SEND_READ_RECEIPTS", {
527 		displayContainer:	ZmPref.TYPE_RADIO_GROUP,
528 		displayOptions:		[	ZmMsg.readReceiptNever,
529 								ZmMsg.readReceiptAlways,
530 								ZmMsg.readReceiptAsk
531 							],
532 		options:			[ 	ZmMailApp.SEND_RECEIPT_NEVER,
533 								ZmMailApp.SEND_RECEIPT_ALWAYS,
534 								ZmMailApp.SEND_RECEIPT_PROMPT
535 							],
536 		precondition:		ZmSetting.MAIL_READ_RECEIPT_ENABLED
537 	});
538 
539 	ZmPref.registerPref("MAIL_WHITELIST", {
540 		displayContainer:	ZmPref.TYPE_CUSTOM
541 	});
542 
543 	ZmPref.registerPref("NOTIF_ADDRESS", {
544 		displayName:		ZmMsg.mailNotifAddress,
545 		displayContainer:	ZmPref.TYPE_INPUT,
546 		validationFunction: ZmPref.validateEmail,
547 		errorMessage:       ZmMsg.invalidEmail,
548 		precondition:		ZmSetting.NOTIF_FEATURE_ENABLED,
549 		hint:				ZmMsg.enterEmailAddress,
550 		setFunction:        ZmPref.setMailNotificationAddressValue
551 	});
552 
553 	ZmPref.registerPref("OPEN_MAIL_IN_NEW_WIN", {
554 		displayName:		ZmMsg.openMailNewWin,
555 		displayContainer:	ZmPref.TYPE_CHECKBOX,
556 		precondition:		ZmSetting.DETACH_MAILVIEW_ENABLED
557 	});
558 
559 	ZmPref.registerPref("POP_DOWNLOAD_SINCE_VALUE", {
560 		displayContainer:	ZmPref.TYPE_STATIC,
561 		precondition:		ZmSetting.POP_ENABLED
562 	});
563 	ZmPref.registerPref("POP_DOWNLOAD_SINCE", {
564 		displayContainer:	ZmPref.TYPE_RADIO_GROUP,
565 		displayOptions:		[	ZmMsg.externalAccessPopDownloadAll,
566 								"*** NOT SHOWN ***",
567 								ZmMsg.externalAccessPopDownloadFromNow
568 							],
569 		options:			[	ZmMailApp.POP_DOWNLOAD_SINCE_ALL,
570 								ZmMailApp.POP_DOWNLOAD_SINCE_NO_CHANGE,
571 								ZmMailApp.POP_DOWNLOAD_SINCE_FROM_NOW
572 							],
573 		displayFunction:	ZmPref.downloadSinceDisplay,
574 		valueFunction:		ZmPref.downloadSinceValue,
575 		precondition:		ZmSetting.POP_ENABLED
576 	});
577 	ZmPref.registerPref("POP_DELETE_OPTION", {
578 		displayContainer:	ZmPref.TYPE_RADIO_GROUP,
579 		displayOptions:     [   ZmMsg.popDeleteHardDelete,
580                                 ZmMsg.popDeleteTrash,
581                                 ZmMsg.popDeleteRead,
582                                 ZmMsg.popDeleteKeep
583                             ],
584 		options:            [   ZmMailApp.POP_DELETE_OPTION_HARD_DELETE,
585                                 ZmMailApp.POP_DELETE_OPTION_TRASH,
586                                 ZmMailApp.POP_DELETE_OPTION_READ,
587                                 ZmMailApp.POP_DELETE_OPTION_KEEP
588                             ],
589 		precondition:       ZmSetting.POP_ENABLED
590 	});
591 	ZmPref.registerPref("POP_INCLUDE_SPAM", {
592 		displayName:		ZmMsg.popIncludeSpam,
593 		displayContainer:	ZmPref.TYPE_CHECKBOX
594 	});
595     ZmPref.registerPref("REPLY_TO_ADDRESS", {
596 		displayName:		ZmMsg.replyToAddress,
597 		displayContainer:	ZmPref.TYPE_INPUT,
598 		validationFunction: ZmPref.validateEmail,
599 		errorMessage:       ZmMsg.invalidEmail
600 	});
601 
602 	ZmPref.registerPref("SELECT_AFTER_DELETE", {
603 		displayContainer:	ZmPref.TYPE_RADIO_GROUP,
604 		orientation:		ZmPref.ORIENT_VERTICAL,
605 		displayOptions: 	[ZmMsg.selectNext, ZmMsg.selectPrevious, ZmMsg.selectAdapt],
606 		options: 			[ZmSetting.DELETE_SELECT_NEXT, ZmSetting.DELETE_SELECT_PREV, ZmSetting.DELETE_SELECT_ADAPT]
607 	});
608 
609 	ZmPref.registerPref("SIGNATURE", {
610 		displayName:		ZmMsg.signature,
611 		displayContainer:	ZmPref.TYPE_TEXTAREA,
612 		maxLength:			ZmPref.MAX_LENGTH[ZmSetting.SIGNATURE],
613 		errorMessage:       AjxMessageFormat.format(ZmMsg.invalidSignature, ZmPref.MAX_LENGTH[ZmSetting.SIGNATURE])
614 	});
615 
616 	ZmPref.registerPref("SIGNATURE_ENABLED", {
617 		displayName:		ZmMsg.signatureEnabled,
618 		displayContainer:	ZmPref.TYPE_CHECKBOX
619 	});
620 
621 	ZmPref.registerPref("SIGNATURE_STYLE", {
622 		displayName:		ZmMsg.signatureStyle,
623 		displayContainer:	ZmPref.TYPE_RADIO_GROUP,
624 		orientation:		ZmPref.ORIENT_HORIZONTAL,
625 		displayOptions:		[ZmMsg.aboveQuotedText, ZmMsg.atBottomOfMessage],
626 		options:			[ZmSetting.SIG_OUTLOOK, ZmSetting.SIG_INTERNET]
627 	});
628 
629 	ZmPref.registerPref("SIGNATURES", {
630 		displayContainer:	ZmPref.TYPE_CUSTOM,
631         initFunction: ZmPref.regenerateSignatureEditor
632 	});
633 
634 	ZmPref.registerPref("START_DATE_ENABLED", {
635 		displayContainer:	ZmPref.TYPE_CHECKBOX,
636 		displayName:		ZmMsg.startOn,
637 		precondition:		ZmSetting.VACATION_MSG_FEATURE_ENABLED
638 	});
639 
640     ZmPref.registerPref("TAB_IN_EDITOR", {
641         displayName:        ZmMsg.tabInEditor,
642         displayContainer:	ZmPref.TYPE_CHECKBOX
643     });
644 
645     ZmPref.registerPref("VACATION_DURATION_ENABLED", {
646 		displayContainer:	ZmPref.TYPE_CHECKBOX,
647 		displayName:		ZmMsg.oooDurationLabel,
648 		precondition:		ZmSetting.VACATION_MSG_FEATURE_ENABLED
649 	});
650 
651     ZmPref.registerPref("VACATION_DURATION_ALL_DAY", {
652 		displayContainer:	ZmPref.TYPE_CHECKBOX,
653 		displayName:		ZmMsg.oooAllDayDurationLabel,
654 		precondition:		ZmSetting.VACATION_MSG_FEATURE_ENABLED
655 	});
656 
657     ZmPref.registerPref("VACATION_CALENDAR_ENABLED", {
658 		displayContainer:	ZmPref.TYPE_CHECKBOX,
659 		displayName:		ZmMsg.vacationCalLabel,
660 		precondition:		ZmSetting.VACATION_MSG_FEATURE_ENABLED
661 	});
662 
663 	ZmPref.registerPref("VACATION_FROM", {
664 		displayName:		ZmMsg.startDate,
665 		displayContainer:	ZmPref.TYPE_INPUT,
666 		precondition:		ZmSetting.VACATION_MSG_FEATURE_ENABLED,
667 		displayFunction:	AjxDateUtil.dateGMT2Local,
668 		valueFunction:		AjxDateUtil.dateLocal2GMT
669 	});
670 
671     ZmPref.registerPref("VACATION_UNTIL", {
672 		displayName:		ZmMsg.endDate,
673 		displayContainer:	ZmPref.TYPE_INPUT,
674 		precondition:		ZmSetting.VACATION_MSG_FEATURE_ENABLED,
675 		displayFunction:	AjxDateUtil.dateGMT2Local,
676 		valueFunction:		AjxDateUtil.dateLocal2GMT
677 	});
678 
679     ZmPref.registerPref("VACATION_MSG", {
680 		displayName:		ZmMsg.awayMessage,
681 		displayContainer:	ZmPref.TYPE_TEXTAREA,
682 		maxLength:			ZmPref.MAX_LENGTH[ZmSetting.AWAY_MESSAGE],
683 		errorMessage:       AjxMessageFormat.format(ZmMsg.invalidAwayMessage, ZmPref.MAX_LENGTH[ZmSetting.AWAY_MESSAGE]),
684 		precondition:		ZmSetting.VACATION_MSG_FEATURE_ENABLED,
685 		validationFunction:	ZmMailApp.validateVacationMsg
686 	});
687 
688 	ZmPref.registerPref("VACATION_MSG_ENABLED", {
689 		displayName:		ZmMsg.outOfOffice,
690 		displayContainer:	ZmPref.TYPE_RADIO_GROUP,
691         orientation:		ZmPref.ORIENT_VERTICAL,
692 		errorMessage:		ZmMsg.missingAwayMessage,
693 		displayOptions:		[ZmMsg.noAutoReplyMessage, ZmMsg.autoReplyMessage],
694 		options:			[false, true],
695         inputId:            ["VACATION_MSG_DISABLED", "VACATION_MSG_ENABLED"]
696 	});
697 
698     ZmPref.registerPref("VACATION_EXTERNAL_TYPE", {  // The inside content been left empty, as we just need to register this pref with settings.
699     });                                              // Depending upon the option the user has chosen in OOO vacation external select dropdown, on saving we add the relevant pref to the list that constructs the request, refer ZmPref.addOOOVacationExternalPrefToList
700 
701     ZmPref.registerPref("VACATION_EXTERNAL_SUPPRESS", {
702         displayContainer:   ZmPref.TYPE_SELECT,
703         displayOptions:     [ZmMsg.vacationExternalAllStandard, ZmMsg.vacationExternalAllCustom, ZmMsg.vacationExternalAllExceptABCustom, ZmMsg.vacationExternalReplySuppress],
704         options:            [false, false, false, true],
705         initFunction:       ZmPref.initOOOVacationExternalSuppress,
706         setFunction:        ZmPref.addOOOVacationExternalPrefOnSave,
707         changeFunction:     ZmPref.handleOOOVacationExternalOptionChange,
708         validationFunction:	ZmMailApp.validateExternalVacationMsg,
709         errorMessage:		ZmMsg.missingAwayMessage
710     });
711 
712     ZmPref.registerPref("VACATION_CALENDAR_TYPE", {
713 		displayName:		ZmMsg.vacationExternalType,
714 		displayContainer:	ZmPref.TYPE_SELECT,
715 		displayOptions:		[ZmMsg.outOfOffice,ZmMsg.busy],
716 		options:			 ["OUTOFOFFICE","BUSY"]
717 	});
718 
719     ZmPref.registerPref("VACATION_EXTERNAL_MSG", {
720 		displayName:		ZmMsg.externalAwayMessage,
721 		displayContainer:	ZmPref.TYPE_TEXTAREA,
722 		maxLength:			ZmPref.MAX_LENGTH[ZmSetting.AWAY_MESSAGE],
723         precondition:		ZmSetting.VACATION_MSG_FEATURE_ENABLED
724 	});
725 
726 	ZmPref.registerPref("VACATION_EXTERNAL_MSG_ENABLED", {  // The content been left empty, as we just need to register this pref with settings.
727     });                                                     // Depending upon the option the user has chosen in OOO external select dropdown, on saving we add the relevant pref to the list that constructs the request, refer ZmPref.addOOOVacationExternalPrefToList
728 
729 	AjxDispatcher.require("Alert");
730 	var notifyText = ZmDesktopAlert.getInstance().getDisplayText();
731 	ZmPref.registerPref("MAIL_NOTIFY_TOASTER", {
732 		displayFunc:		function() { return notifyText; },
733 		precondition:		!!notifyText,
734 		displayContainer:	ZmPref.TYPE_CHECKBOX
735 	});
736 
737 	if (appCtxt.isOffline) {
738 		ZmPref.registerPref("OFFLINE_NOTIFY_NEWMAIL_ON_INBOX", {
739 			displayContainer:	ZmPref.TYPE_RADIO_GROUP,
740 			displayOptions:		[ZmMsg.notifyNewMailOnInbox, ZmMsg.notifyNewMailOnAny],
741 			options:			[true, false]
742 		});
743 	}
744 };
745 
746 ZmMailApp.prototype.formatKeySeq = function(keySeq) {
747 	// Make sure the modifierKey list is created.  This will create the modifierKeys and cache them, but not display them
748 	new ZmShortcutList({cols:[]});
749 	return ZmShortcutList._formatDisplay(keySeq);
750 }
751 
752 /**
753  * @private
754  */
755 ZmMailApp.validateForwardEmail =
756 function(emailStr) {
757 	if (!emailStr || emailStr == "") {
758 		var section = ZmPref.getPrefSectionWithPref(ZmSetting.MAIL_FORWARDING_ADDRESS);
759 		if (!section) { return false; }
760 		var view = appCtxt.getApp(ZmApp.PREFERENCES).getPrefController().getPrefsView();
761 		var checkbox = view.getView(section.id).getFormObject(ZmSetting.MAIL_LOCAL_DELIVERY_DISABLED);
762 		if (checkbox && checkbox.isSelected()) {
763 			checkbox.setSelected(false);
764 		}
765 	}
766 	return ZmPref.validateEmail(emailStr);
767 };
768 
769 /**
770  * @private
771  */
772 ZmMailApp.validateMailLocalDeliveryDisabled =
773 function(checked) {
774 	if (!checked) { return true; }
775 	var section = ZmPref.getPrefSectionWithPref(ZmSetting.MAIL_FORWARDING_ADDRESS);
776 	if (!section) { return false; }
777 	var view = appCtxt.getApp(ZmApp.PREFERENCES).getPrefController().getPrefsView();
778 	var input = view.getView(section.id).getFormObject(ZmSetting.MAIL_FORWARDING_ADDRESS);
779 	return (input != null && input.isValid());
780 };
781 
782 /**
783  * Make sure the server won't be sending out a blank away msg for the user. Check for a
784  * combination of an empty away msg and a checked box for "send away message". Since a
785  * pref is validated only if it changes, we have to have validation functions for both
786  * prefs.
787  *
788  * @private
789  */
790 ZmMailApp.validateVacationMsg =
791 function(awayMsg) {
792 	if (awayMsg && (awayMsg.length > 0)) { return true; }
793 	var section = ZmPref.getPrefSectionWithPref(ZmSetting.VACATION_MSG_ENABLED);
794 	if (!section) { return false; }
795 	var view = appCtxt.getApp(ZmApp.PREFERENCES).getPrefController().getPrefsView();
796 	var input = view.getView(section.id).getFormObject(ZmSetting.VACATION_MSG_ENABLED);
797     var isValid = (input && !(input.getSelectedValue() == "true"));
798     if (!isValid)
799         ZmPref.SETUP["VACATION_MSG"].errorMessage = ZmMsg.missingAwayMessage;
800 
801 	return isValid
802 };
803 
804 /**
805  * @private
806  */
807 ZmMailApp.validateVacationMsgEnabled =
808 function(checked) {
809     if (!checked) { return true; }
810     var section = ZmPref.getPrefSectionWithPref(ZmSetting.VACATION_MSG);
811     if (!section) { return false; }
812     var view = appCtxt.getApp(ZmApp.PREFERENCES).getPrefController().getPrefsView();
813     var input = view.getView(section.id).getFormObject(ZmSetting.VACATION_MSG);
814     if (!input) { return false; }
815     var awayMsg = input.getValue();
816     return (awayMsg && (awayMsg.length > 0));
817 };
818 
819 /**
820  * Make sure the server won't be sending out a blank away msg for the external user.
821  * We are ignoring this validation in two cases :
822  * a) when 'Do not send auto replies' radio button is selected
823  * b) in OOO vacation external sender type, first and last option is selected, as in this case the
824  * OOO external message container is not visible
825  * @private
826  */
827 ZmMailApp.validateExternalVacationMsg =
828 function() {
829     var section = ZmPref.getPrefSectionWithPref(ZmSetting.VACATION_MSG_ENABLED);
830     if (!section) { return false; }
831     var view = appCtxt.getApp(ZmApp.PREFERENCES).getPrefController().getPrefsView().getView(section.id);
832     var cbox = view.getFormObject(ZmSetting.VACATION_MSG_ENABLED);
833     if (cbox && cbox.getSelectedValue()==="false"){ // 'Do not send auto replies' radio button is selected ..
834         return true;
835     }
836     var externalSelect =  view.getFormObject(ZmSetting.VACATION_EXTERNAL_SUPPRESS);
837     var selectOptionValue = externalSelect.getText();
838     if (selectOptionValue.indexOf(ZmMsg.vacationExternalAllStandard) >=0 || selectOptionValue.indexOf(ZmMsg.vacationExternalReplySuppress) >=0) {
839         return true;
840     }
841     var externalTxtArea = view.getFormObject(ZmSetting.VACATION_EXTERNAL_MSG);
842     var awayMsg = externalTxtArea.getValue();
843     return (awayMsg && (awayMsg.length > 0));
844 };
845 
846 /**
847  * @private
848  */
849 ZmMailApp.validateExternalVacationMsgEnabled =
850 function(checked) {
851     if (!checked) { return true; }
852     var section = ZmPref.getPrefSectionWithPref(ZmSetting.VACATION_EXTERNAL_MSG);
853     if (!section) { return false; }
854     var view = appCtxt.getApp(ZmApp.PREFERENCES).getPrefController().getPrefsView();
855     var input = view.getView(section.id).getFormObject(ZmSetting.VACATION_EXTERNAL_MSG);
856     if (!input) { return false; }
857     var awayMsg = input.getValue();
858     return (awayMsg && (awayMsg.length > 0));
859 };
860 
861 /**
862  * @private
863  */
864 ZmMailApp.prototype._registerOperations =
865 function() {
866 	ZmOperation.registerOp(ZmId.OP_ADD_FILTER_RULE, {textKey:"createFilter", image:"Plus"}, ZmSetting.FILTERS_ENABLED);
867 	ZmOperation.registerOp(ZmId.OP_ADD_TO_FILTER_RULE, {textKey: "addToFilter", image: "MailRule"}, ZmSetting.FILTERS_ENABLED);
868 	ZmOperation.registerOp(ZmId.OP_ADD_SIGNATURE, {textKey:"signature", image:"AddSignature", tooltipKey:"chooseSignature"}, ZmSetting.SIGNATURES_ENABLED);
869 	ZmOperation.registerOp(ZmId.OP_CHECK_MAIL, {textKey:"checkMail", tooltipKey:"checkMailPrefDefault", image:"Refresh", textPrecedence:90, showImageInToolbar: true});
870 	ZmOperation.registerOp(ZmId.OP_CREATE_APPT, {textKey:"createAppt", image:"NewAppointment"}, ZmSetting.CALENDAR_ENABLED);
871 	ZmOperation.registerOp(ZmId.OP_CREATE_TASK, {textKey:"createTask", image:"NewTask"}, ZmSetting.TASKS_ENABLED);
872 	ZmOperation.registerOp(ZmId.OP_DELETE_CONV, {textKey:"delConv", image:"DeleteConversation"}, ZmSetting.CONVERSATIONS_ENABLED);
873 	ZmOperation.registerOp(ZmId.OP_DELETE_MSG, {textKey:"delMsg", image:"DeleteMessage"});
874 	ZmOperation.registerOp(ZmId.OP_DELETE_MENU, {textKey:"del", image:"Delete", tooltipKey:"deleteTooltip"});
875 	ZmOperation.registerOp(ZmId.OP_DETACH_COMPOSE, {tooltipKey:"detachComposeTooltip", image:"OpenInNewWindow"});
876 	ZmOperation.registerOp(ZmId.OP_DRAFT, null, ZmSetting.SAVE_DRAFT_ENABLED);
877 	ZmOperation.registerOp(ZmId.OP_EDIT_FILTER_RULE, {textKey:"filterEdit", image:"Edit"}, ZmSetting.FILTERS_ENABLED);
878 	ZmOperation.registerOp(ZmId.OP_FORWARD, {textKey:"forward", tooltipKey:"forwardTooltip", image:"Forward", shortcut:ZmKeyMap.FORWARD, textPrecedence:46});
879 	ZmOperation.registerOp(ZmId.OP_FORWARD_ATT, {textKey:"forwardAtt", tooltipKey:"forwardAtt", image:"Forward"});
880 	ZmOperation.registerOp(ZmId.OP_FORWARD_CONV, {textKey:"forwardConv", tooltipKey:"forwardConv", image:"Forward"});
881 	ZmOperation.registerOp(ZmId.OP_FORWARD_INLINE, {textKey:"forwardInline", tooltipKey:"forwardTooltip", image:"Forward"});
882 	ZmOperation.registerOp(ZmId.OP_INC_ATTACHMENT, {textKey:"includeMenuAttachment"});
883     ZmOperation.registerOp(ZmId.OP_INC_BODY, {textKey:"includeMenuBody"});
884 	ZmOperation.registerOp(ZmId.OP_INC_NONE, {textKey:"includeMenuNone"});
885 	ZmOperation.registerOp(ZmId.OP_INC_SMART, {textKey:"includeMenuSmart"});
886 	ZmOperation.registerOp(ZmId.OP_INCLUDE_HEADERS, {textKey:"includeHeaders"});
887 	ZmOperation.registerOp(ZmId.OP_KEEP_READING, {textKey:"keepReading", tooltipKey:"keepReadingTooltip", shortcut:ZmKeyMap.KEEP_READING});
888 	ZmOperation.registerOp(ZmId.OP_MARK_READ, {textKey:"markAsRead", image:"ReadMessage", shortcut:ZmKeyMap.MARK_READ});
889 	ZmOperation.registerOp(ZmId.OP_MARK_UNREAD, {textKey:"markAsUnread", image:"UnreadMessage", shortcut:ZmKeyMap.MARK_UNREAD});
890 	ZmOperation.registerOp(ZmId.OP_FLAG, {textKey:"flag", image:"FlagRed", shortcut:ZmKeyMap.FLAG}, ZmSetting.FLAGGING_ENABLED);
891 	ZmOperation.registerOp(ZmId.OP_UNFLAG, {textKey:"unflag", image:"FlagDis", shortcut:ZmKeyMap.FLAG}, ZmSetting.FLAGGING_ENABLED);
892 	ZmOperation.registerOp(ZmId.OP_MOVE_DOWN_FILTER_RULE, {textKey:"filterMoveDown", image:"DownArrow"}, ZmSetting.FILTERS_ENABLED);
893 	ZmOperation.registerOp(ZmId.OP_MOVE_TO_BCC, {textKey:"moveToBcc"});
894 	ZmOperation.registerOp(ZmId.OP_MOVE_TO_CC, {textKey:"moveToCc"});
895 	ZmOperation.registerOp(ZmId.OP_MOVE_TO_TO, {textKey:"moveToTo"});
896 	ZmOperation.registerOp(ZmId.OP_MOVE_UP_FILTER_RULE, {textKey:"filterMoveUp", image:"UpArrow"}, ZmSetting.FILTERS_ENABLED);
897 	ZmOperation.registerOp(ZmId.OP_NEW_MESSAGE, {textKey:"newEmail", tooltipKey:"newMessageTooltip", image:"NewMessage", shortcut:ZmKeyMap.NEW_MESSAGE});
898 	ZmOperation.registerOp(ZmId.OP_NEW_MESSAGE_WIN, {textKey:"newEmail", tooltipKey:"newMessageTooltip", image:"NewMessage", shortcut:ZmKeyMap.NEW_MESSAGE_WIN});
899 	ZmOperation.registerOp(ZmId.OP_PRIORITY_HIGH, {textKey:"priorityHigh", image:"PriorityHigh_list"});
900 	ZmOperation.registerOp(ZmId.OP_PRIORITY_LOW, {textKey:"priorityLow", image:"PriorityLow_list"});
901 	ZmOperation.registerOp(ZmId.OP_PRIORITY_NORMAL, {textKey:"priorityNormal", image:"PriorityNormal_list"});
902 	ZmOperation.registerOp(ZmId.OP_REMOVE_FILTER_RULE, {textKey:"filterRemove", image:"Delete"}, ZmSetting.FILTERS_ENABLED);
903     ZmOperation.registerOp(ZmId.OP_REDIRECT, {textKey:"mailRedirect", tooltipKey:"mailRedirectTooltip", image:"Redirect"});
904 	ZmOperation.registerOp(ZmId.OP_REPLY, {textKey:"reply", tooltipKey:"replyTooltip", image:"Reply", shortcut:ZmKeyMap.REPLY, textPrecedence:50});
905 	ZmOperation.registerOp(ZmId.OP_REPLY_ALL, {textKey:"replyAll", tooltipKey:"replyAllTooltip", image:"ReplyAll", shortcut:ZmKeyMap.REPLY_ALL, textPrecedence:48});
906 	ZmOperation.registerOp(ZmId.OP_REQUEST_READ_RECEIPT, {textKey:"requestReadReceipt", image:"ReadMessage"});
907 	ZmOperation.registerOp(ZmId.OP_RESET, {textKey:"reset", image:"Refresh", tooltipKey: "refreshFilters"});
908 	ZmOperation.registerOp(ZmId.OP_RUN_FILTER_RULE, {textKey:"filterRun", image:"SwitchFormat"}, [ ZmSetting.MAIL_ENABLED, ZmSetting.FILTERS_ENABLED ]);
909 	ZmOperation.registerOp(ZmId.OP_SAVE_DRAFT, {textKey:"saveDraft", tooltipKey:"saveDraftTooltip", image:"DraftFolder", shortcut:ZmKeyMap.SAVE}, ZmSetting.SAVE_DRAFT_ENABLED);
910 	ZmOperation.registerOp(ZmId.OP_SEND_MENU, {textKey:"send", tooltipKey:"sendTooltip", image:"Send"}, ZmSetting.SAVE_DRAFT_ENABLED);
911 	ZmOperation.registerOp(ZmId.OP_SEND_LATER, {textKey:"sendLater", tooltipKey:"sendLaterTooltip", image:"SendLater"}, ZmSetting.SAVE_DRAFT_ENABLED);
912 	ZmOperation.registerOp(ZmId.OP_SHOW_BCC, {textKey:"showBcc"});
913 	ZmOperation.registerOp(ZmId.OP_SHOW_CONV, {textKey:"showConv", image:"Conversation"});
914 	ZmOperation.registerOp(ZmId.OP_SHOW_ORIG, {textKey:"showOrig", image:"Message"});
915 	ZmOperation.registerOp(ZmId.OP_SPAM, {textKey:"junkLabel", tooltipKey:"junkTooltip", image:"JunkMail", shortcut:ZmKeyMap.SPAM, textPrecedence:70}, ZmSetting.SPAM_ENABLED);
916 	ZmOperation.registerOp(ZmId.OP_USE_PREFIX, {textKey:"usePrefix"});
917 };
918 
919 /**
920  * @private
921  */
922 ZmMailApp.prototype._registerItems =
923 function() {
924 	ZmItem.registerItem(ZmItem.CONV,
925 						{app:			ZmApp.MAIL,
926 						 nameKey:		"conversation",
927 						 icon:			"Conversation",
928 						 soapCmd:		"ConvAction",
929 						 itemClass:		"ZmConv",
930 						 node:			"c",
931 						 organizer:		ZmOrganizer.FOLDER,
932 						 dropTargets:	[ZmOrganizer.FOLDER, ZmOrganizer.TAG, ZmOrganizer.ZIMLET],
933 						 searchType:	"conversation",
934 						 resultsList:
935 		AjxCallback.simpleClosure(function(search) {
936 			AjxDispatcher.require("MailCore");
937 			return new ZmMailList(ZmItem.CONV, search);
938 		}, this)
939 						});
940 
941 	ZmItem.registerItem(ZmItem.MSG,
942 						{app:			ZmApp.MAIL,
943 						 nameKey:		"message",
944 						 icon:			"Message",
945 						 soapCmd:		"MsgAction",
946 						 itemClass:		"ZmMailMsg",
947 						 node:			"m",
948 						 organizer:		ZmOrganizer.FOLDER,
949 						 dropTargets:	[ZmOrganizer.FOLDER, ZmOrganizer.TAG, ZmOrganizer.ZIMLET],
950 						 searchType:	"message",
951 						 resultsList:
952 		AjxCallback.simpleClosure(function(search) {
953 			AjxDispatcher.require("MailCore");
954 			return new ZmMailList(ZmItem.MSG, search);
955 		}, this)
956 						});
957 
958 	ZmItem.registerItem(ZmItem.ATT,
959 						{app:			ZmApp.MAIL,
960 						 nameKey:		"attachment",
961 						 icon:			"Attachment",
962 						 itemClass:		"ZmMimePart",
963 						 node:			"mp",
964 						 resultsList:
965 		AjxCallback.simpleClosure(function(search) {
966 			return new ZmMailList(ZmItem.ATT, search);
967 		}, this)
968 						});
969 };
970 
971 /**
972  * @private
973  */
974 ZmMailApp.prototype._setupSearchToolbar =
975 function() {
976 	if (appCtxt.get(ZmSetting.MAIL_ENABLED)) {
977 		ZmSearchToolBar.addMenuItem(ZmId.SEARCH_MAIL,
978 									{msgKey:		"mail",
979 									 tooltipKey:	"searchMail",
980 									 icon:			"Message",
981 									 shareIcon:		"SharedMailFolder",
982 									 id:			ZmId.getMenuItemId(ZmId.SEARCH, ZmId.SEARCH_MAIL)
983 									});
984 	}
985 };
986 
987 ZmMailApp.prototype._registerApp =
988 function() {
989 	var newItemOps = {};
990 	newItemOps[ZmOperation.NEW_MESSAGE] = "message";
991 
992 	var actionCodes = {};
993 	actionCodes[ZmKeyMap.NEW_MESSAGE]		= ZmOperation.NEW_MESSAGE;
994 	actionCodes[ZmKeyMap.NEW_MESSAGE_WIN]	= ZmOperation.NEW_MESSAGE_WIN;
995 
996 	ZmApp.registerApp(ZmApp.MAIL, {
997 				mainPkg:			"MailCore",
998 				nameKey:			"mail",
999 				icon:				"MailApp",
1000 				textPrecedence:		70,
1001 				chooserTooltipKey:	"goToMail",
1002 				viewTooltipKey:		"displayMailToolTip",
1003 				defaultSearch:		appCtxt.isChildWindow ? null : ZmId.SEARCH_MAIL,
1004 				organizer:			ZmOrganizer.FOLDER,
1005 				overviewTrees:		[ZmOrganizer.FOLDER, ZmOrganizer.SEARCH, ZmOrganizer.TAG],
1006 				searchTypes:		[ZmItem.MSG, ZmItem.CONV],
1007 				newItemOps:			newItemOps,
1008 				actionCodes:		actionCodes,
1009 				gotoActionCode:		ZmKeyMap.GOTO_MAIL,
1010 				newActionCode:		ZmKeyMap.NEW_MESSAGE,
1011 				qsViews:			["compose", "msg"],
1012 				chooserSort:		10,
1013 				defaultSort:		10,
1014 				upsellUrl:			ZmSetting.MAIL_UPSELL_URL,
1015                 //quickCommandType:	ZmQuickCommand[ZmId.ITEM_MSG],
1016 				searchResultsTab:	true
1017 			});
1018 };
1019 
1020 // App API
1021 
1022 ZmMailApp.prototype.startup =
1023 function(result) {
1024 };
1025 
1026 /**
1027  * Normalize the notifications that occur when a virtual conv gets promoted to a real conv.
1028  * For example, a virtual conv with ID -676 and one msg (ID 676) receives a second msg (ID 677)
1029  * and becomes a real conv with an ID of 678. The following notifications will arrive:
1030  *
1031  *		deleted:	-676
1032  *		created:	c {id:678, n:2}
1033  *					m {id:677, cid:678}
1034  *		modified:	m {id:676, cid:678}
1035  *
1036  * Essentially, we want to handle this as:
1037  *
1038  * 		created:	m {id:677, cid:678}
1039  *		modified:	c {id:-676, _newId: 678}
1040  * 					m {id:676, cid:678}
1041  *
1042  * @private
1043  */
1044 ZmMailApp.prototype.preNotify =
1045 function(notify) {
1046 
1047 	if (!(notify.deleted && notify.created && notify.modified))	{ return notify; }
1048 
1049 	// first, see if we are deleting any virtual convs (which have negative IDs)
1050 	var virtConvDeleted = false;
1051 	var deletedIds = notify.deleted.id && notify.deleted.id.split(",");
1052 	var virtConv = {};
1053 	var newDeletedIds = [];
1054 	if (deletedIds && deletedIds.length) {
1055 		for (var i = 0; i < deletedIds.length; i++) {
1056 			var id = deletedIds[i];
1057 			var nId = ZmOrganizer.normalizeId(id);
1058 			if (nId < 0) {
1059 				virtConv[nId] = true;
1060 				virtConvDeleted = true;
1061 			} else {
1062 				newDeletedIds.push(id);
1063 			}
1064 		}
1065 	}
1066 	if (!virtConvDeleted) {
1067 		return notify;
1068 	}
1069 
1070 	// look for creates of convs that mean a virtual conv got promoted
1071 	var gotNewConv = false;
1072 	var createdMsgs = {};
1073 	var createdConvs = {};
1074 	for (var name in notify.created) {
1075 		var list = notify.created[name];
1076 		if (list && list.length) {
1077 			for (var i = 0; i < list.length; i++) {
1078 				var create = list[i];
1079 				var id = create.id;
1080 				var extra = (name == "m") ? "|cid=" + create.cid + "|l=" + create.l : "|n=" + create.n;
1081 				AjxDebug.println(AjxDebug.NOTIFY, name + ": id=" + id + "|su='" + create.su + "'|f=" + create.f + "|d=" + create.d + extra);
1082 				if (name == "m") {
1083 					createdMsgs[id] = create;
1084 				} else if (name == "c" && (create.n > 1)) {
1085 					// this is *probably* a create for a real conv from a virtual conv
1086 					createdConvs[id] = create;
1087 					gotNewConv = true;
1088 				}
1089 			}
1090 		}
1091 	}
1092 	if (!gotNewConv) {
1093 		return notify;
1094 	}
1095 
1096 	// last thing to confirm virt conv promotion is msg changing cid
1097 	var msgMoved = false;
1098 	var newToOldCid = {};
1099 	var movedMsgs = {};
1100 	var list = notify.modified.m;
1101 	if (list && list.length) {
1102 		for (var i = 0; i < list.length; i++) {
1103 			var mod = list[i];
1104 			var id = mod.id;
1105 			var nId = ZmOrganizer.normalizeId(id);
1106 			var virtCid = nId * -1;
1107 			if (virtConv[virtCid] && createdConvs[mod.cid]) {
1108 				msgMoved = true;
1109 				movedMsgs[id] = mod;
1110 				newToOldCid[mod.cid] = appCtxt.multiAccounts ? ZmOrganizer.getSystemId(virtCid) : virtCid;
1111 				createdConvs[mod.cid]._wasVirtConv = true;
1112 				// go ahead and update the msg cid, since it's used in
1113 				// notification processing for creates
1114 				var msg = appCtxt.getById(id),
1115 					folderId;
1116 				if (msg) {
1117 					msg.cid = mod.cid;
1118 					folderId = msg.folderId;
1119 				}
1120 				createdConvs[mod.cid].m = [{
1121 					id: id,
1122 					l:  folderId
1123 				}];
1124 			}
1125 		}
1126 	}
1127 	if (!msgMoved) {
1128 		return notify;
1129 	}
1130 
1131 	// We're promoting a virtual conv. Normalize the notifications object, and
1132 	// process a preliminary notif that will update the virtual conv's ID to its
1133 	// new value.
1134 
1135 	// First, remove the virt conv from the list of deleted IDs
1136 	if (newDeletedIds.length) {
1137 		notify.deleted.id = newDeletedIds.join(",");
1138 	} else {
1139 		delete notify.deleted;
1140 	}
1141 
1142 	// get rid of creates for virtual convs, since they aren't really creates
1143 	var tmp = [];
1144 	var list = notify.created.c;
1145 	if (list && list.length) {
1146 		for (var i = 0; i < list.length; i++) {
1147 			var create = list[i];
1148 			var c = createdConvs[create.id];
1149 			if (!(c && c._wasVirtConv)) {
1150 				tmp.push(create);
1151 			}
1152 		}
1153 	}
1154 	if (tmp && tmp.length) {
1155 		notify.created.c = tmp;
1156 	} else {
1157 		delete notify.created.c;
1158 	}
1159 
1160 	// if the second msg matched the current search, we'll want to use the conv
1161 	// create node to create the conv later, so save it
1162 	for (var id in createdMsgs) {
1163 		var msgCreate = createdMsgs[id];
1164 		var convCreate = createdConvs[msgCreate.cid];
1165 		if (convCreate && convCreate._wasVirtConv) {
1166 			msgCreate._convCreateNode = convCreate;
1167 		}
1168 	}
1169 
1170 	// create modified notifs for the virtual convs that have been promoted, using
1171 	// the create notif for the conv as a base
1172 	var newMods = [];
1173 	for (var cid in newToOldCid) {
1174 		var node = createdConvs[cid];
1175 		node.id = newToOldCid[cid];
1176 		node._newId = cid;
1177 		newMods.push(node);
1178 	}
1179 
1180 	// Go ahead and process these changes, which will change the ID of each promoted conv
1181 	// from its virtual (negative) ID to its real (positive) one. That will replace the DOM
1182 	// IDs of that conv's elements with ones that reflect the new conv ID.
1183 	if (newMods.length) {
1184 		var mods = {};
1185 		mods["c"] = newMods;
1186 		appCtxt.getRequestMgr()._handleModifies(mods);
1187 	}
1188 };
1189 
1190 /**
1191  * For mail creates, there is no authoritative list (mail lists are always the result
1192  * of a search), so we notify each ZmMailList that we know about. To make life easier,
1193  * we figure out which folder(s) a conv spans before we hand it off.
1194  * <p>
1195  * Since the offline client may receive hundreds of create notifications at a time, we
1196  * make sure a create notification is relevant before creating a mail item.</p>
1197  *
1198  * @param creates	[hash]		hash of create notifications
1199  * 
1200  * @private
1201  */
1202 ZmMailApp.prototype.createNotify =
1203 function(creates, force) {
1204 	if (!creates["m"] && !creates["c"] && !creates["link"]) { return; }
1205 	if (!force && !this._noDefer && this._deferNotifications("create", creates)) {
1206 		AjxDebug.println(AjxDebug.NOTIFY, "ZmMailApp: skipping/deferring notifications"); 
1207 		return;
1208 	}
1209 
1210 	if (creates["link"]) {
1211 		var list = creates["link"];
1212 		for (var i = 0; i < list.length; i++) {
1213 			var create = list[i];
1214 			if (appCtxt.cacheGet(create.id)) { continue; }
1215 			this._handleCreateLink(create, ZmOrganizer.FOLDER);
1216 		}
1217 	}
1218 
1219 	var controllers = this.getAllControllers();
1220 
1221 	// Move currentController to the end of the list if it's not there already
1222 	var currentController = this._getCurrentViewController();
1223 	if (currentController && controllers[controllers.length - 1] !== currentController) {
1224 		AjxUtil.arrayRemove(controllers, currentController);
1225 		controllers.push(currentController);
1226 	}
1227 
1228 	// give each controller a chance to handle the creates
1229 	for (var i = 0; i < controllers.length; i++) {
1230 		var controller = controllers[i];
1231 		if (controller && controller.isZmDoublePaneController) {
1232 			this._checkList(creates, controller.getList(), controller, i == controllers.length - 1);
1233 		}
1234 	}
1235 
1236 	this._handleAlerts(creates);
1237 };
1238 
1239 ZmMailApp.prototype._handleAlerts =
1240 function(creates) {
1241 	var mailCreates = creates["m"] || [];
1242 	if (mailCreates.length == 0) { return; }
1243 
1244 	AjxDispatcher.require("Alert");
1245 
1246 	var activeAcct = appCtxt.getActiveAccount();
1247 	var didAppAlert, didSoundAlert, didBrowserAlert = false;
1248 
1249 	var toasterCount = 0;
1250 
1251 	for (var i = 0; i < mailCreates.length; i++) {
1252 		var mc = mailCreates[i];
1253 		var parsed = (mc && mc.f && (mc.f.indexOf(ZmItem.FLAG_UNREAD) != -1))
1254 			? ZmOrganizer.parseId(mc.l) : null;
1255 
1256 		// don't process alerts while account is undergoing initial sync
1257 		var acct = parsed && parsed.account;
1258 		if (!acct || (acct && acct.isOfflineInitialSync())) { continue; }
1259 
1260 		// offline: check whether to show new-mail notification icon
1261 		// Skip spam/trash folders and the local account
1262 		if (appCtxt.isOffline && parsed && !acct.isMain) {
1263 			var doIt = (appCtxt.get(ZmSetting.OFFLINE_NOTIFY_NEWMAIL_ON_INBOX))
1264 				? (parsed.id == ZmOrganizer.ID_INBOX)
1265 				: (parsed.id != ZmOrganizer.ID_SPAM && parsed.id != ZmOrganizer.ID_TRASH);
1266 
1267 			if (doIt) {
1268 				this.globalMailCount++;
1269 				acct.inNewMailMode = true;
1270 				var allContainers = appCtxt.getOverviewController()._overviewContainer;
1271 				for (var j in allContainers) {
1272 					allContainers[j].updateAccountInfo(acct, true, true);
1273 				}
1274 			}
1275 		}
1276 
1277 		if (appCtxt.get(ZmSetting.MAIL_NOTIFY_ALL) || (parsed && parsed.id == ZmOrganizer.ID_INBOX)) {
1278 			// for multi-account, highlite the non-active accordion item
1279 			if (appCtxt.accountList.size() > 1) {
1280 				ZmAccountAlert.get(acct).start(this);
1281 			}
1282 
1283 			// alert mail app tab for the active account and set flag so we only do it *once*
1284 			if (!didAppAlert && acct == activeAcct &&
1285 				appCtxt.get(ZmSetting.MAIL_NOTIFY_APP, null, acct))
1286 			{
1287 				this.startAlert();
1288 				didAppAlert = true;
1289 			}
1290 
1291 			// do audible alert for this account and set flag so we only do it *once*
1292 			if (!didSoundAlert && appCtxt.get(ZmSetting.MAIL_NOTIFY_SOUNDS, null, acct)) {
1293 				ZmSoundAlert.getInstance().start();
1294 				didSoundAlert = true;
1295 			}
1296 
1297 			// do browser alert for this account and set flag so we only do it *once*
1298 			if (!didBrowserAlert && appCtxt.get(ZmSetting.MAIL_NOTIFY_BROWSER, null, acct)) {
1299 				ZmBrowserAlert.getInstance().start(ZmMsg.newMessage);
1300 				didBrowserAlert = true;
1301 			}
1302 
1303 			// generate toaster message if applicable
1304 			if (appCtxt.get(ZmSetting.MAIL_NOTIFY_TOASTER, null, acct) &&
1305 				toasterCount < 5)
1306 			{
1307 				var msg = appCtxt.getById(mc.id) || ZmMailMsg.createFromDom(mc, {});
1308 				var text = (msg.subject)
1309 					? ([msg.subject, " - ", (msg.fragment || "")].join(""))
1310 					: (msg.fragment || "");
1311 
1312 				var from = msg.getAddress(AjxEmailAddress.FROM);
1313 				var email = (from && from instanceof AjxEmailAddress) ? from.getName() || from.getAddress() :
1314 							(from && typeof from == "string") ? from : ZmMsg.unknown;
1315 				var title = (appCtxt.accountList.size() > 1)
1316 					? AjxMessageFormat.format(ZmMsg.newMailWithAccount, [email, acct.getDisplayName()])
1317 					: AjxMessageFormat.format(ZmMsg.newMail, email);
1318 				ZmDesktopAlert.getInstance().start(title, text);
1319 				toasterCount++;
1320 			}
1321 		}
1322 	}
1323 };
1324 
1325 /**
1326  * We can only handle new mail notifications if:
1327  *  	- we are currently in a mail view
1328  *		- the view is the result of a matchable search
1329  *
1330  * @param {Hash}					creates		the JSON create objects
1331  * @param {ZmMailList}				list		the mail list to notify
1332  * @param {ZmMailListController}	controller	the controller that owns list
1333  * @param {boolean}					last		if true, okay to mark creates as handled
1334  * 
1335  * @private
1336  */
1337 ZmMailApp.prototype._checkList =
1338 function(creates, list, controller, last) {
1339 
1340 	AjxDebug.println(AjxDebug.NOTIFY, "ZmMailApp: handling mail creates for view " + controller.getCurrentViewId());
1341 
1342 	if (!(list && list instanceof ZmMailList)) {
1343 		AjxDebug.println(AjxDebug.NOTIFY, "ZmMailApp: list is not a ZmMailList: " + list);
1344 		return;
1345 	}
1346 
1347 	var convs = {};
1348 	var msgs = {};
1349 
1350 	var sortBy = list.search.sortBy;
1351 
1352 	var convResults = this._checkType(creates, ZmItem.CONV, convs, list, sortBy, null, last);
1353 	var msgResults  = this._checkType(creates, ZmItem.MSG, msgs, list, sortBy, convs, last);
1354 
1355 	if (convResults.gotMail || msgResults.gotMail) {
1356 		list.notifyCreate(convs, msgs);
1357 	}
1358 
1359 	// bug: 30546
1360 	if (convResults.hasMore || msgResults.hasMore) {
1361 		var controller = this._getCurrentViewController();
1362 		
1363 		if (controller) {
1364 			controller.setHasMore(true);
1365 		}
1366 	}
1367 };
1368 
1369 ZmMailApp.prototype._getCurrentViewController =
1370 function() {
1371 	var controller;
1372 	var viewType = appCtxt.getCurrentViewType();
1373 	if (viewType == ZmId.VIEW_CONVLIST) {
1374 		controller = this.getConvListController();
1375 	} else if (viewType == ZmId.VIEW_TRAD) {
1376 		controller = this.getTradController();
1377 	}
1378 	return controller;
1379 };
1380 
1381 /**
1382  * Handles the creates for the given type of mail item.
1383  *
1384  * @param {Array}		creates		a list of JSON create nodes
1385  * @param {constant}	type		the mail item type
1386  * @param {Hash}		items		a hash of created mail items
1387  * @param {ZmMailList}	currList	the list currently being displayed to user
1388  * @param {constant}	sortBy		the sort order
1389  * @param {Hash}		convs		the convs, so we can update folders from msgs
1390  * @param {boolean}		last		if true, okay to mark creates as handled
1391  *
1392  * @return	{Hash}	a hash with booleans gotItem and gotAlertMessage
1393  * 
1394  * @private
1395  */
1396 ZmMailApp.prototype._checkType =
1397 function(creates, type, items, currList, sortBy, convs, last) {
1398 
1399 	var result = { gotMail:false, hasMore:false };
1400 	var nodeName = ZmList.NODE[type];
1401 	var list = creates[nodeName];
1402 	if (!(list && list.length)) { return result; }
1403 
1404 	var throttle;
1405 	if (appCtxt.isOffline) {
1406 		throttle = (appCtxt.get(ZmSetting.OFFLINE_SHOW_ALL_MAILBOXES))
1407 			? appCtxt.accountList.isInitialSyncing()
1408 			: appCtxt.getActiveAccount().isOfflineInitialSync();
1409 	}
1410 	if (throttle) {
1411 		if (!this._maxEntries) {
1412 			var mlv = this.getMailListController().getCurrentView().getMailListView();
1413 			this._maxEntries = mlv && mlv.calculateMaxEntries();
1414 		}
1415 		if (this.numEntries > this._maxEntries) {
1416 			AjxDebug.println(AjxDebug.NOTIFY, "ZmMailApp: too many creates: num=" + this.numEntries + ", max=" + this._maxEntries);
1417 			return result;
1418 		}
1419 	}
1420 
1421 	var INTERVAL_LENGTH = 10 * 1000; //10 seconds
1422 	var INTERVAL_THRESHOLD = 20; //throttle more than 20 messages.
1423 	for (var i = 0; i < list.length; i++) {
1424 		var create = list[i];
1425 
1426 		// generic throttling mechanism. Do it per folder. reset every 10 seconds. If more than 40 creates arrive in this interval, stop handling them. 
1427 		// This is used to throttle external accounts syncs but also good in general to prevent the client from hanging in case of a huge burst of updates.
1428 		var folder = create.l || "conv"; //I bundle all conv creates together (since they don't provide folder) to make it simple. There are NOT a lot of conv creates at this stage. we mostly create them in ZmMailList.prototype.notifyCreate from messages.
1429 		var now = new Date();
1430 		var data = this._throttleStats[folder];
1431 		if (!data || now.getTime() - data.intervalStart.getTime() > INTERVAL_LENGTH) {
1432 			data = this._throttleStats[folder] = {
1433 				intervalStart: now,
1434 				count: 0
1435 			}
1436 		}
1437 		data.count++;
1438 		if (data.count > INTERVAL_THRESHOLD) {
1439 			if (data.count == INTERVAL_THRESHOLD + 1) {
1440 				DBG.println(AjxDebug.DBG1, "folder " + folder + " starting to throttle at  " + now);
1441 			}
1442 			result.hasMore = true;
1443 			continue;
1444 		}
1445 
1446 
1447 		AjxDebug.println(AjxDebug.NOTIFY, "ZmMailApp: process create notification:");
1448 		var extra = (type == ZmItem.MSG) ? "|cid=" + create.cid + "|l=" + create.l : "|n=" + create.n;
1449 		AjxDebug.println(AjxDebug.NOTIFY, type + ": id=" + create.id + "|su='" + create.su + "'|f=" + create.f + "|d=" + create.d + extra);
1450 		if (create._handled) {
1451 			AjxDebug.println(AjxDebug.NOTIFY, "ZmMailApp: create already handled " + create.id);
1452 			continue;
1453 		}
1454 		if (last) {
1455 			create._handled = true;
1456 		}
1457 
1458 		// new conv does not affect a list of msgs
1459 		if (currList.type == ZmItem.MSG && type == ZmItem.CONV) {
1460 			AjxDebug.println(AjxDebug.NOTIFY, "ZmMailApp: msg list ignoring conv create");
1461 			continue;
1462 		}
1463 
1464 		// perform stricter checking if we're in offline mode
1465 		if (appCtxt.isOffline) {
1466 			if ((ZmList.ITEM_TYPE[nodeName] != currList.type) && (currList.type != ZmItem.CONV)) {
1467 				AjxDebug.println(AjxDebug.NOTIFY, "ZmMailApp: type mismatch: " + ZmList.ITEM_TYPE[nodeName] + " / " + currList.type);
1468 				continue;
1469 			}
1470 		}
1471 
1472 		// throttle influx of CREATE notifications during offline initial sync
1473 		if (throttle && this.numEntries > this._maxEntries) {
1474 			AjxDebug.println(AjxDebug.NOTIFY, "ZmMailApp: throttling");
1475 			result.hasMore = true;
1476 			break;
1477 		}
1478 
1479 		DBG.println(AjxDebug.DBG1, "ZmMailApp: handling CREATE for node: " + nodeName);
1480 
1481 		var item = appCtxt.getById(create.id);
1482 		if (!item) {
1483 			AjxDebug.println(AjxDebug.NOTIFY, "ZmMailApp: create " + type + " object " + create.id);
1484 			var itemClass = eval(ZmList.ITEM_CLASS[type]);
1485 			item = itemClass.createFromDom(create, {list: currList});
1486 		}
1487 		else if (item.type == ZmItem.MSG) {
1488 			// bug 47589: make sure conv knows its folders
1489 			var conv = appCtxt.getById(item.cid);
1490 			if (conv) {
1491 				conv.folders[item.folderId] = true;
1492 			}
1493 		}
1494 		items[item.id] = item;
1495 		result.gotMail = true;
1496 	}
1497 	return result;
1498 };
1499 
1500 ZmMailApp.prototype.modifyNotify =
1501 function(modifies, force) {
1502 	if (!modifies["m"] && !modifies["c"]) { return; }
1503 	if (!force && !this._noDefer && this._deferNotifications("modify", modifies)) { return; }
1504 
1505 	this._batchNotify(modifies["m"]);
1506 	this._batchNotify(modifies["c"]);
1507 };
1508 
1509 ZmMailApp.prototype.postNotify =
1510 function(notify) {
1511 	var lv = this._checkReplenishListView;
1512 	if (lv && !lv._isPageless) {
1513 		lv._checkReplenish();
1514 		this._checkReplenishListView = null;
1515 	}
1516 };
1517 
1518 ZmMailApp.prototype.refresh =
1519 function(refresh) {
1520 
1521 	var inbox = appCtxt.getById(ZmFolder.ID_INBOX);
1522 	if (inbox) {
1523 		this.setNewMailNotice(inbox);
1524 	}
1525 
1526 	if (!appCtxt.inStartup) {
1527 		this.resetOverview(this.getOverviewId());
1528  
1529 		// mark all existing mail list views as stale
1530 		var viewIds = [ZmId.VIEW_TRAD, ZmId.VIEW_CONVLIST, ZmId.VIEW_CONV];
1531 		var avm = appCtxt.getAppViewMgr();
1532 		for (var i = 0; i < viewIds.length; i++) {
1533 			var views = avm.getViewsByType(viewIds[i]);
1534 			for (var j = 0; j < views.length; j++) {
1535 				var dpv = avm.getViewComponent(ZmAppViewMgr.C_APP_CONTENT, views[j].id);
1536 				if (dpv && dpv.isZmDoublePaneView) {
1537 					dpv.isStale = true;
1538 				}
1539 			}
1540 		}
1541 		// view is normally updated when user returns to it (from whatever view
1542 		// results from the current request); if the request doesn't result in a
1543 		// view change, use a timer to check if it still needs to be updated
1544 		var curViewId = appCtxt.getCurrentViewId();
1545 		AjxTimedAction.scheduleAction(new AjxTimedAction(this, this._checkRefresh, [curViewId]), 1000);
1546 	}
1547 };
1548 
1549 ZmMailApp.prototype._checkRefresh =
1550 function(lastViewId) {
1551 
1552 	// if the request that prompted the refresh didn't result in a view change
1553 	// (eg NoOpRequest), rerun its underlying search
1554 	if (appCtxt.getCurrentViewId() == lastViewId) {
1555 		var curView = appCtxt.getCurrentView();
1556 		if (curView && curView.isStale && curView._staleHandler) {
1557 			curView._staleHandler();
1558 		}
1559 	}
1560 };
1561 
1562 ZmMailApp.prototype.handleOp =
1563 function(op, params) {
1564 	var inNewWindow = false;
1565 	var showLoadingPage = true;
1566 	if ((op == ZmOperation.NEW_MESSAGE_WIN) || (op == ZmOperation.NEW_MESSAGE)) {
1567 		if (!appCtxt.isWebClientOffline()) {
1568 			inNewWindow = (op == ZmOperation.NEW_MESSAGE_WIN) ? true : this._inNewWindow(params && params.ev);
1569 			showLoadingPage = false;	// don't show "Loading ..." page since main window view doesn't change
1570 		}
1571 		var loadCallback = new AjxCallback(this, this.compose, {action: ZmOperation.NEW_MESSAGE, inNewWindow:inNewWindow});
1572 		AjxDispatcher.require(["ContactsCore", "Contacts"], false, loadCallback, null, showLoadingPage);
1573 	}
1574 };
1575 
1576 // Public methods
1577 
1578 ZmMailApp.prototype.getOverviewPanelContent =
1579 function() {
1580 	var firstTime = !this._overviewPanelContent;
1581 
1582 	var overview = ZmApp.prototype.getOverviewPanelContent.apply(this, arguments);
1583 
1584 	// bug: 42455 - highlight folder now that overview exists
1585 	if (firstTime) {
1586 		appCtxt.getSearchController().updateOverview();
1587 	}
1588 
1589 	return overview;
1590 };
1591 
1592 ZmMailApp.prototype.getOverviewContainer =
1593 function() {
1594 	var firstTime = !this._overviewContainer;
1595 
1596 	var container = ZmApp.prototype.getOverviewContainer.apply(this, arguments);
1597 
1598 	// bug: 42455 - highlight folder now that overview exists
1599 	if (firstTime && !appCtxt.get(ZmSetting.OFFLINE_SHOW_ALL_MAILBOXES)) {
1600 		appCtxt.getSearchController().updateOverview();
1601 	}
1602 
1603 	return container;
1604 };
1605 
1606 ZmMailApp.prototype.getNewButtonProps =
1607 function() {
1608 	return {
1609 		text:		ZmMsg.newMessage,
1610 		tooltip:	ZmMsg.compose,
1611 		icon:		"NewMessage",
1612 		iconDis:	"NewMessageDis",
1613 		defaultId:	ZmOperation.NEW_MESSAGE,
1614         disabled:   !this.containsWritableFolder()
1615 	};
1616 };
1617 
1618 ZmMailApp.prototype.launch =
1619 function(params, callback) {
1620 	this._setLaunchTime(this.toString(), new Date());
1621 
1622     if (appCtxt.isExternalAccount()) {
1623         var loadCallback = this._handleLoadLaunch.bind(this, params, callback);
1624 	    AjxDispatcher.require(["MailCore", "Mail", "Startup2"], true, loadCallback, null, true);
1625     }
1626     else {
1627         this._handleLoadLaunch(params, callback);
1628     }
1629 };
1630 
1631 ZmMailApp.prototype._handleLoadLaunch =
1632 function(params, callback) {
1633 	// set type for initial search
1634 	this._groupBy = appCtxt.get(ZmSetting.GROUP_MAIL_BY);
1635 
1636 	var query;
1637 	params = params || {};
1638 
1639 	if (params.qsParams) {
1640 		var view = params.qsParams.view, id = params.qsParams.id;
1641 		if (view == "compose") {
1642 			this._showComposeView(callback);
1643 			return;
1644 		} else if (id) {
1645 			view = view || "msg";
1646 			if (view == "list") {
1647 				query = ["item:", id].join("");
1648 				params.searchResponse = null;
1649 				this._forceMsgView = true;
1650 			} else if (view == "msg") {
1651 
1652 				var list = new ZmMailList(ZmItem.MSG);
1653 				var msg = new ZmMailMsg(id, list, true);
1654 				list.add(msg);
1655 
1656 				var msgParams = {getHtml:			appCtxt.get(ZmSetting.VIEW_AS_HTML),
1657 								 markRead:			(appCtxt.get(ZmSetting.MARK_MSG_READ) == ZmSetting.MARK_READ_NOW),
1658 								 callback:			new AjxCallback(this, this._handleResponseMsgLoad, [msg, callback]),
1659 								 errorCallback:		new AjxCallback(this, this._handleErrorMsgLoad, callback)};
1660 				msg.load(msgParams);
1661 				return;
1662 			}
1663 		}
1664 	}
1665 
1666 	this.mailSearch(query, callback, params.searchResponse);
1667 };
1668 
1669 ZmMailApp.prototype._handleErrorLaunch =
1670 function(params, ex) {
1671 	if (ex.code == ZmCsfeException.MAIL_NO_SUCH_FOLDER ||
1672 		ex.code == ZmCsfeException.MAIL_NO_SUCH_TAG ||
1673 		ex.code == ZmCsfeException.MAIL_QUERY_PARSE_ERROR)
1674 	{
1675 		// reset the params so we default to searching the inbox which *will* work
1676 		var newParams = {query:"in:inbox", callback:params.callback, errorCallback:null, types:params.types};
1677 		appCtxt.getSearchController().search(newParams);
1678 	}
1679 };
1680 
1681 /**
1682  * If we can't show the given msg, just do regular mail launch and show initial search. Make sure to
1683  * run the callback so that the rest of the UI is drawn.
1684  *
1685  * @param callback
1686  * @param ex
1687  * 
1688  * @private
1689  */
1690 ZmMailApp.prototype._handleErrorMsgLoad =
1691 function(callback, ex) {
1692 	this.mailSearch();
1693 	if (callback) {
1694 		callback.run();
1695 	}
1696 	this._notifyRendered();
1697 	return false;
1698 };
1699 
1700 ZmMailApp.prototype._handleResponseMsgLoad =
1701 function(msg, callback) {
1702 	AjxDispatcher.require("Startup2");
1703 	var msgCtlr = AjxDispatcher.run("GetMsgController");
1704 	if (msgCtlr) {
1705 		msgCtlr.show(msg, null, null, null, true); // Show the message without pagination buttons
1706 		if (callback) {
1707 			callback.run();
1708 		}
1709 		this._notifyRendered();
1710 
1711 		appCtxt.notifyZimlets('onMsgView', [msg, null, appCtxt.getCurrentView()], {waitUntilLoaded:true});
1712 	}
1713 };
1714 
1715 /**
1716  * Performs a mail search.
1717  * 
1718  * @param	{String}	query		the query
1719  * @param	{AjxCallback}	callback		the callback
1720  * @param	{Object}	response	the response
1721  * @param	{constant}	type		the type
1722  */
1723 ZmMailApp.prototype.mailSearch =
1724 function(query, callback, response, type) {
1725 	var account = appCtxt.isOffline && appCtxt.inStartup && appCtxt.accountList.defaultAccount;
1726 	if (account) {
1727 		appCtxt.accountList.setActiveAccount(account);
1728 	}
1729 
1730 	var sc = appCtxt.getSearchController();
1731 	var queryHint, noUpdateOverview;
1732 	if (appCtxt.get(ZmSetting.OFFLINE_SHOW_ALL_MAILBOXES) &&
1733 		appCtxt.accountList.size() > 2)
1734 	{
1735 		query = null;
1736 		queryHint = appCtxt.accountList.generateQuery(ZmOrganizer.ID_INBOX);
1737 		noUpdateOverview = true;
1738 		sc.searchAllAccounts = true;
1739 	}
1740 	else if(appCtxt.isExternalAccount()) {
1741         query = "inid:" + this.getDefaultFolderId();
1742     } else if (appCtxt.isWebClientOffline()) {
1743         query = query || "in:inbox";
1744     } else {
1745 		query = query || appCtxt.get(ZmSetting.INITIAL_SEARCH, null, account);
1746 	}
1747 
1748 	var types = new AjxVector();
1749 	types.add(type || this.getGroupMailBy());
1750 	var sortBy = AjxUtil.get(response, "Body", "SearchResponse", "sortBy") || ZmSearch.DATE_DESC;
1751 
1752 	var params = {
1753 		searchFor:			ZmId.SEARCH_MAIL,
1754 		query:				query,
1755 		queryHint:			queryHint,
1756 		types:				types,
1757 		limit:				this.getLimit(),
1758 		getHtml:			appCtxt.get(ZmSetting.VIEW_AS_HTML, null, account),
1759 		noUpdateOverview:	noUpdateOverview,
1760         offlineCache:       true,
1761 		accountName:		(account && account.name),
1762 		callback:			callback,
1763 		response:			response,
1764 		sortBy:             sortBy
1765 	};
1766 	params.errorCallback = new AjxCallback(this, this._handleErrorLaunch, params);
1767 	sc.search(params);
1768 };
1769 
1770 /**
1771  * Shows the search results.
1772  * 
1773  * @param	{Object}					results						the results
1774  * @param	{AjxCallback}				callback					the callback
1775  * @param 	{ZmSearchResultsController}	searchResultsController		owning controller
1776  */
1777 ZmMailApp.prototype.showSearchResults =
1778 function(results, callback, searchResultsController) {
1779 	var loadCallback = this._handleLoadShowSearchResults.bind(this, results, callback, searchResultsController);
1780 	AjxDispatcher.require("MailCore", false, loadCallback, null, true);
1781 };
1782 
1783 ZmMailApp.prototype._handleLoadShowSearchResults =
1784 function(results, callback, searchResultsController) {
1785 
1786 	var sessionId = searchResultsController ? searchResultsController.getCurrentViewId() : ZmApp.MAIN_SESSION;
1787 	var controller = ((results.type == ZmItem.MSG) || !appCtxt.get(ZmSetting.CONVERSATIONS_ENABLED)) ? this.getTradController(sessionId, searchResultsController) :
1788 													this.getConvListController(sessionId, searchResultsController);
1789 	controller.show(results);
1790 	this._setLoadedTime(this.toString(), new Date());
1791 	
1792 	if (this._forceMsgView) {
1793 		controller.selectFirstItem();
1794 		this._forceMsgView = false;
1795 	}
1796 
1797 	if (callback) {
1798 		callback.run(controller);
1799 	}
1800 	this._notifyRendered();
1801 
1802 	// update the title to reflect the new search results
1803 	appCtxt.getAppViewMgr().updateTitle();
1804 };
1805 
1806 ZmMailApp.prototype._parseComposeUrl =
1807 function(urlQueryStr){
1808 
1809 	urlQueryStr = urlQueryStr || '';
1810 	urlQueryStr.replace(/^mailto:/i, "");
1811 
1812 	//Decode the whole query string. Components will be decoded as well, but that's okay since it should do no harm and the query string may have been double-encoded as well (once by user to trick crawlers, then again by the browser in constructing the mailto URL).
1813 	urlQueryStr = AjxStringUtil.urlComponentDecode(urlQueryStr);
1814 
1815 	var match = urlQueryStr.match(/\bto=([^&]+)/i);
1816 	var to = match ? AjxStringUtil.urlComponentDecode(match[1].replace(/\+/g, " ")) : null;
1817 	to = to && AjxEmailAddress.isValid(to) ? AjxStringUtil.urlComponentDecode(to) : AjxStringUtil.htmlEncode(to);
1818 	
1819 	match = urlQueryStr.match(/\bsubject=([^&]+)/i);
1820 	var subject = match ? (AjxStringUtil.urlComponentDecode(match[1]).replace(/\+/g, " ")) : null;
1821 
1822 	match = urlQueryStr.match(/\bcc=([^&]+)/i);
1823 	var cc = match ? AjxStringUtil.urlComponentDecode(match[1].replace(/\+/g, " ")) : null;
1824 	cc = cc && AjxEmailAddress.isValid(cc) ? cc : AjxStringUtil.htmlEncode(cc);
1825 	
1826 	match = urlQueryStr.match(/\bbcc=([^&]+)/i);
1827 	var bcc = match ? AjxStringUtil.urlComponentDecode(match[1].replace(/\+/g, " ")) : null;
1828 	bcc = bcc && AjxEmailAddress.isValid(bcc) ? bcc : AjxStringUtil.htmlEncode(bcc);
1829 	
1830 	match = urlQueryStr.match(/\bbody=([^&]+)/i);
1831 	var body = match ? (AjxStringUtil.urlComponentDecode(match[1]).replace(/\+/g, " ")) : null;
1832 
1833 	return {
1834 		to: to,
1835 		subject: AjxStringUtil.htmlEncode(subject),
1836 		cc: cc,
1837 		bcc: bcc,
1838 		body: AjxStringUtil.htmlEncode(body)
1839 	};
1840 };
1841 
1842 ZmMailApp.prototype._showComposeView =
1843 function(callback, queryStr) {
1844 	var qs = queryStr || location.search;
1845 
1846 	AjxDispatcher.require("Startup2");
1847 	var composeController = AjxDispatcher.run("GetComposeController");
1848 
1849 	// RFC 2368 = mailto:user@zimbra.com?(headers=values)*
1850 	var composeParams = this._parseComposeUrl(qs);
1851 	var to = composeParams.to;
1852 	if (to && to.indexOf('mailto') == 0) {
1853 		to = to.replace(/mailto:/,'');
1854 		var mailtoQuery = to.split('?');
1855 		composeParams.to = mailtoQuery[0];
1856 		if (mailtoQuery.length > 1) {
1857 			//mailto:xyz@abc.com?....
1858 			mailtoQuery = mailtoQuery[1];
1859 			var mailtoParams = this._parseComposeUrl(mailtoQuery);
1860 			// mailto:user@abc.com?to=xyz@abc.com&... or mailto:?to=xyz@abc.com
1861 			composeParams.to = composeParams.to
1862 					? (mailtoParams.to ? [composeParams.to, ','+mailtoParams.to].join('') : composeParams.to )
1863 					:  mailtoParams.to;
1864 			composeParams.subject = mailtoParams.subject || composeParams.subject;
1865 			composeParams.cc = mailtoParams.cc || composeParams.cc;
1866 			composeParams.bcc = mailtoParams.bcc || composeParams.bcc;
1867 			composeParams.body = mailtoParams.body || composeParams.body;
1868 		}
1869 	}
1870 
1871 	var params = {
1872 		action: ZmOperation.NEW_MESSAGE,
1873 		toOverride: composeParams.to,
1874 		ccOverride: composeParams.cc,
1875 		bccOverride: composeParams.bcc,
1876 		subjOverride: composeParams.subject,
1877 		extraBodyText: composeParams.body,
1878 		extraBodyTextIsExternal: Boolean(composeParams.body),
1879 		callback: callback
1880 	};
1881 
1882 	// this can happen in offlie where user clicks on mailto link and we're
1883 	// already in compose view
1884 	if (appCtxt.isOffline &&
1885 		appCtxt.get(ZmSetting.OFFLINE_SUPPORTS_MAILTO) &&
1886 		appCtxt.getCurrentViewId() == ZmId.VIEW_COMPOSE)
1887 	{
1888 		composeController.resetComposeForMailto(params);
1889 	}
1890 	else {
1891 		composeController.doAction(params);
1892 	}
1893 
1894 	this._notifyRendered();
1895     return composeController;
1896 };
1897 
1898 /**
1899  * Returns a conversation list controller.
1900  * 
1901  * @return	{ZmConvListController}	conversation list controller
1902  */
1903 ZmMailApp.prototype.getConvListController =
1904 function(sessionId, searchResultsController) {
1905 	return this.getSessionController({controllerClass:			"ZmConvListController",
1906 									  sessionId:				sessionId || ZmApp.MAIN_SESSION,
1907 									  searchResultsController:	searchResultsController});
1908 };
1909 
1910 /**
1911  * Returns a conversation controller.
1912  * 
1913  * @return	{ZmConvController}		conversation controller
1914  */
1915 ZmMailApp.prototype.getConvController =
1916 function(sessionId) {
1917 	return this.getSessionController({controllerClass:	"ZmConvController",
1918 									  sessionId:		sessionId});
1919 };
1920 
1921 /**
1922  * Gets the traditional (msg list) controller.
1923  * 
1924  * @return	{ZmTradController}	traditional controller
1925  */
1926 ZmMailApp.prototype.getTradController =
1927 function(sessionId, searchResultsController) {
1928 	return this.getSessionController({controllerClass:			"ZmTradController",
1929 									  sessionId:				sessionId || ZmApp.MAIN_SESSION,
1930 									  searchResultsController:	searchResultsController});
1931 };
1932 
1933 /**
1934  * Gets the message controller.
1935  * 
1936  * @return	{ZmMsgController}		message controller
1937  */
1938 ZmMailApp.prototype.getMsgController =
1939 function(sessionId) {
1940 
1941     // if message is already open get that session controller
1942     var controllers = this._sessionController[ZmId.VIEW_MSG];
1943     var controller;
1944     for (var id in controllers) {
1945         if (!controllers[id].isHidden && controllers[id].getMsg() && controllers[id].getMsg().nId == sessionId) {
1946             controller = controllers[id];
1947             break;
1948         }
1949     }
1950 
1951     if (controller) {
1952         sessionId = controller.getSessionId();
1953         this._curSessionId[ZmId.VIEW_MSG] = sessionId;
1954         controller.inactive = false;
1955         return controller;
1956     }
1957         
1958 	return this.getSessionController({controllerClass:	"ZmMsgController",
1959 									  sessionId:		sessionId});
1960 };
1961 
1962 /**
1963  * Returns a compose controller.
1964  * 
1965  * @return	{ZmComposeController}	compose controller
1966  */
1967 ZmMailApp.prototype.getComposeController =
1968 function(sessionId) {
1969 	return this.getSessionController({controllerClass:	"ZmComposeController",
1970 									  sessionId:		sessionId});
1971 };
1972 
1973 ZmMailApp.prototype.getConfirmController =
1974 function(sessionId) {
1975 	return this.getSessionController({controllerClass:	"ZmMailConfirmController",
1976 									  sessionId:		sessionId});
1977 };
1978 
1979 /**
1980  * Gets the current mail list controller, which may be conversation list or msg list (traditional).
1981  * 
1982  * @return	{ZmTradController|ZmConvListController}	mail list controller
1983  */
1984 ZmMailApp.prototype.getMailListController =
1985 function() {
1986 	var groupMailBy = appCtxt.get(ZmSetting.GROUP_MAIL_BY) ;
1987 	return (groupMailBy == ZmSetting.GROUP_BY_CONV) ? AjxDispatcher.run("GetConvListController") :
1988 													  AjxDispatcher.run("GetTradController");
1989 };
1990 
1991 ZmMailApp.prototype.runRefresh =
1992 function() {
1993 	this.getMailListController().runRefresh();
1994 };
1995 
1996 /**
1997  * Begins a compose session by presenting a form to the user.
1998  *
1999  * @param	{Hash}	params			a hash of parameters
2000  * @param {constant}	params.action		the new message, reply, forward, or an invite action
2001  * @param {Boolean}	params.inNewWindow		if <code>true</code>, we are in detached window
2002  * @param {ZmMailMsg}	params.msg			the original message (reply/forward), or address (new message)
2003  * @param {String}	params.toOverride 	the initial value for To: field
2004  * @param {String}	params.subjOverride 	the initial value for Subject: field
2005  * @param {String}	params.extraBodyText the canned text to prepend to body (invites)
2006  * @param {AjxCallback}	params.callback		the callback to run after view has been set
2007  * @param {String}	params.accountName	the on-behalf-of From address
2008  */
2009 ZmMailApp.prototype.compose =
2010 function(params) {
2011 	Dwt.setLoadingTime("ZmMailApp-compose");
2012 	params = params || {};
2013 	if (!params.sessionId) {
2014 		// see if we already have a compose session for this message
2015 		var controllers = this._sessionController[ZmId.VIEW_COMPOSE];
2016 		var controller;
2017 		var msgId = params.msg && params.msg.nId;
2018 		for (var id in controllers) {
2019 			  if (controllers[id].getMsg() && controllers[id].getMsg().nId == msgId){
2020 				 controller = controllers[id];
2021 				 break;
2022 			  }
2023 		}
2024 	}
2025 	
2026     if (!controller) {
2027 	    controller = AjxDispatcher.run("GetComposeController", params.sessionId);
2028     }
2029 
2030     appCtxt.composeCtlrSessionId = controller.getSessionId();	// help new window dispose components
2031 	controller.doAction(params);
2032 	Dwt.setLoadedTime("ZmMailApp-compose");
2033 };
2034 
2035 /**
2036  * Sets the new mail notice.
2037  * 
2038  * @param	{ZmOrganizer}	organizer		the organizer
2039  */
2040 ZmMailApp.prototype.setNewMailNotice =
2041 function(organizer) {
2042 	var appChooser = appCtxt.getAppChooser();
2043 	if (appChooser) {
2044 		var mb = appChooser.getButton(ZmApp.MAIL);
2045 		var icon = (organizer.numUnread > 0) ? "EnvelopeOpen" : "MailApp";
2046 		mb.setImage(icon);
2047 	}
2048     if(organizer.id == ZmOrganizer.ID_INBOX) {
2049         this._setFavIcon(organizer.numUnread);
2050     }
2051 	this._setNewMailBadge();
2052 };
2053 
2054 ZmMailApp.prototype._setNewMailBadge =
2055 function() {
2056 	if (appCtxt.isOffline && appCtxt.get(ZmSetting.OFFLINE_SUPPORTS_DOCK_UPDATE)) {
2057 		if (AjxEnv.isMac && window.platform) {
2058 			window.platform.icon().badgeText = (this.globalMailCount > 0)
2059 				? this.globalMailCount : null;
2060 		}
2061 		else if (AjxEnv.isWindows) {
2062 			window.platform.icon().imageSpec = (this.globalMailCount > 0)
2063 				? "resource://webapp/icons/default/newmail.png"
2064 				: "resource://webapp/icons/default/launcher.ico";
2065 			window.platform.icon().title = (this.globalMailCount > 0)
2066 				? AjxMessageFormat.format(ZmMsg.unreadCount, this.globalMailCount) : null;
2067 		}
2068 	}
2069 };
2070 
2071 ZmMailApp.prototype.clearNewMailBadge =
2072 function() {
2073 	this.globalMailCount = 0;
2074 	this._setNewMailBadge();
2075 };
2076 
2077 ZmMailApp.prototype._setFavIcon =
2078 function(unread) {
2079     var url;
2080     if (unread == 0) {
2081         url = [appContextPath, "/img/logo/favicon.ico"].join("");
2082     } else if (unread > 9) {
2083         url = [appContextPath,"/img/logo/favicon_plus.ico"].join("");
2084     } else {
2085         url = [appContextPath, "/img/logo/favicon_", unread, ".ico"].join("");
2086     }
2087     Dwt.setFavIcon(url);
2088 };
2089 
2090 /**
2091  * Gets the "group mail by" setting. This is a convenience method to
2092  * convert "group mail by" between server (string) and client (int constant) versions.
2093  * 
2094  * @return	{String}	the group by mail setting
2095  */
2096 ZmMailApp.prototype.getGroupMailBy =
2097 function() {
2098 	var setting = this._groupBy || appCtxt.get(ZmSetting.GROUP_MAIL_BY);
2099 	return setting ? ZmMailApp.GROUP_MAIL_BY_ITEM[setting] : ZmItem.MSG;
2100 };
2101 
2102 ZmMailApp.prototype.setGroupMailBy =
2103 function(groupBy, skipNotify) {
2104 	this._groupBy = groupBy;
2105 	appCtxt.set(ZmSetting.GROUP_MAIL_BY, groupBy, null, false, skipNotify);
2106 };
2107 
2108 // return enough for us to get a scroll bar since we are pageless
2109 ZmMailApp.prototype.getLimit =
2110 function(offset) {
2111 	var limit = appCtxt.get(ZmSetting.PAGE_SIZE);
2112 	return offset ? limit : 2 * limit;
2113 };
2114 
2115 /**
2116  * Adds a "Reply" submenu for replying to sender or all.
2117  *
2118  * @param {ZmToolBar|ZmActionMenu}	parent		the parent widget (a toolbar or action menu)
2119  * @return	{ZmActionMenu}	the menu
2120  */
2121 ZmMailApp.addReplyMenu =
2122 function(parent) {
2123 	var list = [ZmOperation.REPLY, ZmOperation.REPLY_ALL];
2124 	var menu = new ZmActionMenu({parent:parent, menuItems:list});
2125 	parent.setMenu(menu);
2126 	return menu;
2127 };
2128 
2129 /**
2130  * Adds a "Forward" submenu for forwarding inline or as attachment.
2131  *
2132  * @param {ZmToolBar|ZmActionMenu}	parent		the parent widget (a toolbar or action menu)
2133  * @return	{ZmActionMenu}	the menu
2134  */
2135 ZmMailApp.addForwardMenu =
2136 function(parent) {
2137 	var list = [ZmOperation.FORWARD_INLINE, ZmOperation.FORWARD_ATT];
2138 	var menu = new ZmActionMenu({parent:parent, menuItems:list});
2139 	parent.setMenu(menu);
2140 	return menu;
2141 };
2142 
2143 /**
2144  * Adds a data source collection.
2145  * 
2146  * @param	{ZmAccount}		account		the account
2147  * @return	{ZmDataSourceCollection}	the data source collection
2148  */
2149 ZmMailApp.prototype.getDataSourceCollection =
2150 function(account) {
2151 	var appCtxt = window.parentAppCtxt || window.appCtxt;
2152 	var activeAcct = account ? account.name : appCtxt.getActiveAccount().name;
2153 
2154 	if (!this._dataSourceCollection[activeAcct]) {
2155 		this._dataSourceCollection[activeAcct] = new ZmDataSourceCollection();
2156 		if (appCtxt.getActiveAccount().isMain) {
2157 			this._dataSourceCollection[activeAcct].initialize(appCtxt.getSettings().getInfoResponse.dataSources);
2158 		}
2159 	}
2160 	return this._dataSourceCollection[activeAcct];
2161 };
2162 
2163 /**
2164  * Gets the identity collection.
2165  * 
2166  * @param	{ZmAccount}		account		the account
2167  * @return	{ZmIdentityCollection}	the identity collection
2168  */
2169 ZmMailApp.prototype.getIdentityCollection =
2170 function(account) {
2171 	// child window always gets its own identitiy collection
2172 	if (appCtxt.isChildWindow) {
2173 		if (!this._identityCollection) {
2174 			this._identityCollection = new ZmIdentityCollection();
2175 		}
2176 		return this._identityCollection;
2177 	}
2178 
2179 	var activeAcct = account ? account.name : appCtxt.getActiveAccount().name;
2180 
2181 	if (!this._identityCollection[activeAcct]) {
2182 		var ic = this._identityCollection[activeAcct] = new ZmIdentityCollection();
2183 		var settings = appCtxt.getSettings(account);
2184 		if (settings)
2185 			ic.initialize(settings.getInfoResponse.identities);
2186 	}
2187 	return this._identityCollection[activeAcct];
2188 };
2189 
2190 /**
2191  * Gets the signature collection.
2192  * 
2193  * @param	{ZmAccount}		account		the account
2194  * @return	{ZmSignatureCollection}	the signature collection
2195  */
2196 ZmMailApp.prototype.getSignatureCollection =
2197 function(account) {
2198 	var appCtxt = window.parentAppCtxt || window.appCtxt;
2199     account = account || appCtxt.getActiveAccount();
2200 	var activeAcct = account.name;
2201     var settings = appCtxt.getSettings(account);
2202 	if (!this._signatureCollection[activeAcct] && settings) {
2203 		var sc = this._signatureCollection[activeAcct] = new ZmSignatureCollection();
2204 		sc.initialize(settings.getInfoResponse.signatures);
2205 	}
2206 	return this._signatureCollection[activeAcct];
2207 };
2208 
2209 ZmMailApp.prototype._addSettingsChangeListeners =
2210 function() {
2211 	ZmApp.prototype._addSettingsChangeListeners.call(this);
2212 
2213 	if (!this._settingsListener) {
2214 		this._settingsListener = new AjxListener(this, this._settingsChangeListener);
2215 	}
2216 
2217 	var settings = appCtxt.getSettings();
2218 	settings.getSetting(ZmSetting.VIEW_AS_HTML).addChangeListener(this._settingListener);
2219 	settings.getSetting(ZmSetting.TRUSTED_ADDR_LIST).addChangeListener(this._settingListener);
2220 	settings.addChangeListener(this._settingsListener);
2221 };
2222 
2223 /**
2224  * Individual setting listener.
2225  */
2226 ZmMailApp.prototype._settingChangeListener =
2227 function(ev) {
2228 	ZmApp.prototype._settingChangeListener.call(this, ev);
2229 
2230 	if (ev.type != ZmEvent.S_SETTING) { return; }
2231 
2232 	var setting = ev.source;
2233 	var mlc = this.getMailListController();
2234 
2235 	if (mlc && (setting.id == ZmSetting.VIEW_AS_HTML || setting.id == ZmSetting.TRUSTED_ADDR_LIST)) {
2236         this.resetTrustedSendersList();
2237 		var dpv = mlc._doublePaneView;
2238 		var msg = dpv ? dpv.getMsg() : null;
2239 		if (msg) {
2240 			dpv.reset();
2241 			dpv.setItem(msg);
2242 		}
2243 	}
2244 };
2245 
2246 /**
2247  * Settings listener. Process changed settings as a group, so that we
2248  * don't redo the search more than once if more than one relevant mail
2249  * setting has changed.
2250  * 
2251  * @private
2252  */
2253 ZmMailApp.prototype._settingsChangeListener =
2254 function(ev) {
2255 	if (ev.type != ZmEvent.S_SETTINGS) { return; }
2256 
2257 	var list = ev.getDetail("settings");
2258 	if (!(list && list.length)) { return; }
2259 
2260 	var mlc = this.getMailListController();
2261 	if (!mlc) { return; }
2262 
2263 	var curView = mlc.getCurrentViewType();
2264 	var newView, groupByView;
2265 
2266 	for (var i = 0; i < list.length; i++) {
2267 		var setting = list[i];
2268 		if (setting.id == ZmSetting.SHOW_FRAGMENTS) {
2269 			if (curView != ZmId.VIEW_MSG) {
2270 				newView = groupByView || curView;
2271 			}
2272 		}
2273 	}
2274 	newView = groupByView || newView;
2275 
2276 	if (newView) {
2277 		mlc.switchView(newView, true);
2278 	}
2279 };
2280 
2281 ZmMailApp.prototype.getTrustedSendersList =
2282 function() {
2283     if(!this._trustedList) {
2284         var trustedList = appCtxt.get(ZmSetting.TRUSTED_ADDR_LIST);
2285         if(trustedList) {
2286             this._trustedList = AjxVector.fromArray(trustedList);
2287         }
2288         else {
2289             this._trustedList = new AjxVector();
2290         }
2291     }
2292     return this._trustedList;
2293 };
2294 
2295 ZmMailApp.prototype.resetTrustedSendersList =
2296 function() {
2297     this._trustedList = null;
2298 };
2299 
2300 ZmMailApp._handleOOORemindResponse = function(dialog,isTurnOff){
2301    ZmMailApp._hideOOORemindDialog(dialog);
2302    var dontRemind = document.getElementById(dialog._htmlElId + "_dontRemind");
2303 
2304    if(isTurnOff || dontRemind.checked){
2305         ZmMailApp._saveRemindStatus(isTurnOff,dontRemind.checked);
2306    }
2307 
2308 };
2309 
2310 ZmMailApp._hideOOORemindDialog=function(dialog){
2311     if(dialog){
2312         dialog.popdown();
2313     }
2314 };
2315 
2316 ZmMailApp._saveRemindStatus = function(turnOff,dontRemind) {
2317     var soapDoc = AjxSoapDoc.create("ModifyPrefsRequest", "urn:zimbraAccount");
2318 
2319     if(turnOff){
2320         var node = soapDoc.set("pref", "FALSE");
2321         node.setAttribute("name", "zimbraPrefOutOfOfficeReplyEnabled");
2322     }
2323     else if(dontRemind){
2324         var node = soapDoc.set("pref", "FALSE");
2325         node.setAttribute("name", "zimbraPrefOutOfOfficeStatusAlertOnLogin");
2326     }
2327 
2328     var paramsObj = {soapDoc:soapDoc, asyncMode:true};
2329     if(turnOff){paramsObj.callback=ZmMailApp._oooReplyCallback;}
2330 
2331     appCtxt.getAppController().sendRequest(paramsObj);
2332 };
2333 
2334 ZmMailApp._oooReplyCallback = function(){
2335     appCtxt.set(ZmSetting.VACATION_MSG_ENABLED,false);
2336 }
2337 
2338 ZmMailApp.prototype._isOnVacation = function() {
2339 	if (!appCtxt.get(ZmSetting.VACATION_MSG_ENABLED)) {
2340 		return false;  //no vacation
2341 	}
2342 	var from = appCtxt.get(ZmSetting.VACATION_FROM);
2343 	var to = appCtxt.get(ZmSetting.VACATION_UNTIL);
2344 
2345 	if (!from) {
2346 		return true; //unlimited vacation (if from is empty, so is to)
2347 	}
2348 
2349 	var today = new Date();
2350 	var formatter = new AjxDateFormat("yyyyMMddHHmmss'Z'");
2351 	var fromDate = formatter.parse(AjxDateUtil.dateGMT2Local(from));
2352 	var toDate = formatter.parse(AjxDateUtil.dateGMT2Local(to));
2353 	return fromDate < today && today < toDate;
2354 };
2355 
2356 
2357 ZmMailApp.prototype._checkVacationReplyEnabled = function(){
2358     if (!appCtxt.get(ZmSetting.VACATION_MSG_REMIND_ON_LOGIN)) {
2359 		return; //reminder not enabled
2360 	}
2361 
2362 	if (!this._isOnVacation()) {
2363 		return;
2364 	}
2365 
2366 	var ynDialog = new DwtMessageDialog({parent:appCtxt.getShell(), buttons:[DwtDialog.YES_BUTTON, DwtDialog.NO_BUTTON], id: "VacationDialog"});
2367 	var content = AjxTemplate.expand("mail.Message#VacationRemindDialog", {id:ynDialog._htmlElId});
2368 	ynDialog.setTitle(ZmMsg.OOORemindDialogTitle);
2369 	ynDialog.setContent(content);
2370 	var dontRemind = document.getElementById(ynDialog._htmlElId + "_dontRemind");
2371 	dontRemind.checked = false;
2372 	ynDialog.registerCallback(DwtDialog.YES_BUTTON, ZmMailApp._handleOOORemindResponse, null, [ynDialog, true]);
2373 	ynDialog.registerCallback(DwtDialog.NO_BUTTON, ZmMailApp._handleOOORemindResponse, null, [ynDialog, false]);
2374 	ynDialog.popup();
2375 };
2376 
2377 ZmMailApp.prototype._createVirtualFolders =
2378 function() {
2379     ZmOffline.addOutboxFolder();
2380 };
2381 
2382 ZmMailApp.prototype.resetWebClientOfflineOperations =
2383 function() {
2384 	ZmApp.prototype.resetWebClientOfflineOperations.apply(this);
2385     //Refreshing the mail list both for online and offline mode
2386 	var isWebClientOnline = !appCtxt.isWebClientOffline();
2387 	this.refresh();
2388 	var folders = appCtxt.getFolderTree().getByType(ZmOrganizer.FOLDER);
2389 	var overview = this.getOverview();
2390 	if (folders && overview) {
2391 		for (var i = 0; i < folders.length; i++) {
2392 			var folder = folders[i];
2393 			var treeItem = folder && overview.getTreeItemById(folder.id);
2394 			if (!treeItem) {
2395 				continue;
2396 			}
2397 			if (isWebClientOnline) {
2398 				treeItem.setVisible(true);
2399 			}
2400 			else {
2401 				//Don't hide ROOT folder and OUTBOX folder
2402 				if (folder.id != ZmFolder.ID_ROOT && folder.rid != ZmFolder.ID_ROOT && folder.id != ZmFolder.ID_OUTBOX && folder.webOfflineSyncDays === 0) {
2403 					treeItem.setVisible(false);
2404 				}
2405 			}
2406 		}
2407 	}
2408 };
2409 
2410 /*
2411  * Enables Mail preferences in case of Admin viewing user account keeping Mail App is disabled.
2412  */
2413 ZmMailApp.prototype.enableMailPrefs =
2414 function() {
2415 	// ZmPref is unavailable, hence we load it, register settings, operations & preferences.
2416 	AjxDispatcher.require("PreferencesCore");
2417 	this._registerSettings();
2418 	this._registerOperations();
2419 	this._registerPrefs();
2420 };
2421 
2422 // Folders to ignore when displaying a conv's messages
2423 ZmMailApp.FOLDERS_TO_OMIT = [ZmFolder.ID_TRASH, ZmFolder.ID_SPAM];
2424 
2425 // returns lookup hash of folders (starting with Trash/Junk) whose messages aren't included when
2426 // viewing or replying a conv; if we're in one of those, we still show its messages
2427 ZmMailApp.getFoldersToOmit = function(search) {
2428 
2429 	search = search || appCtxt.getCurrentSearch();
2430 
2431 	var folders = ZmMailApp.FOLDERS_TO_OMIT,
2432 		omit = [],
2433 		curFolderId = search && search.folderId;
2434 
2435 	var isUserInitiatedSearch = search && search.userInitiated;
2436 
2437 	for (var i = 0; i < folders.length; i++) {
2438 		if (!isUserInitiatedSearch && folders[i] != curFolderId) {
2439 			omit.push(folders[i]);
2440 		}
2441 	}
2442 	return AjxUtil.arrayAsHash(omit);
2443 };
2444 
2445 /*
2446 returns the folders to omit in case of reply/reply-all/forward - this includes DRAFTS always in addition to the others as returned by ZmMailApp.getFoldersToOmit
2447 (the others depend on current folder, but DRAFTS should always be ignored when replying/forwarding, even under Drafts folder)
2448  */
2449 ZmMailApp.getReplyFoldersToOmit = function(search) {
2450 	var omit = ZmMailApp.getFoldersToOmit(search);
2451 	omit[ZmFolder.ID_DRAFTS] = true;
2452 	return omit;
2453 };
2454