1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc.
  5  *
  6  * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at: https://www.zimbra.com/license
  9  * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15
 10  * have been added to cover use of software over a computer network and provide for limited attribution
 11  * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B.
 12  *
 13  * Software distributed under the License is distributed on an "AS IS" basis,
 14  * WITHOUT WARRANTY OF ANY KIND, either express or implied.
 15  * See the License for the specific language governing rights and limitations under the License.
 16  * The Original Code is Zimbra Open Source Web Client.
 17  * The Initial Developer of the Original Code is Zimbra, Inc.  All rights to the Original Code were
 18  * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015.
 19  *
 20  * All portions of the code are Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 ZmAccountsPage = function(parent, section, controller) {
 25 	// bug: 20458 - We don't have ZmDataSource and other classes unless
 26 	//              we load MailCore (which we never do if mail is disabled).
 27 	ZmAccountsPage._definePrefs();
 28 	ZmAccountsPage._defineClasses();
 29 
 30 	ZmPreferencesPage.call(this, parent, section, controller);
 31 
 32 	this._sectionDivs = {};
 33 	this._accounts = new AjxVector();
 34 	this._deletedAccounts = [];
 35 };
 36 ZmAccountsPage.prototype = new ZmPreferencesPage;
 37 ZmAccountsPage.prototype.constructor = ZmAccountsPage;
 38 
 39 ZmAccountsPage.prototype.toString =
 40 function() {
 41 	return "ZmAccountsPage";
 42 };
 43 
 44 //
 45 // Constants
 46 //
 47 
 48 // pref values
 49 
 50 ZmAccountsPage.DOWNLOAD_TO_INBOX  = ZmOrganizer.ID_INBOX;
 51 ZmAccountsPage.DOWNLOAD_TO_FOLDER = -1;
 52 
 53 // counters
 54 
 55 ZmAccountsPage.__personaCount = 0;
 56 ZmAccountsPage.__externalCount = 0;
 57 
 58 // section prefs
 59 
 60 ZmAccountsPage._definePrefs =
 61 function() {
 62 	ZmAccountsPage.PREFS = {
 63 		// Primary / Common
 64 		ALERT: {
 65 			displayContainer:	ZmPref.TYPE_CUSTOM
 66 		},
 67 		PROVIDER: {
 68 			displayContainer:   ZmPref.TYPE_SELECT
 69 		},
 70 		NAME: {
 71 			displayContainer:	ZmPref.TYPE_INPUT
 72 		},
 73 		HEADER: {
 74 			displayContainer:	ZmPref.TYPE_STATIC,
 75 			displayName:		ZmMsg.accountSubHeader
 76 		},
 77 		EMAIL: {
 78 			displayContainer:	ZmPref.TYPE_INPUT,
 79 			hint:				ZmMsg.exampleEmailAddr
 80 		},
 81 		VISIBLE: {
 82 			displayContainer:	ZmPref.TYPE_CHECKBOX
 83 		},
 84 		REPLY_TO: {
 85 			displayName:		ZmMsg.accountReplyTo,
 86 			displayContainer:	ZmPref.TYPE_CHECKBOX
 87 		},
 88 		REPLY_TO_NAME: {
 89 			displayContainer:	ZmPref.TYPE_INPUT,
 90 			hint:				ZmMsg.exampleEmailName
 91 		},
 92 		REPLY_TO_EMAIL: {
 93 			displayContainer:	ZmPref.TYPE_COMBOBOX,
 94 			hint:				ZmMsg.emailAddr
 95 		},
 96 		// External
 97 		ACCOUNT_TYPE: {
 98 			displayContainer:	ZmPref.TYPE_RADIO_GROUP,
 99 			orientation:		ZmPref.ORIENT_HORIZONTAL,
100 			displayOptions:		["POP3", "IMAP"], // TODO: i18n
101 			options:			[ZmAccount.TYPE_POP, ZmAccount.TYPE_IMAP]
102 		},
103 		USERNAME: {
104 			displayContainer:	ZmPref.TYPE_INPUT
105 		},
106 		HOST: {
107 			displayContainer:	ZmPref.TYPE_INPUT,
108 			hint:				ZmMsg.exampleMailServer
109 		},
110 		PASSWORD: {
111 			// TODO: rename ZmPref.TYPE_PASSWORD to TYPE_CHANGE_PASSWORD
112 			// TODO: add ZmPref.TYPE_PASSWORD
113 			displayContainer:	ZmPref.TYPE_INPUT
114 		},
115 		CHANGE_PORT: {
116 			displayName:		ZmMsg.accountChangePortLabel,
117 			displayContainer:	ZmPref.TYPE_CHECKBOX
118 		},
119 		PORT: {
120 			displayContainer:	ZmPref.TYPE_INPUT,
121 			validator:			DwtInputField.validateNumber
122 		},
123 		PORT_DEFAULT: {
124 			displayContainer:	ZmPref.TYPE_STATIC,
125 			displayName:		ZmMsg.accountPortDefault
126 		},
127 		SSL: {
128 			displayName:		ZmMsg.accountUseSSL,
129 			displayContainer:	ZmPref.TYPE_CHECKBOX,
130 			options:			[ZmDataSource.CONNECT_CLEAR, ZmDataSource.CONNECT_SSL]
131 		},
132 		TEST: {
133 			displayName:		ZmMsg.accountTest,
134 			displayContainer:	ZmPref.TYPE_CUSTOM // NOTE: a button
135 		},
136 		DOWNLOAD_TO: {
137 			displayContainer:	ZmPref.TYPE_RADIO_GROUP,
138 			displayOptions:		[ZmMsg.accountDownloadToInbox, ZmMsg.accountDownloadToFolder],
139 			options:			[ZmAccountsPage.DOWNLOAD_TO_INBOX, ZmAccountsPage.DOWNLOAD_TO_FOLDER]
140 		},
141 		DELETE_AFTER_DOWNLOAD: {
142 			displayName:		ZmMsg.accountDeleteAfterDownload,
143 			displayContainer:	ZmPref.TYPE_CHECKBOX
144 		},
145 		// Persona
146 		FROM_NAME: {
147 			displayContainer:	ZmPref.TYPE_INPUT,
148 			hint:				ZmMsg.exampleEmailName
149 		},
150 		FROM_EMAIL: {
151 			displayContainer:	ZmPref.TYPE_SELECT,
152 			hint:				ZmMsg.emailAddr
153 		},
154 		FROM_EMAIL_TYPE: {
155 			displayContainer:	ZmPref.TYPE_SELECT,
156 			hint:				ZmMsg.emailAddr
157 		},
158 		WHEN_SENT_TO: {
159 			displayName:		ZmMsg.personaWhenSentTo,
160 			displayContainer:	ZmPref.TYPE_CHECKBOX
161 		},
162 		WHEN_SENT_TO_LIST: {
163 			displayContainer:	ZmPref.TYPE_INPUT
164 		},
165 		WHEN_IN_FOLDER: {
166 			displayName:		ZmMsg.personaWhenInFolder,
167 			displayContainer:	ZmPref.TYPE_CHECKBOX
168 		},
169 		WHEN_IN_FOLDER_LIST: {
170 			displayContainer:	ZmPref.TYPE_INPUT,
171 			hint:				ZmMsg.exampleFolderNames
172 		},
173 		WHEN_IN_FOLDER_BUTTON: {
174 			displayContainer:	ZmPref.TYPE_CUSTOM
175 		}
176 	};
177 
178 	/**
179 	 * Defines the various account sections. Each section has a list of "prefs"
180 	 * that may appear in that section. In the code below, each pref is marked
181 	 * as "A" if it's a field on the account object and "I" if it's a field on
182 	 * the identity object.
183 	 */
184 	ZmAccountsPage.SECTIONS = {
185 		PRIMARY: {
186 			id: "PRIMARY",
187 			prefs: [
188 				"NAME",					// A
189 				"HEADER",				//
190 				"EMAIL",				// A
191 				"VISIBLE",				//
192 				"FROM_NAME",			// I
193 				"FROM_EMAIL",			// I
194 				"FROM_EMAIL_TYPE",			// I
195 				"REPLY_TO",				// I
196 				"REPLY_TO_NAME",		// I
197 				"REPLY_TO_EMAIL"		// I
198 			]
199 		},
200 		EXTERNAL: {
201 			id: "EXTERNAL",
202 			prefs: [
203 				"ALERT",
204 				"PROVIDER",
205 				"NAME",						// A
206 				"HEADER",
207 				"EMAIL",					// I - maps to from name in identity
208 				"ACCOUNT_TYPE",				// A
209 				"USERNAME",					// A
210 				"HOST",						// A
211 				"PASSWORD",					// A
212 				"CHANGE_PORT",
213 				"PORT",						// A
214 				"PORT_DEFAULT",
215 				"SSL",						// A
216 				"TEST",
217 				"DOWNLOAD_TO",				// A
218 				"DELETE_AFTER_DOWNLOAD",	// A
219 				"FROM_NAME",				// I
220 				"FROM_EMAIL",				// I
221 				"REPLY_TO",					// I
222 				"REPLY_TO_NAME",			// I
223 				"REPLY_TO_EMAIL"			// I
224 			]
225 		},
226 		PERSONA: {
227 			id: "PERSONA",
228 			prefs: [
229 				"NAME",						// I
230 				"HEADER",
231 				"FROM_NAME",				// I
232 				"FROM_EMAIL",				// I
233 				"FROM_EMAIL_TYPE",				// I
234 				"REPLY_TO",					// I
235 				"REPLY_TO_NAME",			// I
236 				"REPLY_TO_EMAIL",			// I
237 				"WHEN_SENT_TO",				// I
238 				"WHEN_SENT_TO_LIST",		// I
239 				"WHEN_IN_FOLDER",			// I
240 				"WHEN_IN_FOLDER_LIST",		// I
241 				"WHEN_IN_FOLDER_BUTTON"
242 			]
243 		}
244 	};
245 
246 	/*** DEBUG ***
247 	var providers = [
248 		{ id: "yahoo-pop", name: "Yahoo! Mail", type: "POP", host: "yahoo.com" },
249 		{ id: "gmail-pop", name: "GMail (POP)", type: "POP", host: "pop.gmail.com", connectionType: "ssl" },
250 		{ id: "gmail-imap", name: "GMail (IMAP)", type: "IMAP", host: "imap.gmail.com", connectionType: "ssl" }
251 	];
252 	for (var i = 0; i < providers.length; i++) {
253 		ZmDataSource.addProvider(providers[i]);
254 	}
255 	/***/
256 
257 	// create a section for each provider with a custom template
258 	var providers = ZmDataSource.getProviders();
259 	for (var id in providers) {
260 		if (!AjxTemplate.getTemplate("prefs.Pages#ExternalAccount-"+id)) { continue; }
261 		ZmAccountsPage.SECTIONS[id] = {
262 			id:    id,
263 			prefs: ZmAccountsPage.SECTIONS["EXTERNAL"].prefs
264 		};
265 	}
266 
267 }; // function ZmAccountsPage._definePrefs
268 
269 ZmAccountsPage.ACCOUNT_PROPS = {
270 	"NAME":						{ setter: "setName",	getter: "getName" },
271 	"EMAIL":					{ setter: "setEmail",	getter: "getEmail" },
272 	"EMAIL_TYPE":					"emailType",
273 	"VISIBLE":					"visible",
274 	"ACCOUNT_TYPE":				"type",
275 	"USERNAME":					"userName",
276 	"HOST":						"mailServer",
277 	"PASSWORD":					"password",
278 	"PORT":						"port",
279 	"SSL":						"connectionType",
280 	"DOWNLOAD_TO":				{ setter: "setFolderId", getter: "getFolderId" },
281 	"DELETE_AFTER_DOWNLOAD":	"leaveOnServer"
282 };
283 
284 ZmAccountsPage.IDENTITY_PROPS = {
285 	"FROM_NAME":			"sendFromDisplay",
286 	"FROM_EMAIL":			"sendFromAddress",
287 	"FROM_EMAIL_TYPE":		"sendFromAddressType",
288 	"REPLY_TO":				"setReplyTo",
289 	"REPLY_TO_NAME":		"setReplyToDisplay",
290 	"REPLY_TO_EMAIL":		"setReplyToAddress",
291 	"WHEN_SENT_TO":			"useWhenSentTo",
292 	"WHEN_SENT_TO_LIST":	"whenSentToAddresses",
293 	"WHEN_IN_FOLDER":		"useWhenInFolder",
294 	"WHEN_IN_FOLDER_LIST":	"whenInFolderIds"
295 };
296 
297 ZmAccountsPage.prototype._handleTwoStepAuthLink =
298 function(params) {
299 	if (appCtxt.get(ZmSetting.TWO_FACTOR_AUTH_ENABLED)) {
300 		var dialog = appCtxt.getYesNoMsgDialog();
301 		dialog.setMessage(ZmMsg.twoStepAuthDisableConfirm, DwtMessageDialog.CRITICAL_STYLE, ZmMsg.twoStepAuthDisable);
302 		dialog.registerCallback(DwtDialog.YES_BUTTON, ZmTwoFactorSetupDialog.disableTwoFactorAuth.bind(window, params, dialog));
303 	}
304 	else {
305 		if (!this._twoFactorSetupDialog) {
306 			this._twoFactorSetupDialog = new ZmTwoFactorSetupDialog(params);
307 		}
308 		var dialog = this._twoFactorSetupDialog;
309 	}
310 	dialog.popup();
311 };
312 
313 ZmAccountsPage.prototype._handleTwoStepAuthCodesViewLink =
314 function(params) {
315 	if (!this._oneTimeCodesDialog) {
316 		this._oneTimeCodesDialog = new ZmOneTimeCodesDialog(params);
317 	}
318 	this._oneTimeCodesDialog.popup();
319 };
320 
321 ZmAccountsPage.prototype.getGrantRightsDlg =
322 function(callback) {
323 
324     if (!this._grantRightsDlg) {
325         this._grantRightsDlg = new ZmGrantRightsDialog(appCtxt.getShell(), "ZmGrantRightsDialog");
326     }
327     this._grantRightsDlg._okCallBack = callback;
328     return this._grantRightsDlg;
329 
330 };
331 
332 ZmAccountsPage.prototype._errRightsCommand =
333 function(user, ex) {
334     this._delegateErrFormatter = this._delegateErrFormatter || new AjxMessageFormat(ZmMsg.delegateNoSuchAccErr);
335     var msg = this._delegateErrFormatter.format(user);
336     if (ex.code === "account.NO_SUCH_ACCOUNT") {
337         appCtxt.getAppController().popupErrorDialog(msg, ex);
338         return true;
339     }
340 
341     return false;
342 
343 };
344 
345 ZmAccountsPage.prototype._handleDelegateRights =
346 function(user, sendAs, sendObo, isGrant, refresh) {
347 	var request = isGrant ? "GrantRightsRequest" : "RevokeRightsRequest";
348 	var soapDoc = AjxSoapDoc.create(request, "urn:zimbraAccount");
349 	var batchCmd = new ZmBatchCommand(null, appCtxt.accountList.mainAccount.name);
350 	var callback = this._handleDelegateRightsCallback.bind(this, user, sendAs, sendObo, isGrant, refresh);
351 	var errCallback = this._errRightsCommand.bind(this, user);
352 	var aceNode = null;
353 	if (sendAs){
354 		aceNode = soapDoc.set("ace");
355 		aceNode.setAttribute("gt", "usr");
356 		aceNode.setAttribute("d", user);
357 		aceNode.setAttribute("right", ZmSetting.SEND_AS);
358 	}
359 
360 	if (sendObo){
361 		aceNode = soapDoc.set("ace");
362 		aceNode.setAttribute("gt", "usr");
363 		aceNode.setAttribute("d", user);
364 		aceNode.setAttribute("right", ZmSetting.SEND_ON_BEHALF_OF);
365 	}
366 
367 	batchCmd.addNewRequestParams(soapDoc, callback, errCallback);
368 	batchCmd.run();
369 };
370 
371  ZmAccountsPage.prototype._handleDelegateRightsCallback =
372  function(user, sendAs, sendObo, isGrant, refresh, result){
373      var email = user;
374      if (isGrant){
375         var response = result.getResponse();
376         var aces = response && response.GrantRightsResponse && response.GrantRightsResponse.ace;
377         if (aces && aces.length && aces[0].d)
378             email = aces[0].d;
379      }
380      if (refresh) {
381          this._getGrants();
382      }
383      this._sendGrantRightsMessage(appCtxt.accountList.mainAccount.name, email, sendAs, sendObo, isGrant);
384 
385  };
386 
387 ZmAccountsPage.prototype._grantDelegateRights =
388 function(user, sendAs, sendObo) {
389     if (sendAs || sendObo){
390         this._handleDelegateRights(user, sendAs, sendObo, true, true);
391     }
392 };
393 
394 ZmAccountsPage.prototype._handleAddDelegateButton =
395 function() {
396     var callback = this._grantDelegateRights.bind(this);
397     var dlg = this.getGrantRightsDlg(callback);
398     dlg.setData();
399     dlg.popup();
400 };
401 
402 
403 ZmAccountsPage.prototype._editDelegateRights =
404 function() {
405     var user = this._editDelegateOrig.user;
406     var sendAs = arguments[1];
407     var sendObo = arguments[2];
408     var isGrant = null;
409     var updateSendAs = false;
410     var updateSendObo = false;
411 
412     if (sendAs != this._editDelegateOrig.sendAs && sendObo != this._editDelegateOrig.sendOnBehalfOf){
413         if (sendAs == sendObo){
414             updateSendAs = updateSendObo = true;
415             isGrant = sendAs;
416         }else{
417             this._handleDelegateRights(user, true, false, sendAs, false);
418             updateSendObo = true;
419             isGrant = sendObo;
420         }
421     }else if (sendAs != this._editDelegateOrig.sendAs){
422             updateSendAs = true;
423             isGrant = sendAs;
424     }else if (sendObo != this._editDelegateOrig.sendOnBehalfOf){
425             updateSendObo = true;
426             isGrant = sendObo;
427     }
428     this._handleDelegateRights(user, updateSendAs, updateSendObo, isGrant, true);
429 };
430 
431 ZmAccountsPage.prototype._editDelegateButton =
432     function() {
433     var item = this.delegatesList.getSelection()[0];
434     this._editDelegateOrig = item;
435     this._editDelegateOrig.sendAs = item.sendAs || false;
436     this._editDelegateOrig.sendOnBehalfOf = item.sendOnBehalfOf || false;
437     var callback = this._editDelegateRights.bind(this);
438     var dlg = this.getGrantRightsDlg(callback);
439     dlg.setData(item);
440     dlg.popup();
441 };
442 ZmAccountsPage.prototype._compareDelegateEntries =
443 function(a, b) {
444 	return a.user < b.user ? -1 : (a.user > b.user ? 1 : 0);
445 };
446 ZmAccountsPage.prototype._setDelegateSendPrefs =
447 function(grants) {
448     var ace = grants._data.GetRightsResponse && grants._data.GetRightsResponse.ace;
449     var userRights = [];
450     var right = user = node = null;
451     var res = new AjxVector();
452     if (!ace){
453         this._dlSelectionListener(false);
454         return res;
455     }
456     for (var i=0;i<ace.length;i++){
457         user = ace[i].d;
458         right = ace[i].right;
459         if (!userRights[user]){
460          userRights[user] = {user:user, id:ace[i].zid};
461 
462         }
463         userRights[user][right] = true;
464     }
465 
466     for (var usr in userRights) {
467         res.add(userRights[usr])
468     }
469 
470     res.sort(this._compareDelegateEntries);
471     return res;
472 };
473 
474 ZmAccountsPage.prototype._refreshRights =
475 function(grants) {
476     var delegatesEl =   document.getElementById(this._htmlElId+"_DELEGATE_RIGHTS") || (this.delegatesList && this.delegatesList.getHtmlElement());
477     var dlUsers = this._setDelegateSendPrefs(grants);
478     var delegatesList = new ZmAccountDelegatesListView(this);
479     delegatesList.replaceElement(delegatesEl);
480     delegatesList.setEnabled(true);
481     delegatesList.set(dlUsers);
482     delegatesList.addSelectionListener(this._dlSelectionListener.bind(this,(dlUsers.size() > 0)));
483     this.delegatesList = delegatesList;
484     if (this._grantRightsDlg && this._grantRightsDlg.isPoppedUp()) this._grantRightsDlg.popdown();
485 
486     this._dlSelectionListener(false);
487 };
488 
489 ZmAccountsPage.prototype._removeDelegateButton =
490 function() {
491     var item = this.delegatesList.getSelection()[0];
492     this._handleDelegateRights(item.user, item.sendAs, item.sendOnBehalfOf, false, true);
493 };
494 
495 ZmAccountsPage.prototype._sendGrantRightsMessage =
496 function(granter, grantee, sendAs,sendObo,isGrant) {
497     var msg = new ZmMailMsg();
498     var addrs = new AjxVector();
499     var permissions = (sendAs && sendObo)?ZmMsg.sendAsAndSendOnBehalfOf:(sendAs?ZmMsg.sendAs:ZmMsg.sendOnBehalfOflbl);
500     var subject = "";
501     var status = "";
502     if (isGrant){
503         status = (sendAs && sendObo) ? ZmMsg.delegateRightsStatus : ZmMsg.delegateRightStatus;
504         subject = (sendAs && sendObo) ? ZmMsg.delegateRightsSubject : ZmMsg.delegateRightSubject;
505     } else{
506         status = (sendAs && sendObo) ? ZmMsg.revokeRightsStatus : ZmMsg.revokeRightStatus;
507         subject = (sendAs && sendObo) ? ZmMsg.revokeRightsSubject : ZmMsg.revokeRightSubject;
508     }
509     subject = AjxMessageFormat.format(subject, [granter]);
510     status = AjxMessageFormat.format(status, grantee);
511     var text = (isGrant)?ZmMsg.delegateCreatedText : ZmMsg.delegateRevokedText;
512     text = AjxMessageFormat.format(text, [permissions, grantee, granter]);
513     var html = (isGrant)?ZmMsg.delegateCreatedHtml : ZmMsg.delegateRevokedHtml;
514     html = AjxMessageFormat.format(html, [permissions, grantee, granter]);
515     appCtxt.setStatusMsg(status);
516 
517     addrs.add(new AjxEmailAddress(grantee, AjxEmailAddress.TO));
518     msg.setAddresses(AjxEmailAddress.TO, addrs);
519     msg.setSubject(subject);
520     var topPart = new ZmMimePart();
521     topPart.setContentType(ZmMimeTable.MULTI_ALT);
522 
523     var htmlPart = new ZmMimePart();
524     htmlPart.setContentType(ZmMimeTable.TEXT_HTML);
525     htmlPart.setContent(html);
526 
527     var textPart = new ZmMimePart();
528     textPart.setContentType(ZmMimeTable.TEXT_PLAIN);
529     textPart.setContent(text);
530 
531     topPart.children.add(textPart);
532     topPart.children.add(htmlPart);
533     msg.setTopPart(topPart);
534     msg.send();
535 };
536 
537 ZmAccountsPage.getScratchCodes =
538 function(isNew, params, callback) {
539 	if (isNew) {
540 		var jsonObj = {GenerateScratchCodesRequest : {_jsns:"urn:zimbraAccount"}};
541 	}
542 	else {
543 		var jsonObj = {GetScratchCodesRequest: {_jsns: "urn:zimbraAccount"}};
544 	}
545 	var getScratchCodesCallback = ZmAccountsPage._getScratchCodesCallback.bind(window, isNew, params, callback);
546 	appCtxt.getAppController().sendRequest({jsonObj:jsonObj, asyncMode:true, callback:getScratchCodesCallback});
547 };
548 
549 ZmAccountsPage._getScratchCodesCallback =
550 function(isNew, params, callback, result) {
551 	if (!result || result.isException()) {
552 		return;
553 	}
554 	var response = result.getResponse();
555 	var scratchCodesResponse = response && (response.GetScratchCodesResponse || response.GenerateScratchCodesResponse);
556 	if (!scratchCodesResponse) {
557 		return;
558 	}
559 	var scratchCode = scratchCodesResponse.scratchCodes && scratchCodesResponse.scratchCodes.scratchCode;
560 	if (scratchCode) {
561 		var scratchCodeArray = [];
562 		for (var i = 0; i < scratchCode.length; i++) {
563 			if (scratchCode[i]._content) {
564 				scratchCodeArray.push(scratchCode[i]._content);
565 			}
566 		}
567 		if (scratchCodeArray.length === 0) {
568 			Dwt.setInnerHtml(params.twoStepAuthCodesSpan, ZmMsg.twoStepAuthOneTimeCodesEmpty);
569 			Dwt.setDisplay(params.twoStepAuthCodesViewLink, Dwt.DISPLAY_NONE);
570 			Dwt.setDisplay(params.twoStepAuthCodesGenerateLink, "");
571 		}
572 		else {
573 			Dwt.setInnerHtml(params.twoStepAuthCodesSpan, AjxMessageFormat.format(ZmMsg.twoStepAuthOneTimeCodesCount, scratchCodeArray.length));
574 			Dwt.setDisplay(params.twoStepAuthCodesViewLink, "");
575 			Dwt.setDisplay(params.twoStepAuthCodesGenerateLink, Dwt.DISPLAY_NONE);
576 		}
577 		if (callback) {
578 			callback(scratchCodeArray);
579 		}
580 	}
581 };
582 
583 ZmAccountsPage.prototype._getTrustedDevicesCount =
584 function() {
585 	var jsonObj = {GetTrustedDevicesRequest : {_jsns : "urn:zimbraAccount"}};
586 	var respCallback = this._getTrustedDevicesCountCallback.bind(this);
587 	appCtxt.getAppController().sendRequest({jsonObj:jsonObj, asyncMode:true, callback:respCallback});
588 };
589 
590 ZmAccountsPage.prototype._getTrustedDevicesCountCallback =
591 function(result) {
592 	var response = result && result.getResponse();
593 	var getTrustedDevicesResponse = response && response.GetTrustedDevicesResponse;
594 	if (!response || !getTrustedDevicesResponse) {
595 		return;
596 	}
597 	var trustedDevicesCount = 0;
598 	var trustedDeviceRevokeLink = document.getElementById(this._htmlElId + "_TRUSTED_DEVICE_REVOKE_LINK");
599 	if (getTrustedDevicesResponse.thisDeviceTrusted) {
600 		if (trustedDeviceRevokeLink) {
601 			Dwt.setHandler(trustedDeviceRevokeLink, DwtEvent.ONCLICK, this._handleTrustedDeviceRevokeLink.bind(this));
602 			Dwt.delClass(trustedDeviceRevokeLink, "ZmLinkDisabled");
603 		}
604 		trustedDevicesCount = 1;
605 	}
606 	else {
607 		if (trustedDeviceRevokeLink) {
608 			Dwt.addClass(trustedDeviceRevokeLink, "ZmLinkDisabled");
609 		}
610 	}
611 	var trustedDevicesRevokeAllLink = document.getElementById(this._htmlElId + "_TRUSTED_DEVICES_REVOKE_ALL_LINK");
612 	if (getTrustedDevicesResponse.nOtherDevices) {
613 		if (trustedDevicesRevokeAllLink) {
614 			Dwt.setHandler(trustedDevicesRevokeAllLink, DwtEvent.ONCLICK, this._handleTrustedDevicesRevokeAllLink.bind(this));
615 			Dwt.delClass(trustedDevicesRevokeAllLink, "ZmLinkDisabled");
616 		}
617 		trustedDevicesCount = trustedDevicesCount + parseInt(getTrustedDevicesResponse.nOtherDevices);
618 	}
619 	else {
620 		if (trustedDevicesRevokeAllLink) {
621 			Dwt.addClass(trustedDevicesRevokeAllLink, "ZmLinkDisabled");
622 		}
623 	}
624 	var trustedDevicesCountSpan = document.getElementById(this._htmlElId + "_TRUSTED_DEVICES_COUNT");
625 	Dwt.setInnerHtml(trustedDevicesCountSpan, AjxMessageFormat.format(ZmMsg.trustedDevicesCount, trustedDevicesCount));
626 };
627 
628 ZmAccountsPage.prototype._handleTrustedDeviceRevokeLink =
629 function() {
630 	var trustedDeviceRevokeLink = document.getElementById(this._htmlElId + "_TRUSTED_DEVICE_REVOKE_LINK");
631 	//link is currently disabled by CSS pointer-event which will prevent onclick event. For older browsers just check for ZmLinkDisabled class
632 	if (Dwt.hasClass(trustedDeviceRevokeLink, "ZmLinkDisabled")) {
633 		return false;
634 	}
635 	var msgDialog = appCtxt.getOkCancelMsgDialog();
636 	msgDialog.setMessage(ZmMsg.revokeTrustedDeviceMsg, DwtMessageDialog.WARNING_STYLE, ZmMsg.revokeTrustedDevice);
637 	msgDialog.registerCallback(DwtDialog.OK_BUTTON, this._revokeTrustedDevice.bind(this, msgDialog));
638 	msgDialog.getButton(DwtDialog.OK_BUTTON).setText(ZmMsg.revoke);
639 	msgDialog.popup();
640 };
641 
642 ZmAccountsPage.prototype._revokeTrustedDevice =
643 function(msgDialog) {
644 	msgDialog.popdown();
645 	var jsonObj = {RevokeTrustedDeviceRequest : {_jsns : "urn:zimbraAccount"}};
646 	var respCallback = this._revokeTrustedDeviceCallback.bind(this);
647 	appCtxt.getAppController().sendRequest({jsonObj:jsonObj, asyncMode:true, callback:respCallback});
648 };
649 
650 ZmAccountsPage.prototype._revokeTrustedDeviceCallback =
651 function() {
652 	this._getTrustedDevicesCount();
653 };
654 
655 ZmAccountsPage.prototype._handleTrustedDevicesRevokeAllLink =
656 function() {
657 	var trustedDevicesRevokeAllLink = document.getElementById(this._htmlElId + "_TRUSTED_DEVICES_REVOKE_ALL_LINK");
658 	if (Dwt.hasClass(trustedDevicesRevokeAllLink, "ZmLinkDisabled")) {
659 		return false;
660 	}
661 	var msgDialog = appCtxt.getOkCancelMsgDialog();
662 	msgDialog.setMessage(ZmMsg.revokeAllTrustedDevicesMsg, DwtMessageDialog.WARNING_STYLE, ZmMsg.revokeAllTrustedDevices);
663 	msgDialog.registerCallback(DwtDialog.OK_BUTTON, this._revokeOtherTrustedDevices.bind(this, msgDialog));
664 	msgDialog.getButton(DwtDialog.OK_BUTTON).setText(ZmMsg.revokeAll);
665 	msgDialog.popup();
666 };
667 
668 ZmAccountsPage.prototype._revokeOtherTrustedDevices =
669 function(msgDialog) {
670 	msgDialog.popdown();
671 	var jsonObj = {RevokeOtherTrustedDevicesRequest : {_jsns : "urn:zimbraAccount"}};
672 	var respCallback = this._revokeOtherTrustedDevicesCallback.bind(this);
673 	appCtxt.getAppController().sendRequest({jsonObj:jsonObj, asyncMode:true, callback:respCallback});
674 };
675 
676 ZmAccountsPage.prototype._revokeOtherTrustedDevicesCallback =
677 function() {
678 	this._getTrustedDevicesCount();
679 };
680 
681 ZmAccountsPage.prototype._getGrants =
682 function() {
683     var jsonObj = {GetRightsRequest:{
684         _jsns:"urn:zimbraAccount",
685         "ace":[
686             {right:ZmSetting.SEND_AS},
687             {right:ZmSetting.SEND_ON_BEHALF_OF}
688         ]
689       }
690     };
691     var respCallback = this._refreshRights.bind(this);
692     appCtxt.getAppController().sendRequest({jsonObj:jsonObj, asyncMode:true, callback:respCallback});
693 };
694 
695 ZmAccountsPage.prototype._dlSelectionListener =
696 function(opt) {
697     this.editDelegateButton.setEnabled(opt);
698     this.removeDelegateButton.setEnabled(opt);
699 }
700 
701 ZmAccountsPage.prototype.setAccountSecurity =
702 function() {
703 	//If two-factor authentication feature is not available just return.
704 	if (!appCtxt.get(ZmSetting.TWO_FACTOR_AUTH_AVAILABLE)) {
705 		return;
706 	}
707 	//If two-factor authentication is required user cannot see the Enable/Disable two-step authentication link.
708 	if (!appCtxt.get(ZmSetting.TWO_FACTOR_AUTH_REQUIRED)) {
709 		var twoStepAuthLink = document.getElementById(this._htmlElId + "_TWO_STEP_AUTH_LINK");
710 		if (twoStepAuthLink) {
711 			var paramsObj = {
712 				twoStepAuthLink : twoStepAuthLink,
713 				twoStepAuthSpan : Dwt.getElement(this._htmlElId + "_TWO_STEP_AUTH"),
714 				twoStepAuthCodesContainer : Dwt.getElement(this._htmlElId + "_TWO_STEP_AUTH_CODES_CONTAINER"),
715 				twoStepAuthEnabledCallback : this.setAccountSecurity.bind(this)
716 			};
717 			Dwt.setHandler(twoStepAuthLink, DwtEvent.ONCLICK, this._handleTwoStepAuthLink.bind(this, paramsObj));
718 		}
719 	}
720 	//If two-factor authentication is not enabled just return.
721 	if (!appCtxt.get(ZmSetting.TWO_FACTOR_AUTH_ENABLED)) {
722 		return;
723 	}
724 
725 	var twoStepAuthCodesSpan = document.getElementById(this._htmlElId + "_TWO_STEP_AUTH_CODES");
726 	if (twoStepAuthCodesSpan) {
727 		var twoStepAuthCodesViewLink = document.getElementById(this._htmlElId + "_TWO_STEP_AUTH_CODES_VIEW_LINK");
728 		var twoStepAuthCodesGenerateLink = document.getElementById(this._htmlElId + "_TWO_STEP_AUTH_CODES_GENERATE_LINK");
729 		var params = {
730 			twoStepAuthCodesSpan : twoStepAuthCodesSpan,
731 			twoStepAuthCodesViewLink : twoStepAuthCodesViewLink,
732 			twoStepAuthCodesGenerateLink : twoStepAuthCodesGenerateLink
733 		};
734 		if (twoStepAuthCodesViewLink) {
735 			Dwt.setHandler(twoStepAuthCodesViewLink, DwtEvent.ONCLICK, this._handleTwoStepAuthCodesViewLink.bind(this, params));
736 		}
737 		if (twoStepAuthCodesGenerateLink) {
738 			Dwt.setHandler(twoStepAuthCodesGenerateLink, DwtEvent.ONCLICK, ZmAccountsPage.getScratchCodes.bind(window, true, params, false));
739 		}
740 		ZmAccountsPage.getScratchCodes(false, params, false);
741 	}
742 
743 	if (appCtxt.get(ZmSetting.TRUSTED_DEVICES_ENABLED)) {
744 		var trustedDevicesCountSpan = document.getElementById(this._htmlElId + "_TRUSTED_DEVICES_COUNT");
745 		if (trustedDevicesCountSpan) {
746 			this._getTrustedDevicesCount();
747 		}
748 	}
749 
750 	//Whether app-specific passwords are enabled when two-factor auth is enabled
751 	if (appCtxt.get(ZmSetting.APP_PASSWORDS_ENABLED)) {
752 		var applicationCodesElement = document.getElementById(this._htmlElId + "_APPLICATION_CODES");
753 		if (!applicationCodesElement) {
754 			return;
755 		}
756 		this._getAppSpecificPasswords(applicationCodesElement);
757 
758 		var addApplicationCodeButton = document.getElementById(this._htmlElId + "_ADD_APPLICATION_CODE");
759 		if (addApplicationCodeButton) {
760 			var button = new DwtButton({parent:this, id:"addApplicationCodeBtn"});
761 			button.setText(ZmMsg.twoStepAuthAddAppCode);
762 			button.setEnabled(true);
763 			button.addSelectionListener(new AjxListener(this, this._handleAddApplicationCode));
764 			button.replaceElement(addApplicationCodeButton);
765 			this.addApplicationCodeButton = button;
766 		}
767 
768 		var revokeApplicationCodeButton = document.getElementById(this._htmlElId+"_REVOKE_APPLICATION_CODE");
769 		if (revokeApplicationCodeButton) {
770 			var button = new DwtButton({parent:this, id:"revokeApplicationCodeBtn"});
771 			button.setText(ZmMsg.twoStepAuthRevokeCode);
772 			button.setEnabled(false);
773 			button.addSelectionListener(new AjxListener(this, this._revokeApplicationCode));
774 			button.replaceElement(revokeApplicationCodeButton);
775 			this.revokeApplicationCodeButton = button;
776 		}
777 	}
778 };
779 
780 ZmAccountsPage.prototype.setAccountDelegates =
781 function() {
782     var delegatesEl =   document.getElementById(this._htmlElId+"_DELEGATE_RIGHTS");
783     if(!delegatesEl) return;
784     this._getGrants(delegatesEl);
785 
786 
787     var addDelegateButtonDiv = document.getElementById(this._htmlElId+"_ADD_DELEGATE");
788 	if (addDelegateButtonDiv) {
789 		var button = new DwtButton({parent:this, id:"addDelegateBtn"});
790 		button.setText(ZmMsg.addDelegate);
791 		button.setEnabled(true);
792 		button.addSelectionListener(new AjxListener(this, this._handleAddDelegateButton));
793         button.replaceElement(addDelegateButtonDiv);
794 		this.addDelegateButton = button;
795 	}
796 
797     var editDelegateButtonDiv = document.getElementById(this._htmlElId+"_EDIT_DELEGATE");
798     if (editDelegateButtonDiv) {
799         var button = new DwtButton({parent:this, id:"editDelegateBtn"});
800         button.setText(ZmMsg.editPermissions);
801         button.setEnabled(false);
802         button.addSelectionListener(new AjxListener(this, this._editDelegateButton));
803         button.replaceElement(editDelegateButtonDiv);
804         this.editDelegateButton = button;
805     }
806 
807     var removeDelegateButtonDiv = document.getElementById(this._htmlElId+"_REMOVE_DELEGATE");
808     if (removeDelegateButtonDiv) {
809        var button = new DwtButton({parent:this, id:"removeDelegateBtn"});
810        button.setText(ZmMsg.remove);
811        button.setEnabled(false);
812        button.addSelectionListener(new AjxListener(this, this._removeDelegateButton));
813        button.replaceElement(removeDelegateButtonDiv);
814        this.removeDelegateButton = button;
815     }
816 
817 
818 
819 }
820 //
821 // Public methods
822 //
823 
824 ZmAccountsPage.prototype.setAccount =
825 function(account, skipUpdate, ignoreProvider) {
826 	// keep track of changes made to current account
827 	if (this._currentAccount) {
828 		if (!skipUpdate) {
829 			this._setAccountFields(this._currentAccount, this._currentSection);
830 		}
831 		this._tabGroup.removeMember(this._currentSection.tabGroup);
832 		this._currentAccount = null;
833 		this._currentSection = null;
834 	}
835 
836 	// toggle delete button
837 	if (this._deleteButton) {
838 		var isEnabled = (appCtxt.isOffline)
839 			? (account && account.type == ZmAccount.TYPE_PERSONA)
840 			: (account && account.type != ZmAccount.TYPE_ZIMBRA);
841         if (account.smtpEnabled) {
842             isEnabled = false;
843         }
844 		this._deleteButton.setEnabled(isEnabled);
845 	}
846 
847 	// intialize sections
848 	for (var type in this._sectionDivs) {
849 		Dwt.setVisible(this._sectionDivs[type], false);
850 	}
851 
852 	// HACK: Attempt to get around an IE update issue.
853 	setTimeout(AjxCallback.simpleClosure(this._setAccount2, this, account, skipUpdate, ignoreProvider),0);
854 };
855 
856 ZmAccountsPage.prototype._setAccount2 =
857 function(account, skipUpdate, ignoreProvider) {
858 	// NOTE: hide all sections first, then show the specific section b/c some
859 	// sections use same div. This avoids double inititalization in that case.
860 	var isExternal = account instanceof ZmDataSource;
861 	var provider = !ignoreProvider && isExternal && account.getProvider();
862 	var div = (provider && this._sectionDivs[provider.id]) || this._getSectionDiv(account);
863     if (div) {
864         this._currentAccount = account;
865         Dwt.setVisible(div, true);
866         if (appCtxt.isOffline) {
867             // bug 48014 - add "Use this persona" section for offline
868             if(account.type == ZmAccount.TYPE_PERSONA) {
869                 this._currentSection = ZmAccountsPage.SECTIONS["PERSONA"];
870                 this._setPersona(account, this._currentSection);
871             } else {
872                 this._currentSection = ZmAccountsPage.SECTIONS["PRIMARY"];
873                 this._setZimbraAccount(account, this._currentSection);
874             }
875         } else {
876             switch (account.type) {
877                 case ZmAccount.TYPE_POP:
878                 case ZmAccount.TYPE_IMAP: {
879                     this._currentSection = provider && ZmAccountsPage.SECTIONS[provider.id];
880                     this._currentSection = this._currentSection || ZmAccountsPage.SECTIONS["EXTERNAL"];
881                     this._setExternalAccount(account, this._currentSection);
882                     if (ignoreProvider) {
883                         this._setControlValue("PROVIDER", this._currentSection, "");
884                     }
885                     if (!skipUpdate) {
886                         var password = this._getControlObject("PASSWORD", this._currentSection);
887                         if (password) {
888                             password.setShowPassword(false);
889                         }
890                     }
891                     break;
892                 }
893                 case ZmAccount.TYPE_PERSONA: {
894                     this._currentSection = ZmAccountsPage.SECTIONS["PERSONA"];
895                     this._setPersona(account, this._currentSection);
896                     break;
897                 }
898                 default: {
899                     this._currentSection = ZmAccountsPage.SECTIONS["PRIMARY"];
900                     this._setZimbraAccount(account, this._currentSection);
901                     break;
902                 }
903             }
904         }
905 		if (!this._tabGroup.contains(this._currentSection.tabGroup)) {
906 			this._tabGroup.addMember(this._currentSection.tabGroup);
907 		}
908 	}
909 
910 	// update list cells
911 	this._updateList(account);
912 
913 	var control = this._currentSection && this._currentSection.controls[isExternal ? "EMAIL" : "NAME"];
914 
915 	// When a hidden field is applied focus(), IE throw's an exception.
916 	// Thus checking for isActive()
917 	if (control && this.isActive()) {
918 		control.focus();
919 	}
920 };
921 
922 ZmAccountsPage.prototype.isActive =
923 function() {
924 	return (this._controller.getTabView().getActiveView().toString() == this.toString());
925 };
926 
927 // ZmPreferencesPage methods
928 
929 ZmAccountsPage.prototype.showMe =
930 function() {
931 	var hasRendered = this.hasRendered; // cache before calling base
932 
933 	ZmPreferencesPage.prototype.showMe.apply(this, arguments);
934 
935 	if (!hasRendered) {
936 		this.reset();
937 	}
938 };
939 
940 ZmAccountsPage.prototype.reset =
941 function(useDefaults) {
942 	ZmPreferencesPage.prototype.reset.apply(this, arguments);
943 
944 	// clear current list of accounts
945 	this._accounts.removeAll();
946 	this._deletedAccounts = [];
947 	this._currentAccount = null;
948 	this._currentSection = null;
949 
950 	var usedIdentities = {};
951 
952 	// add zimbra accounts (i.e. family mboxes)
953 	var mboxes = appCtxt.accountList.getAccounts();
954 	var active = appCtxt.getActiveAccount();
955 	for (var j in mboxes) {
956 		var acct = mboxes[j];
957 		var ident = acct.getIdentity();
958 		// NOTE: We create proxies of all of the account objects so that we can
959 		//       store temporary values while editing.
960 		if (active.isMain || acct == active) {
961 			this._accounts.add(ZmAccountsPage.__createProxy(acct));
962 			if (ident) usedIdentities[ident.id] = true;
963 		}
964 	}
965 	// add data sources unless we're in offline mode
966 	if (!appCtxt.isOffline && active.isMain) {
967 		var dataSourceCollection = appCtxt.getDataSourceCollection();
968 		var dataSources = dataSourceCollection.getItems(); // TODO: rename getItems or rename getIdentities
969 		for (var i = 0; i < dataSources.length; i++) {
970 			var datasource = dataSources[i];
971 			var ident = datasource.getIdentity();
972 			delete datasource.password;
973 			this._accounts.add(ZmAccountsPage.__createProxy(datasource));
974 			if (ident) usedIdentities[ident.id] = true;
975 		}
976 	}
977 
978 	// add identities/personas
979 	var identityCollection = appCtxt.getIdentityCollection();
980 	var identities = identityCollection.getIdentities();
981 	for (var i = 0; i < identities.length; i++) {
982 		var identity = identities[i];
983 		if (identity.isDefault || identity.isFromDataSource) continue;
984 		var id = identity.id;
985 		if (usedIdentities[id]) continue;
986 		usedIdentities[id] = true;
987 		var persona = new ZmPersona(identity);
988 		this._accounts.add(ZmAccountsPage.__createProxy(persona), null, true);
989 	}
990     var signatureLinkElement = Dwt.getElement(this._htmlElId + "_External_Signatures_Link");
991     Dwt.setHandler(signatureLinkElement, DwtEvent.ONCLICK, function(){skin.gotoPrefs("SIGNATURES")});
992 	// initialize list view
993 	this._accounts.sort(ZmAccountsPage.__ACCOUNT_COMPARATOR);
994 	var account = this._accounts.get(0);
995 	this._resetAccountListView(account);
996 	this.setAccount(account);
997 	this.setAccountSecurity();
998     this.setAccountDelegates();
999 };
1000 
1001 // saving
1002 
1003 ZmAccountsPage.prototype.isDirty =
1004 function() {
1005 	// make sure that the current object proxy is up-to-date
1006 	this._setAccountFields(this._currentAccount, this._currentSection, true);
1007 
1008 	var printAcct = function(acct) {
1009 		if (AjxUtil.isArray(acct)) {
1010 			return AjxUtil.map(acct, printAcct).join("\n");
1011 		}
1012 		return ["name: ",acct.name,", id: ",acct.id].join("");
1013 	}
1014 
1015 	var dirty = this._deletedAccounts.length > 0;
1016 	if (dirty) {
1017 		AjxDebug.println(AjxDebug.PREFS, "Dirty preferences:\n" + "Deleted accounts:\n" + printAcct(this._deletedAccounts));
1018 	}
1019 	if (!dirty) {
1020 		var accounts = this._accounts.getArray();
1021 		var dirtyAccounts = [];
1022 		for (var i = 0; i < accounts.length; i++) {
1023 			var account = accounts[i];
1024 			if (account._new || account._dirty || account._visibleDirty) {
1025 				dirty = true;
1026 				dirtyAccounts.push(account);
1027 			}
1028 		}
1029 		if (dirty) {
1030 			AjxDebug.println(AjxDebug.PREFS, "Dirty preferences:\n" + "Dirty accounts:\n" + printAcct(dirtyAccounts));
1031 		}
1032 	}
1033 	return dirty;
1034 };
1035 
1036 /**
1037  * Does minimal checking:
1038  * <li>bug 21104: persona name and the associated display value for the From address
1039  * <li>bug 950: email addresses
1040  */
1041 ZmAccountsPage.prototype.validate =
1042 function() {
1043 	var accounts = this._accounts.getArray();
1044 	for (var i = 0; i < accounts.length; i++) {
1045 		var account = accounts[i];
1046 		var type = account.type;
1047 		var isPrimary = type == ZmAccount.TYPE_ZIMBRA;
1048 		var isExternal = type == ZmAccount.TYPE_POP || type == ZmAccount.TYPE_IMAP;
1049 		var isPersona = type == ZmAccount.TYPE_PERSONA;
1050 
1051 		// bug 21104
1052 		if (isPersona && (account._new || account._dirty) && 
1053 			!(account.identity && account.identity.name)) {
1054 			this._errorMsg = ZmMsg.invalidPersonaName;
1055 			return false;
1056 		}
1057 		// bug 950
1058 		if (isExternal && !this.__validateEmail(this.__getAccountValue(account, "EMAIL"))) {
1059 			return false;
1060 		}
1061 		if (this.__getIdentityValue(account, "REPLY_TO") &&
1062 			!this.__validateEmail(this.__getIdentityValue(account, "REPLY_TO_EMAIL"))) {
1063 			return false;
1064 		}
1065 		if (isExternal && !this.__validateEmail(this.__getIdentityValue(account, "FROM_EMAIL"))) {
1066 			return false;
1067 		}
1068 		if (isPersona && this.__getIdentityValue(account, "WHEN_SENT_TO") &&
1069 			!this.__validateEmailList(this.__getIdentityValue(account, "WHEN_SENT_TO_LIST"))) {
1070 			return false;
1071 		}
1072 		if ( AjxUtil.isEmpty(this.__getIdentityValue(account, "FROM_NAME")) &&
1073 		    !AjxUtil.isEmpty(appCtxt.get(ZmSetting.DISPLAY_NAME))) {
1074 			this._errorMsg = ZmMsg.missingEmailDisplayName;
1075 			return false;
1076 		}
1077 	}
1078 	return true;
1079 };
1080 
1081 ZmAccountsPage.prototype.__getAccountValue =
1082 function(account, id) {
1083 	var prop = ZmAccountsPage.ACCOUNT_PROPS[id];
1084 	if (!prop) return;
1085 	return (typeof prop == "string") ? account[prop] : account[prop.getter]();
1086 };
1087 
1088 ZmAccountsPage.prototype.__getIdentityValue =
1089 function(account, id) {
1090 	var prop = ZmAccountsPage.IDENTITY_PROPS[id];
1091 	if (!prop) return;
1092 	var identity = account.getIdentity();
1093 	return identity && (typeof prop == "string" ? identity[prop] : identity[prop]());
1094 };
1095 
1096 ZmAccountsPage.prototype.__validateEmail =
1097 function(s) {
1098 	if (!ZmPref.validateEmail(s)) {
1099 		this._errorMsg = AjxStringUtil.htmlEncode(AjxMessageFormat.format(ZmMsg.invalidEmail, [s]));
1100 		return false;
1101 	}
1102 	return true;
1103 };
1104 
1105 ZmAccountsPage.prototype.__validateEmailList =
1106 function(l) {
1107 	var ss = String(l).split(/[,;]/);
1108 	for (var i = 0; i < ss.length; i++) {
1109 		var valid = this.__validateEmail(ss[i]);
1110 		if (!valid) return false;
1111 	}
1112 	return true;
1113 };
1114 
1115 ZmAccountsPage.prototype.getErrorMessage =
1116 function() {
1117 	return this._errorMsg;
1118 };
1119 
1120 ZmAccountsPage.prototype.getPreSaveCallback =
1121 function() {
1122 	return new AjxCallback(this, this._preSave);
1123 };
1124 
1125 ZmAccountsPage.prototype.getPostSaveCallback =
1126 function() {
1127 	return new AjxCallback(this, this._postSave);
1128 };
1129 
1130 ZmAccountsPage.prototype.addCommand =
1131 function(batchCmd) {
1132 	// make sure that the current object proxy is up-to-date
1133 	// NOTE: This is already done so don't do it again or else we'll
1134 	// NOTE: lose the folderId from the create/rename folder op.
1135 	//this._setAccountFields(this._currentAccount, this._currentSection);
1136 
1137 	// delete accounts
1138 	for (var i = 0; i < this._deletedAccounts.length; i++) {
1139 		var callback = null;
1140 		var account = this._deletedAccounts[i];
1141 		var folderId = account.folderId;
1142 		if (folderId == ZmAccountsPage.DOWNLOAD_TO_FOLDER || folderId != ZmAccountsPage.DOWNLOAD_TO_INBOX) {
1143 			var root = appCtxt.getById(ZmOrganizer.ID_ROOT);
1144 			var name = account.getName();
1145 			var folder = root.getByName(name);
1146 			if (folder && !folder.isSystem()) {
1147 				callback = new AjxCallback(this, this._promptToDeleteFolder, [folder]);
1148 			}
1149 		}
1150 		this._deletedAccounts[i].doDelete(callback, null, batchCmd);
1151 	}
1152 
1153 	// for multi-account mbox, check if user changed visible flag on subaccounts
1154 	if (appCtxt.accountList.size(true) > 1) {
1155 		this._saveVisibleAccounts(batchCmd);
1156 	}
1157 
1158 	// modify existing accounts
1159 	var newAccounts = [];
1160 	var accounts = this._accounts.getArray();
1161 	for (var i = 0; i < accounts.length; i++) {
1162 		var account = accounts[i];
1163 		if (account._new) {
1164 			newAccounts.push(account);
1165 			continue;
1166 		}
1167 
1168 		if (account._dirty) {
1169 			var callback = new AjxCallback(this, this._handleSaveAccount, [account]);
1170 			account.save(callback, null, batchCmd);
1171 		}
1172 	}
1173 
1174 	// add new accounts
1175 	for (var i = 0; i < newAccounts.length; i++) {
1176 		var account = newAccounts[i];
1177 		var callback = new AjxCallback(this, this._handleCreateAccount, [account]);
1178 		account.create(callback, null, batchCmd);
1179 	}
1180 
1181 	// refresh display after all is done
1182 	var soapDoc = AjxSoapDoc.create("NoOpRequest", "urn:zimbraMail");
1183 	var callback = new AjxCallback(this, this.reset);
1184 	batchCmd.addNewRequestParams(soapDoc, callback);
1185 };
1186 
1187 //
1188 // Protected methods
1189 //
1190 
1191 ZmAccountsPage.prototype._testAccounts =
1192 function(accounts, okCallback, cancelCallback) {
1193 	this._controller.getTestDialog().popup(accounts, okCallback, cancelCallback);
1194 };
1195 
1196 // set controls based on account
1197 
1198 ZmAccountsPage.prototype._setZimbraAccount =
1199 function(account, section) {
1200 	this._setGenericFields(account, section);
1201 	this._setIdentityFields(account, section);
1202 	if (appCtxt.isFamilyMbox) {
1203 		this._setControlEnabled("VISIBLE", section, !account.isMain);
1204 	}
1205 };
1206 
1207 ZmAccountsPage.prototype._setExternalAccount =
1208 function(account, section) {
1209 	this._setGenericFields(account, section);
1210 	this._setDataSourceFields(account, section);
1211 	this._setIdentityFields(account, section);
1212 	if (this._setControlVisible("ALERT", section, !account.isStatusOk())) {
1213 		var alert = section.controls["ALERT"];
1214 		alert.setStyle(DwtAlert.CRITICAL);
1215 		alert.setTitle(account.failingSince ? ZmMsg.dataSourceFailureTitle : ZmMsg.accountInactiveTitle);
1216 		alert.setContent(account.lastError || ZmMsg.accountInactiveContent);
1217 	}
1218 };
1219 
1220 ZmAccountsPage.prototype._setPersona =
1221 function(account, section) {
1222 	this._setGenericFields(account, section);
1223 	this._setIdentityFields(account, section);
1224 };
1225 
1226 ZmAccountsPage.prototype._setGenericFields =
1227 function(account, section) {
1228 	this._setControlValue("NAME", section, account.getName());
1229 	this._setControlValue("HEADER", section, account.getName());
1230 	this._setControlValue("EMAIL", section, account.getEmail());
1231 	this._setControlValue("VISIBLE", section, account.visible);
1232 };
1233 
1234 ZmAccountsPage.prototype._setDataSourceFields =
1235 function(account, section) {
1236 	var isSsl = account.connectionType == ZmDataSource.CONNECT_SSL;
1237 	var isInbox = account.folderId == ZmOrganizer.ID_INBOX;
1238 	var isPortChanged = account.port != account.getDefaultPort();
1239     var isSmtpEnabled = account.smtpEnabled;
1240 
1241 	this._setControlValue("ACCOUNT_TYPE", section, account.type);
1242 	this._setControlEnabled("ACCOUNT_TYPE", section, account._new);
1243 	this._setControlValue("USERNAME", section, account.userName);
1244 	this._setControlValue("HOST", section, account.mailServer);
1245 	this._setControlValue("PASSWORD", section, account.password);
1246 	this._setControlValue("SSL", section, isSsl);
1247 	this._setControlEnabled("TEST", section, true);
1248 	this._setDownloadToFolder(account);
1249 	this._setControlValue("DELETE_AFTER_DOWNLOAD", section, account.leaveOnServer);
1250 	this._setControlValue("CHANGE_PORT", section, isPortChanged);
1251 	this._setControlEnabled("PORT", section, isPortChanged);
1252 	this._setPortControls(account.type, account.connectionType, account.port);
1253 
1254 	var provider = account.getProvider();
1255 	this._setControlValue("PROVIDER", section, provider ? provider.id : "");
1256 	this._setControlVisible("PROVIDER", section, AjxUtil.keys(ZmDataSource.getProviders()).length > 0);
1257     this._setExternalSectionControlsView(section, !isSmtpEnabled);
1258 };
1259 
1260 ZmAccountsPage.prototype._setDownloadToFolder =
1261 function(account) {
1262 	var section = this._currentSection;
1263 	var radioGroup = section.controls["DOWNLOAD_TO"];
1264 	if (!radioGroup) return;
1265 
1266 	var pref = ZmAccountsPage.PREFS["DOWNLOAD_TO"];
1267 	var options = pref.options;
1268 	var displayOptions = pref.displayOptions;
1269 	var pattern = displayOptions[options[0] == ZmAccountsPage.DOWNLOAD_TO_INBOX ? 1 : 0];
1270 	var name = AjxStringUtil.htmlEncode(this._getControlValue("NAME", section));
1271 	var text = AjxMessageFormat.format(pattern, name);
1272 
1273 	var radioButton = radioGroup.getRadioButtonByValue(ZmAccountsPage.DOWNLOAD_TO_FOLDER);
1274 	radioButton.setText(text);
1275 
1276 	var isImap = account.type == ZmAccount.TYPE_IMAP;
1277 	var isInbox = !isImap && account.folderId == ZmOrganizer.ID_INBOX;
1278 	var value = isInbox ? ZmAccountsPage.DOWNLOAD_TO_INBOX : ZmAccountsPage.DOWNLOAD_TO_FOLDER;
1279 	this._setControlValue("DOWNLOAD_TO", section, value);
1280 	this._setControlEnabled("DOWNLOAD_TO", section, !isImap);
1281 };
1282 
1283 ZmAccountsPage.prototype._setPortControls =
1284 function(accountType, connectionType, accountPort) {
1285 	var isPop = accountType == ZmAccount.TYPE_POP;
1286 	var isSsl = connectionType == ZmDataSource.CONNECT_SSL;
1287 
1288 	var section = this._currentSection;
1289 	this._setControlValue("PORT", section, accountPort);
1290 	this._setControlEnabled("DELETE_AFTER_DOWNLOAD", section, isPop);
1291 
1292 	this._setControlEnabled("DOWNLOAD_TO", section, isPop);
1293 	// imap is never allowed in inbox
1294 	if (!isPop) {
1295 		this._setControlValue("DOWNLOAD_TO", section, ZmAccountsPage.DOWNLOAD_TO_FOLDER);
1296 	}
1297 
1298 	var portTypeLabel = AjxMessageFormat.format(ZmAccountsPage.PREFS["CHANGE_PORT"].displayName, accountType);
1299 	this._setControlLabel("CHANGE_PORT", section, portTypeLabel);
1300 
1301 	var defaultPort = isPop ? ZmPopAccount.PORT_CLEAR : ZmImapAccount.PORT_CLEAR;
1302 	if (isSsl) {
1303 		defaultPort = isPop ? ZmPopAccount.PORT_SSL : ZmImapAccount.PORT_SSL;
1304 	}
1305 	var defaultPortLabel = AjxMessageFormat.format(ZmAccountsPage.PREFS["PORT_DEFAULT"].displayName, defaultPort);
1306 	this._setControlLabel("PORT_DEFAULT", section, defaultPortLabel);
1307 };
1308 
1309 ZmAccountsPage.prototype._setIdentityFields =
1310 function(account, section) {
1311 	var identity = account.getIdentity();
1312 
1313 	this._setControlValue("FROM_NAME", section, identity.sendFromDisplay);
1314     this._setControlValue("FROM_EMAIL", section, (identity.sendFromAddressType == ZmSetting.SEND_ON_BEHALF_OF) ? ZmMsg.onBehalfOfMidLabel + " " + identity.sendFromAddress : identity.sendFromAddress);
1315     this._setControlValue("FROM_EMAIL_TYPE", section, identity.sendFromAddressType);
1316     this._setControlValue("REPLY_TO", section, identity.setReplyTo);
1317 	this._setControlValue("REPLY_TO_NAME", section, identity.setReplyToDisplay);
1318 	this._setControlValue("REPLY_TO_EMAIL", section, identity.setReplyToAddress);
1319 	this._setControlValue("READ_RECEIPT_TO_ADDR", section, identity.readReceiptAddr);
1320 	this._setControlValue("WHEN_SENT_TO", section, identity.useWhenSentTo);
1321 	this._setControlValue("WHEN_SENT_TO_LIST", section, identity.whenSentToAddresses);
1322 	this._setControlValue("WHEN_IN_FOLDER", section, identity.useWhenInFolder);
1323 	this._setControlValue("WHEN_IN_FOLDER_LIST", section, identity.whenInFolderIds);
1324 
1325 	this._setReplyToControls();
1326 	this._setWhenSentToControls();
1327 	this._setWhenInFolderControls();
1328 };
1329 
1330 ZmAccountsPage.prototype._saveVisibleAccounts =
1331 function(batchCmd) {
1332 	var accounts = this._accounts.getArray();
1333 	var visibilityChanged = false;
1334 
1335 	// check if visibility changed for any sub accounts
1336 	for (var i = 0; i < accounts.length; i++) {
1337 		if (accounts[i]._visibleDirty) {
1338 			visibilityChanged = true;
1339 			break;
1340 		}
1341 	}
1342 
1343 	// collect *all* visible accounts for ModifyPrefsRequest and add to batchCmd
1344 	if (visibilityChanged) {
1345 		var soapDoc = AjxSoapDoc.create("ModifyPrefsRequest", "urn:zimbraAccount");
1346 		var setting = appCtxt.getSettings().getSetting(ZmSetting.CHILD_ACCTS_VISIBLE);
1347 		var foundVisible = false;
1348 		for (var j = 0; j < accounts.length; j++) {
1349 			var account = accounts[j];
1350 			if (!account.isMain && account.visible) {
1351 				var node = soapDoc.set("pref", account.id);
1352 				node.setAttribute("name", setting.name);
1353 				foundVisible = true;
1354 			}
1355 		}
1356 		// user unset visible for all accounts - send empty value
1357 		if (!foundVisible) {
1358 			var node = soapDoc.set("pref", "");
1359 			node.setAttribute("name", setting.name);
1360 		}
1361 		var callback = new AjxCallback(this, this._handleSaveVisibleAccount);
1362 		batchCmd.addNewRequestParams(soapDoc, callback);
1363 	}
1364 };
1365 
1366 ZmAccountsPage.prototype._setReplyToControls =
1367 function() {
1368 	var section = this._currentSection;
1369 	var replyTo = this._getControlValue("REPLY_TO", section);
1370 
1371 	this._setControlEnabled("REPLY_TO_NAME", section, replyTo);
1372 	this._setControlEnabled("REPLY_TO_EMAIL", section, replyTo);
1373 };
1374 
1375 ZmAccountsPage.prototype._setWhenSentToControls =
1376 function() {
1377 	var section = this._currentSection;
1378 	var whenSentTo = this._getControlValue("WHEN_SENT_TO", section);
1379 
1380 	this._setControlEnabled("WHEN_SENT_TO_LIST", section, whenSentTo);
1381 };
1382 
1383 ZmAccountsPage.prototype._setWhenInFolderControls =
1384 function() {
1385 	var section = this._currentSection;
1386 	var whenInFolder = this._getControlValue("WHEN_IN_FOLDER", section);
1387 
1388 	this._setControlEnabled("WHEN_IN_FOLDER_LIST", section, whenInFolder);
1389 	this._setControlEnabled("WHEN_IN_FOLDER_BUTTON", section, whenInFolder);
1390 };
1391 
1392 ZmAccountsPage.prototype._setControlLabel =
1393 function(id, section, value) {
1394 	var control = section.controls[id];
1395 	var setup = ZmAccountsPage.PREFS[id];
1396 	if (!control || !setup) return;
1397 
1398 	switch (setup.displayContainer) {
1399 		case ZmPref.TYPE_STATIC:
1400 		case ZmPref.TYPE_CHECKBOX: {
1401 			control.setText(value);
1402 			break;
1403 		}
1404 	}
1405 };
1406 
1407 ZmAccountsPage.prototype._getControlObject =
1408 function(id, section) {
1409 	return section && section.controls[id];
1410 };
1411 
1412 ZmAccountsPage.prototype._setControlValue =
1413 function(id, section, value) {
1414 	var control = section.controls[id];
1415 	var setup = ZmAccountsPage.PREFS[id];
1416 	if (!setup) return;
1417 	if (!control) {
1418 		setup.value = value;
1419 		return;
1420 	}
1421 
1422 	if (setup.displayFunction) {
1423 		value = setup.displayFunction(value);
1424 	}
1425 	if (id == "DELETE_AFTER_DOWNLOAD") {
1426 		value = !value;
1427 	}
1428 	else if (id == "WHEN_SENT_TO_LIST") {
1429 		value = value ? value.join(", ") : "";
1430 	}
1431 	else if (id == "WHEN_IN_FOLDER_LIST") {
1432 		var tree = appCtxt.getTree(ZmOrganizer.FOLDER);
1433 		var folderIds = value;
1434 		var array = [value];
1435 		var seenComma = false;
1436 		for (var i = 0; i < folderIds.length; i++) {
1437 			var fid = folderIds[i];
1438 			var searchPath = array[i] = tree.getById(fid).getSearchPath();
1439 			seenComma = seenComma || searchPath.match(/,/);
1440 		}
1441 		value = AjxUtil.uniq(array).join(seenComma ? "; " : ", ");
1442 	}
1443 
1444 	switch (setup.displayContainer) {
1445 		case ZmPref.TYPE_STATIC: {
1446 			var message = setup.displayName ? AjxMessageFormat.format(setup.displayName, value) : value;
1447 			control.setText(message);
1448 			break;
1449 		}
1450 		case ZmPref.TYPE_CHECKBOX: {
1451 			control.setSelected(value);
1452 			break;
1453 		}
1454 		case ZmPref.TYPE_INPUT:
1455 		case ZmPref.TYPE_COMBOBOX: {
1456 			control.setValue(value);
1457 			break;
1458 		}
1459 		case ZmPref.TYPE_SELECT:
1460 		case ZmPref.TYPE_RADIO_GROUP: {
1461 			control.setSelectedValue(value, true);
1462 			break;
1463 		}
1464 	}
1465 };
1466 
1467 ZmAccountsPage.prototype._getControlValue =
1468 function(id, section) {
1469 	var control = section.controls[id];
1470 	var setup = ZmAccountsPage.PREFS[id];
1471 	if (!setup) return null;
1472 	if (!control) {
1473 		return setup.value || (id == "DOWNLOAD_TO" && ZmAccountsPage.DOWNLOAD_TO_FOLDER);
1474 	}
1475 
1476 	var value = null;
1477 	if (id == "WHEN_SENT_TO_LIST") {
1478 		var array = AjxEmailAddress.parseEmailString(control.getValue()).all.getArray();
1479 		for (var i = 0; i < array.length; i++) {
1480 			array[i] = array[i].address;
1481 		}
1482 		value = array;
1483 	}
1484 	else if (id == "WHEN_IN_FOLDER_LIST") {
1485 		var tree = appCtxt.getTree(ZmOrganizer.FOLDER);
1486 		var root = tree.getById(ZmOrganizer.ID_ROOT);
1487 
1488 		var folderPaths = control.getValue().replace(/\s*(;|,)\s*/g,"$1").split(/;|,/);
1489 		var array = [];
1490 		for (var i = 0; i < folderPaths.length; i++) {
1491 			var folder = root.getByPath(folderPaths[i],true);
1492 			if (!folder) continue;
1493 			array.push(folder.id);
1494 		}
1495 		value = array;
1496 	}
1497 	else if (id == "DELETE_AFTER_DOWNLOAD") {
1498 		value = !control.isSelected();
1499 	}
1500 	else {
1501 		switch (setup.displayContainer) {
1502 			case ZmPref.TYPE_STATIC: {
1503 				value = control.getText();
1504 				break;
1505 			}
1506 			case ZmPref.TYPE_CHECKBOX: {
1507 				value = control.isSelected();
1508 				if (setup.options) {
1509 					value = setup.options[Number(value)];
1510 				}
1511 				break;
1512 			}
1513 			case ZmPref.TYPE_RADIO_GROUP: {
1514 				value = control.getSelectedValue();
1515 				break;
1516 			}
1517 			case ZmPref.TYPE_INPUT:
1518 			case ZmPref.TYPE_SELECT: {
1519 				value = control.getValue();
1520 				break;
1521 			}
1522 			case ZmPref.TYPE_COMBOBOX: {
1523 				value = control.getValue() || control.getText();
1524 				break;
1525 			}
1526 		}
1527 	}
1528 
1529 	return setup.valueFunction ? setup.valueFunction(value) : value;
1530 };
1531 
1532 ZmAccountsPage.prototype._setControlVisible =
1533 function(id, section, visible) {
1534 	var control = section.controls[id];
1535 	var setup = ZmAccountsPage.PREFS[id];
1536 	if (control) control.setVisible(visible);
1537 	var el = document.getElementById([this._htmlElId, section.id, id, "row"].join("_"));
1538 	if (el) Dwt.setVisible(el, visible);
1539 	return control || el ? visible : false;
1540 };
1541 
1542 ZmAccountsPage.prototype._setControlEnabled =
1543 function(id, section, enabled) {
1544 	var control = section.controls[id];
1545 	var setup = ZmAccountsPage.PREFS[id];
1546 	if (!control || !setup) return;
1547 
1548 	control.setEnabled(enabled);
1549 };
1550 
1551 /**
1552  * If selected datasource has attr smtpEnabled to true, make the external controls
1553  * readOnly else editable.
1554  */
1555 
1556 ZmAccountsPage.prototype._setExternalSectionControlsView =
1557 function(section, toEnable) {
1558     var prefs,
1559         prefsLen,
1560         i,
1561         pref,
1562         signatureLinkElement,
1563         signatureTextSpan;
1564 
1565     prefs = section.prefs; // external sections prefs.
1566     prefsLen = prefs.length;
1567     signatureLinkElement = Dwt.getElement(this._htmlElId + "_External_Signatures_Link");
1568     signatureTextSpan    = Dwt.getElement(this._htmlElId + "_External_Signatures_Text");
1569 
1570     for (i = 0; i < prefsLen; i++) {
1571         pref = prefs[i];
1572         this._setControlEnabled(pref, section, toEnable); // Disable/enable external section pref, depending on the boolean value of attr smtpEnabled in selected Data Source.
1573     }
1574     if (toEnable) {
1575         Dwt.setVisible(signatureLinkElement,true);
1576         Dwt.setVisible(signatureTextSpan,false);
1577     }
1578     else {
1579         Dwt.setVisible(signatureLinkElement,false);
1580         Dwt.setVisible(signatureTextSpan,true);
1581     }
1582 };
1583 
1584 ZmAccountsPage.prototype._setAccountFields =
1585 function(account, section, dontClearFolder) {
1586 	if (!account || !section) return;
1587 
1588 	for (var id in ZmAccountsPage.ACCOUNT_PROPS) {
1589 		var control = section.controls[id];
1590 		if (!control) {
1591 			// HACK: default to new folder if control not available
1592 			if (id == "DOWNLOAD_TO" && !dontClearFolder) {
1593 				account.folderId = ZmAccountsPage.DOWNLOAD_TO_FOLDER; 
1594 			}
1595 			continue;
1596 		}
1597 
1598 		var prop = ZmAccountsPage.ACCOUNT_PROPS[id];
1599 		var isField = AjxUtil.isString(prop);
1600 
1601 		var ovalue = isField ? account[prop] : account[prop.getter]();
1602 		var nvalue = this._getControlValue(id, section);
1603 		if (this._valuesEqual(ovalue, nvalue)) continue;
1604 
1605 		// special case: download-to
1606 		if (id == "DOWNLOAD_TO" &&
1607 		    ovalue != ZmOrganizer.ID_INBOX && nvalue != ZmOrganizer.ID_INBOX) {
1608 			continue;
1609 		}
1610 
1611 		// handling visible is special
1612 		if (id == "VISIBLE") {
1613 			account._visibleDirty = true;
1614 		} else {
1615 			account._dirty = true;
1616 		}
1617 
1618 		if (AjxUtil.isString(prop)) {
1619 			account[prop] = nvalue;
1620 		}
1621 		else {
1622 			account[prop.setter](nvalue);
1623 		}
1624 	}
1625 
1626 	var identity = account.getIdentity();
1627 	for (var id in ZmAccountsPage.IDENTITY_PROPS) {
1628 		var control = section.controls[id];
1629 		if (!control) { continue; }
1630 
1631 		var prop = ZmAccountsPage.IDENTITY_PROPS[id];
1632 		var isField = AjxUtil.isString(prop);
1633 
1634 		var ovalue = (isField ? identity[prop] : identity[prop.getter]())|| "";
1635 		var nvalue = (this._getControlValue(id, section))||"";
1636         if (id == "FROM_EMAIL" && identity.sendFromAddressType == ZmSetting.SEND_ON_BEHALF_OF) ovalue = ZmMsg.sendOnBehalfOf + " " + ovalue;
1637 
1638         if (this._valuesEqual(ovalue, nvalue)) { continue; }
1639 
1640         if (id == "FROM_EMAIL" && nvalue.indexOf(ZmMsg.sendOnBehalfOf + " ") == 0) {
1641             nvalue = nvalue.replace(ZmMsg.sendOnBehalfOf + " ", "");
1642             identity.sendFromAddressType = ZmSetting.SEND_ON_BEHALF_OF;
1643         } else {
1644             identity.sendFromAddressType = ZmSetting.SEND_AS;
1645         }
1646 
1647 		account._dirty = true;
1648 		if (isField) {
1649 			identity[prop] = nvalue;
1650 		}
1651 		else {
1652 			identity[prop.setter](nvalue);
1653 		}
1654 	}
1655 };
1656 
1657 ZmAccountsPage.prototype._valuesEqual =
1658 function(ovalue, nvalue) {
1659 	if (AjxUtil.isArray(ovalue) && AjxUtil.isArray(nvalue)) {
1660 		if (ovalue.length != nvalue.length) {
1661 			return false;
1662 		}
1663 		var oarray = [].concat(ovalue).sort();
1664 		var narray = [].concat(nvalue).sort();
1665 		for (var i = 0; i < oarray.length; i++) {
1666 			if (oarray[i] != narray[i]) {
1667 				return false;
1668 			}
1669 		}
1670 		return true;
1671 	}
1672 	return ovalue == nvalue;
1673 };
1674 
1675 // init ui
1676 
1677 ZmAccountsPage.prototype._initControl =
1678 function(id, setup, value, section) {
1679 	ZmPreferencesPage.prototype._initControl.apply(this, arguments);
1680 	if (id == "PROVIDER" && !setup.options) {
1681 		var providers = AjxUtil.values(ZmDataSource.getProviders());
1682 		providers.sort(ZmAccountsPage.__BY_PROVIDER_NAME);
1683 		providers.unshift( { id: "", name: "Custom" } ); // TODO: i18n
1684 
1685 		var options = new Array(providers.length);
1686 		var displayOptions = new Array(providers.length);
1687 		for (var i = 0; i < providers.length; i++) {
1688 			var provider = providers[i];
1689 			options[i] = provider.id;
1690 			displayOptions[i] = provider.name;
1691 		}
1692 
1693 		setup.options = options;
1694 		setup.displayOptions = displayOptions;
1695 	}
1696 };
1697 
1698 ZmAccountsPage.prototype._setupInput =
1699 function(id, setup, value) {
1700 	if (id == "PASSWORD") {
1701 		var input = new DwtPasswordField({ parent: this });
1702 		input.setValue(value);
1703 		this.setFormObject(id, input);
1704 		return input;
1705 	}
1706 	var input = ZmPreferencesPage.prototype._setupInput.apply(this, arguments);
1707 	switch (id) {
1708 		case "NAME": {
1709 			input.addListener(DwtEvent.ONKEYUP, new AjxListener(this, this._handleNameChange));
1710 			break;
1711 		}
1712 		case "HOST": {
1713 			input.addListener(DwtEvent.ONKEYUP, new AjxListener(this, this._handleHostChange));
1714 			break;
1715 		}
1716 		case "EMAIL": {
1717 			input.addListener(DwtEvent.ONKEYUP, new AjxListener(this, this._handleEmailChange));
1718 			break;
1719 		}
1720 		case "USERNAME": {
1721 			input.addListener(DwtEvent.ONKEYUP, new AjxListener(this, this._handleUserNameChange));
1722 			break;
1723 		}
1724 		case "WHEN_SENT_TO_LIST": {
1725 			input.setHint(appCtxt.get(ZmSetting.USERNAME));
1726 			break;
1727 		}
1728 	}
1729 	return input;
1730 };
1731 
1732 ZmAccountsPage.prototype._setupCheckbox =
1733 function(id, setup, value) {
1734 	var checkbox = ZmPreferencesPage.prototype._setupCheckbox.apply(this, arguments);
1735 	if (id == "SSL") {
1736 		checkbox.addSelectionListener(new AjxListener(this, this._handleTypeOrSslChange));
1737 	}
1738 	else if (id == "CHANGE_PORT") {
1739 		checkbox.addSelectionListener(new AjxListener(this, this._handleChangePort));
1740 	}
1741 	else if (id == "REPLY_TO") {
1742 		checkbox.addSelectionListener(new AjxListener(this, this._handleReplyTo));
1743 	}
1744 	else if (id == "WHEN_SENT_TO") {
1745 		checkbox.addSelectionListener(new AjxListener(this, this._handleWhenSentTo));
1746 	}
1747 	else if (id == "WHEN_IN_FOLDER") {
1748 		checkbox.addSelectionListener(new AjxListener(this, this._handleWhenInFolder));
1749 	}
1750 	return checkbox;
1751 };
1752 
1753 ZmAccountsPage.prototype._setupRadioGroup =
1754 function(id, setup, value) {
1755 	var container = ZmPreferencesPage.prototype._setupRadioGroup.apply(this, arguments);
1756 	if (id == "ACCOUNT_TYPE") {
1757 		var radioGroup = this.getFormObject("ACCOUNT_TYPE");
1758 		radioGroup.addSelectionListener(new AjxListener(this, this._handleTypeChange));
1759 	}
1760 	return container;
1761 };
1762 
1763 ZmAccountsPage.prototype._setSelectFromLabels =
1764 function( displayOptions, fromAddress){
1765  if (!this._selectFromLabels){
1766     var tmpArray1 = [];
1767     var tmpArray2 = [];
1768     var email  = null;
1769     var index = -1;
1770      // Add sendOnBehalfOf emails
1771     for (var i=0;i < appCtxt.sendOboEmails.length; i++){
1772       email = appCtxt.sendOboEmails[i].addr;
1773       index = -1;
1774       if (index = AjxUtil.indexOf(fromAddress,email) != -1)
1775           fromAddress.splice(index, 1);
1776       if (index = AjxUtil.indexOf(displayOptions,email) != -1)
1777            displayOptions.splice(index, 1);
1778       tmpArray2.push({label:(ZmMsg.sendOnBehalfOf + " " + email), value:(ZmMsg.sendOnBehalfOf + " " + email)});
1779     }
1780 
1781     // Add sendAs emails
1782     for (var i=0;i < appCtxt.sendAsEmails.length; i++){
1783       email = appCtxt.sendAsEmails[i].addr;
1784       index = -1;
1785       if ((index = AjxUtil.indexOf(fromAddress,email)) != -1)
1786           fromAddress.splice(index, 1);
1787       if ((index = AjxUtil.indexOf(displayOptions,email)) != -1)
1788            displayOptions.splice(index, 1);
1789       tmpArray2.push({label:email, value:email});
1790     }
1791 
1792 
1793     if (fromAddress && fromAddress.length)
1794         fromAddress = fromAddress.sort();
1795 
1796     if (displayOptions && displayOptions.length)
1797         displayOptions = displayOptions.sort();
1798 
1799     displayOptions = AjxUtil.mergeArrays(displayOptions, fromAddress);
1800 
1801     var length = displayOptions.length;
1802     for (var i=0;i<length; i++){
1803         tmpArray1.push({label:displayOptions[i], value:displayOptions[i]});
1804     }
1805     this._selectFromLabels =  tmpArray1.concat(tmpArray2);
1806  }
1807  return this._selectFromLabels;
1808 };
1809 
1810 
1811 ZmAccountsPage.prototype._setupSelect =
1812 function(id, setup, value) {
1813 	var select;
1814 	if (id == "FROM_EMAIL") {
1815 		setup.displayOptions = this._getAllAddresses();
1816 		var fromAddress = appCtxt.get(ZmSetting.MAIL_FROM_ADDRESS);
1817         setup.options = this._setSelectFromLabels(setup.displayOptions, fromAddress );
1818         setup.choices = setup.options;
1819 		if (appCtxt.get(ZmSetting.ALLOW_ANY_FROM_ADDRESS)) {
1820 			select = this._setupComboBox(id, setup, value);
1821 			// By setting the setSelectedValue method on the combox
1822 			// box, it fakes the setter method of a DwtSelect.
1823 			select.setSelectedValue = select.setValue;
1824 			// NOTE: For this control, we always want the text value 
1825 			select.getValue = select.getText;
1826 		}
1827 	}
1828 	else if (setup.displayOptions && setup.displayOptions.length < 2) {
1829 		select = this._setupInput(id, setup, value);
1830 		select.setEnabled(false);
1831 		select.setSelectedValue = select.setValue;
1832 	}
1833 	if (!select) {
1834 		select = ZmPreferencesPage.prototype._setupSelect.apply(this, arguments);
1835 	}
1836 	if (id == "PROVIDER") {
1837 		select.addChangeListener(new AjxListener(this, this._handleProviderChange));
1838 	}
1839 	return select;
1840 };
1841 
1842 ZmAccountsPage.__BY_PROVIDER_NAME = function(a, b) {
1843 	if (a.name.match(/^zimbra/i)) return -1;
1844 	if (b.name.match(/^zimbra/i)) return  1;
1845 	if (a.name.match(/^yahoo/i)) return -1;
1846 	if (b.name.match(/^yahoo/i)) return  1;
1847 	return a.name.localeCompare(b.name);
1848 };
1849 
1850 ZmAccountsPage.prototype._setupComboBox =
1851 function(id, setup, value) {
1852 	if (id == "REPLY_TO_EMAIL") {
1853 		var addresses = this._getAllAddresses();
1854 		var accounts = [].concat(appCtxt.getDataSourceCollection().getImapAccounts(), appCtxt.getDataSourceCollection().getPopAccounts());
1855 		addresses = this._getAddressesFromAccounts(accounts, addresses, true, true);
1856 		setup.displayOptions = addresses;
1857 	}
1858 	return ZmPreferencesPage.prototype._setupComboBox.apply(this, arguments);
1859 };
1860 
1861 ZmAccountsPage.prototype._updateComboBox =
1862 function(id, extras) {
1863 	var dwtElement = this.getFormObject(id);
1864 	if (dwtElement && AjxUtil.isFunction(dwtElement.removeAll) && AjxUtil.isFunction(dwtElement.add)) {
1865 		if (id == "REPLY_TO_EMAIL") {
1866 			if (!AjxUtil.isArray(extras))
1867 				extras = AjxUtil.isString(extras) ? [extras] : [];
1868 
1869 			var addresses = this._getAllAddresses().concat(extras);
1870 			var accounts = this._accounts.getArray();
1871 			addresses = this._getAddressesFromAccounts(accounts, addresses, true, true);
1872 				    
1873 			dwtElement.removeAll();
1874 			for (var i=0; i<addresses.length; i++) {
1875 				dwtElement.add(addresses[i], addresses[i], false);
1876 			}
1877 		}
1878 	}
1879 };
1880 
1881 
1882 
1883 
1884 
1885 ZmAccountsPage.prototype._setupCustom =
1886 function(id, setup, value) {
1887 	if (id == ZmSetting.ACCOUNTS) {
1888 		// setup list
1889 		var listView = this._accountListView = new ZmAccountsListView(this);
1890 		listView.addSelectionListener(new AjxListener(this, this._handleAccountSelection));
1891 		this.setFormObject(id, listView);
1892 
1893 		// setup buttons
1894 		this._setupButtons();
1895 
1896 		// setup account sections
1897 		this._setupPrimaryDiv();
1898 		this._setupExternalDiv();
1899 		this._setupPersonaDiv();
1900 
1901 		// initialize list
1902 		this._resetAccountListView();
1903 
1904 		return listView;
1905 	}
1906 	if (id == "TEST") {
1907 		var button = new DwtButton({parent:this});
1908 		button.setText(setup.displayName);
1909 		button.addSelectionListener(new AjxListener(this, this._handleTestButton));
1910 		return button;
1911 	}
1912 	if (id == "WHEN_IN_FOLDER_BUTTON") {
1913 		var button = new DwtButton({parent:this});
1914 		button.setImage("SearchFolder");
1915 		button.addSelectionListener(new AjxListener(this, this._handleFolderButton));
1916 		return button;
1917 	}
1918 	if (id == "ALERT") {
1919 		return new DwtAlert(this);
1920 	}
1921 
1922 	return ZmPreferencesPage.prototype._setupCustom.apply(this, arguments);
1923 };
1924 
1925 ZmAccountsPage.prototype._getAllAddresses =
1926 function() {
1927 	var username = appCtxt.get(ZmSetting.USERNAME);
1928 	var addresses = appCtxt.get(ZmSetting.ALLOW_FROM_ADDRESSES);
1929 	var aliases = appCtxt.get(ZmSetting.MAIL_ALIASES);
1930 	return [].concat(username, addresses, aliases);
1931 };
1932 
1933 /*
1934  * Takes a list of accounts and extracts their email addresses
1935  * @param accounts	array of account objects
1936  * @param unique	boolean: if true, addresses will be included in output only if they are not already present. Defaults to true
1937  * @param valid		boolean: if true, performs a validation check on the address and only includes it if it passes. Defaults to true
1938  * @param addresses	optional array of addresses (as strings) to append to. Defaults to an empty array
1939 */
1940 ZmAccountsPage.prototype._getAddressesFromAccounts = function(accounts, addresses, unique, valid) {
1941 	if (!AjxUtil.isArray(addresses))
1942 		addresses = [];
1943 	for (var i=0; i<accounts.length; i++) {
1944 		var account = accounts[i];
1945 		if (account.isMain || account.enabled) {
1946 			var address = account.getEmail();
1947 			if (!AjxUtil.isEmpty(address) && (!valid || AjxEmailAddress.isValid(address)) && (!unique || AjxUtil.indexOf(addresses, address, false) == -1)) // Make sure we are not adding an empty address and that we are not adding the address twice
1948 				addresses.push(address);
1949 		}
1950 	}
1951 	return addresses;
1952 };
1953 
1954 ZmAccountsPage.prototype._resetAccountListView =
1955 function(accountOrIndex) {
1956 	var accounts = this._accounts.clone();
1957 	var count = accounts.size();
1958 	// NOTE: We go backwards so we don't have to adjust index when we remove an item.
1959 	for (var i = count - 1; i >= 0; i--) {
1960 		var account = accounts.get(i);
1961 		if (account.type == ZmAccount.TYPE_ZIMBRA && !account.isMain && !account.visible) {
1962 			accounts.removeAt(i);
1963 		}
1964 	}
1965 	this._accountListView.set(accounts);
1966 	var account = accountOrIndex;
1967 	if (AjxUtil.isNumber(account)) {
1968 		var index = accountOrIndex;
1969 		var list = this._accountListView.getList();
1970 		var size = list.size();
1971 		if (accountOrIndex >= size) {
1972 			index = size - 1;
1973 		}
1974 		account = list.get(index);
1975 	}
1976 	this._accountListView.setSelection(account || appCtxt.accountList.mainAccount);
1977 	this._updateReplyToEmail();
1978 };
1979 
1980 // account buttons
1981 
1982 ZmAccountsPage.prototype._setupButtons =
1983 function() {
1984 	var deleteButtonDiv = document.getElementById(this._htmlElId+"_DELETE");
1985 	if (deleteButtonDiv) {
1986 		var button = new DwtButton({parent:this});
1987 		button.setText(ZmMsg.del);
1988 		button.setEnabled(false);
1989 		button.addSelectionListener(new AjxListener(this, this._handleDeleteButton));
1990 		this._replaceControlElement(deleteButtonDiv, button);
1991 		this._deleteButton = button;
1992 	}
1993 
1994 	var addExternalButtonDiv = document.getElementById(this._htmlElId+"_ADD_EXTERNAL");
1995 	if (addExternalButtonDiv) {
1996 		var button = new DwtButton({parent:this});
1997 		button.setText(ZmMsg.addExternalAccount);
1998 		button.addSelectionListener(new AjxListener(this, this._handleAddExternalButton));
1999 		this._replaceControlElement(addExternalButtonDiv, button);
2000 		this._addExternalButton = button;
2001 	}
2002 
2003 	var addPersonaButtonDiv = document.getElementById(this._htmlElId+"_ADD_PERSONA");
2004 	if (addPersonaButtonDiv) {
2005 		var button = new DwtButton({parent:this});
2006 		button.setText(ZmMsg.addPersona);
2007 		button.addSelectionListener(new AjxListener(this, this._handleAddPersonaButton));
2008 		this._replaceControlElement(addPersonaButtonDiv, button);
2009 		this._addPersonaButton = button;
2010 	}
2011 };
2012 
2013 // account sections
2014 
2015 
2016 ZmAccountsPage.prototype._getSectionDiv =
2017 function(account) {
2018 	return appCtxt.isOffline
2019 		? ((account.type == ZmAccount.TYPE_PERSONA) ? this._sectionDivs[account.type] : this._sectionDivs[ZmAccount.TYPE_ZIMBRA] )
2020 		: this._sectionDivs[account.type];
2021 };
2022 
2023 ZmAccountsPage.prototype._setupPrimaryDiv =
2024 function() {
2025 	var div = document.getElementById(this._htmlElId+"_PRIMARY");
2026 	if (div) {
2027 		this._sectionDivs[ZmAccount.TYPE_ZIMBRA] = div;
2028 		this._createSection("PRIMARY", div);
2029 	}
2030 };
2031 
2032 ZmAccountsPage.prototype._setupExternalDiv =
2033 function() {
2034 	// setup generic external account div
2035 	var div = document.getElementById(this._htmlElId+"_EXTERNAL");
2036 	if (div) {
2037 		this._sectionDivs[ZmAccount.TYPE_POP] = div;
2038 		this._sectionDivs[ZmAccount.TYPE_IMAP] = div;
2039 		this._createSection("EXTERNAL", div);
2040 	}
2041 
2042 	// setup divs for specific providers
2043 	var providers = ZmDataSource.getProviders();
2044 	for (var id in providers) {
2045 		var div = document.getElementById([this._htmlElId, id].join("_"));
2046 		if (!div) continue;
2047 		this._sectionDivs[id] = div;
2048 		this._createSection(id, div);
2049 	}
2050 };
2051 
2052 ZmAccountsPage.prototype._setupPersonaDiv =
2053 function() {
2054 	var div = document.getElementById(this._htmlElId+"_PERSONA");
2055 	if (div) {
2056 		this._sectionDivs[ZmAccount.TYPE_PERSONA] = div;
2057 		this._createSection("PERSONA", div);
2058 	}
2059 };
2060 
2061 ZmAccountsPage.prototype._createSection =
2062 function(name, sectionDiv) {
2063 	var section = ZmAccountsPage.SECTIONS[name];
2064 	var prefIds = section && section.prefs;
2065 	if (!prefIds) return;
2066 
2067 	this._enterTabScope();
2068 	try {
2069 		this._addTabLinks(sectionDiv);
2070 
2071 		section.controls = {};
2072 
2073 		var prefs = ZmAccountsPage.PREFS;
2074 		for (var i = 0; i < prefIds.length; i++) {
2075 			var id = prefIds[i];
2076 			var setup = prefs[id];
2077 			if (!setup) continue;
2078 
2079 			var containerId = [this._htmlElId, name, id].join("_");
2080 			var containerEl = document.getElementById(containerId);
2081 			if (!containerEl) continue;
2082 
2083 			this._initControl(id, setup, value, name);
2084 
2085 			var type = setup.displayContainer;
2086 			var value = null;
2087 			var control;
2088 			switch (type) {
2089 				case ZmPref.TYPE_STATIC: {
2090 					control = this._setupStatic(id, setup, value);
2091 					break;
2092 				}
2093 				case ZmPref.TYPE_INPUT: {
2094 					control = this._setupInput(id, setup, value);
2095 					break;
2096 				}
2097 				case ZmPref.TYPE_SELECT: {
2098 					control = this._setupSelect(id, setup, value);
2099 					break;
2100 				}
2101 				case ZmPref.TYPE_COMBOBOX: {
2102 					control = this._setupComboBox(id, setup, value);
2103 					break;
2104 				}
2105 				case ZmPref.TYPE_CHECKBOX: {
2106 					control = this._setupCheckbox(id, setup, value);
2107 					break;
2108 				}
2109 				case ZmPref.TYPE_RADIO_GROUP: {
2110 					control = this._setupRadioGroup(id, setup, value);
2111 					break;
2112 				}
2113 				case ZmPref.TYPE_CUSTOM: {
2114 					control = this._setupCustom(id, setup, value);
2115 					break;
2116 				}
2117 				default: continue;
2118 			}
2119 
2120 			if (control) {
2121 				if (name == "PRIMARY" && id == "EMAIL") {
2122 					control.setEnabled(false);
2123 				}
2124 				this._replaceControlElement(containerEl, control);
2125 				if (type == ZmPref.TYPE_RADIO_GROUP) {
2126 					control = this.getFormObject(id);
2127 				}
2128 				section.controls[id] = control;
2129 			}
2130 		}
2131 
2132 		section.tabGroup = new DwtTabGroup(name);
2133 		this._addControlsToTabGroup(section.tabGroup);
2134 	}
2135 	finally {
2136 		this._exitTabScope();
2137 	}
2138 };
2139 
2140 // listeners
2141 
2142 ZmAccountsPage.prototype._handleAccountSelection =
2143 function(evt) {
2144 	var account = this._accountListView.getSelection()[0];
2145 	this.setAccount(account);
2146 };
2147 
2148 ZmAccountsPage.prototype._handleDeleteButton =
2149 function(evt) {
2150 	var account = this._accountListView.getSelection()[0];
2151 	if (!account._new) {
2152 		account._deleted = true;
2153 		this._deletedAccounts.push(account);
2154 	}
2155 	var index = this._accountListView.getItemIndex(account);
2156     if (account.type == ZmAccount.TYPE_PERSONA) {
2157         var personaList = ZmNewPersona.getPersonaList(this._accountListView.getList().getArray());
2158         var personaListLength = personaList.length;
2159 
2160         // If there's only one persona or the last added persona are being deleted then reset the personal display count.
2161         if (personaListLength === 1) {
2162             ZmNewPersona.ID = 0;
2163         }
2164         else if (personaListLength === index) {
2165             if (ZmNewPersona.ID > 0) {
2166                 ZmNewPersona.ID--;
2167             }
2168         }
2169     }
2170 
2171     this._accounts.remove(account);
2172 	this._resetAccountListView(index);
2173 };
2174 
2175 ZmAccountsPage.prototype._handleAddExternalButton =
2176 function(evt) {
2177 	var account = new ZmNewDataSource();
2178 	this._accounts.add(account);
2179 	this._accounts.sort(ZmAccountsPage.__ACCOUNT_COMPARATOR);
2180 	this._resetAccountListView(account);
2181 };
2182 
2183 ZmAccountsPage.prototype._handleAddPersonaButton =
2184 function(evt) {
2185 	var persona = new ZmNewPersona();
2186 	this._accounts.add(persona);
2187 	this._accounts.sort(ZmAccountsPage.__ACCOUNT_COMPARATOR);
2188 	this._resetAccountListView(persona);
2189 };
2190 
2191 ZmAccountsPage.prototype._updateList =
2192 function(account) {
2193 	var lv = this._accountListView;
2194     var email = AjxStringUtil.htmlEncode(account.getEmail());
2195     var identity = account.getIdentity();
2196     if (!account.isMain && identity && identity.sendFromAddressType == ZmSetting.SEND_ON_BEHALF_OF){
2197         email = appCtxt.getUsername() + " " + ZmMsg.sendOnBehalfOf + " " + email;
2198     }
2199 
2200 	lv.setCellContents(account, ZmItem.F_NAME, AjxStringUtil.htmlEncode(account.getName()));
2201 	lv.setCellContents(account, ZmItem.F_EMAIL,email);
2202 	lv.setCellContents(account, ZmItem.F_TYPE, AjxStringUtil.htmlEncode(lv._getAccountType(account)));
2203 };
2204 
2205 // generic listeners
2206 
2207 ZmAccountsPage.prototype._handleNameChange =
2208 function(evt) {
2209 	var inputEl = DwtUiEvent.getTarget(evt);
2210 	var newName = inputEl.value;
2211 	this._accountListView.setCellContents(this._currentAccount, ZmItem.F_NAME, AjxStringUtil.htmlEncode(newName));
2212 	this._setControlValue("HEADER", this._currentSection, newName);
2213 
2214 	if (this._currentAccount.identity) {
2215 		this._currentAccount.identity.name = newName;
2216 	}
2217 
2218 	var type = this._currentAccount.type;
2219 	if (type == ZmAccount.TYPE_POP || type == ZmAccount.TYPE_IMAP) {
2220 		this._setDownloadToFolder(this._currentAccount);
2221 	}
2222 };
2223 
2224 ZmAccountsPage.prototype._handleEmailChange =
2225 function(evt) {
2226 	// update email cell
2227 	var section = this._currentSection;
2228 	var email = this._getControlValue("EMAIL", section);
2229 	this._updateEmailCell(email);
2230 
2231 	// auto-fill username and host
2232 	var m = email.match(/^(.*?)(?:@(.*))?$/);
2233 	if (!m) return;
2234 
2235 	var dataSource = this._currentAccount;
2236 	if (dataSource.userName == "") {
2237 		this._setControlValue("USERNAME", section, m[1]);
2238 	}
2239 	if (m[2] && dataSource.mailServer == "") {
2240 		this._setControlValue("HOST", section, m[2]);
2241 	}
2242 	this._updateReplyToEmail(email);
2243 };
2244 
2245 ZmAccountsPage.prototype._updateEmailCell =
2246 function(email) {
2247 	this._accountListView.setCellContents(this._currentAccount, ZmItem.F_EMAIL, AjxStringUtil.htmlEncode(email));
2248 };
2249 
2250 ZmAccountsPage.prototype._updateReplyToEmail =
2251 function(email) {
2252 	if (AjxEmailAddress.isValid(email)) {
2253 		this._updateComboBox("REPLY_TO_EMAIL", email);
2254 	} else {
2255 		this._updateComboBox("REPLY_TO_EMAIL");
2256 	}
2257 };
2258 
2259 // data source listeners
2260 
2261 ZmAccountsPage.prototype._handleTypeChange =
2262 function(evt) {
2263 	var type = ZmAccount.getTypeName(this._getControlValue("ACCOUNT_TYPE", this._currentSection));
2264 	this._accountListView.setCellContents(this._currentAccount, ZmItem.F_TYPE, AjxStringUtil.htmlEncode(type));
2265 	this._handleTypeOrSslChange(evt);
2266 };
2267 
2268 ZmAccountsPage.prototype._handleDownloadTo =
2269 function(evt) {
2270 	var isInbox = this._getControlValue("DOWNLOAD_TO", this._currentSection) == ZmAccountsPage.DOWNLOAD_TO_INBOX;
2271 	this._currentAccount.folderId = isInbox ? ZmOrganizer.ID_INBOX : -1; 
2272 };
2273 
2274 ZmAccountsPage.prototype._handleProviderChange =
2275 function() {
2276 	var id = this._getControlValue("PROVIDER", this._currentSection);
2277 	var dataSource = this._currentAccount;
2278 
2279 	// initialize data source
2280 	if (id) {
2281 		var provider = ZmDataSource.getProviders()[id];
2282 		if (!provider) return;
2283 
2284 		// init default values
2285 		dataSource.reset();
2286 		for (var p in provider) {
2287 			if (p == "id") continue;
2288 			if (p == "type") {
2289 				dataSource.setType(provider[p]);
2290 				continue;
2291 			}
2292 			if (ZmDataSource.DATASOURCE_ATTRS[p]) {
2293 				dataSource[ZmDataSource.DATASOURCE_ATTRS[p]] = provider[p];
2294 			}
2295 		}
2296 	}
2297 
2298 	// reset interface
2299 	var skipUpdate = true;
2300 	var ignoreProvider = id == "";
2301 	this.setAccount(dataSource, skipUpdate, ignoreProvider);
2302 };
2303 
2304 ZmAccountsPage.prototype._handleTypeOrSslChange =
2305 function(evt) {
2306 	var dataSource = this._currentAccount;
2307 	var section = this._currentSection;
2308 	if (dataSource._new) {
2309 		var type = this._getControlValue("ACCOUNT_TYPE", section);
2310 		if (!type) {
2311 			type = appCtxt.get(ZmSetting.POP_ACCOUNTS_ENABLED) ? ZmAccount.TYPE_POP : ZmAccount.TYPE_IMAP;
2312 		}
2313 		dataSource.setType(type);
2314 
2315 		var isPop = type == ZmAccount.TYPE_POP;
2316 		this._setControlEnabled("DELETE_AFTER_DOWNLOAD", this._currentSection, isPop);
2317 		this._setControlEnabled("DOWNLOAD_TO", this._currentSection, isPop);
2318 	}
2319 
2320 	var ssl = this._getControlValue("SSL", section) == ZmDataSource.CONNECT_SSL;
2321 	dataSource.connectionType = ssl ? ZmDataSource.CONNECT_SSL : ZmDataSource.CONNECT_CLEAR;
2322 	dataSource.port = dataSource.getDefaultPort();
2323 	this._setPortControls(dataSource.type, dataSource.connectionType, dataSource.port);
2324 };
2325 
2326 ZmAccountsPage.prototype._handleUserNameChange =
2327 function(evt) {
2328 	var userName = this._getControlValue("USERNAME", this._currentSection);
2329 	this._currentAccount.userName = userName;
2330 	if (!this._getControlValue("EMAIL", this._currentSection)) {
2331 		var provider = ZmDataSource.getProviderForAccount(this._currentAccount);
2332 		var email = userName && provider && provider._host ? [userName,provider._host].join("@") : userName;
2333 		this._updateEmailCell(email);
2334 	}
2335 };
2336 
2337 ZmAccountsPage.prototype._handleHostChange =
2338 function(evt) {
2339 	this._currentAccount.mailServer = this._getControlValue("HOST", this._currentSection);
2340 };
2341 
2342 ZmAccountsPage.prototype._handleChangePort =
2343 function(evt) {
2344 	this._setControlEnabled("PORT", this._currentSection, evt.detail);
2345 };
2346 
2347 ZmAccountsPage.prototype._handleTestButton =
2348 function(evt) {
2349 	var button = evt.item;
2350 	button.setEnabled(false);
2351 
2352 	// make sure that the current object proxy is up-to-date
2353 	var dataSource = this._currentAccount;
2354 	this._setAccountFields(dataSource, this._currentSection);
2355 
2356 	// check values
2357 	if (!dataSource.userName || !dataSource.mailServer || !dataSource.port) {
2358 		appCtxt.setStatusMsg(ZmMsg.accountTestErrorMissingInfo, ZmStatusView.LEVEL_CRITICAL);
2359 		button.setEnabled(true);
2360 		return;
2361 	}
2362 
2363 	// testconnection
2364 	var accounts = [ dataSource ];
2365 	var callback = new AjxCallback(this, this._testFinish, [button]);
2366 	this._testAccounts(accounts, callback, callback);
2367 };
2368 
2369 ZmAccountsPage.prototype._testFinish =
2370 function(button) {
2371 	button.setEnabled(true);
2372 };
2373 
2374 // identity listeners
2375 
2376 ZmAccountsPage.prototype._handleReplyTo =
2377 function(evt) {
2378 	this._setReplyToControls();
2379 };
2380 
2381 ZmAccountsPage.prototype._handleWhenSentTo =
2382 function(evt) {
2383 	this._setWhenSentToControls();
2384 };
2385 
2386 ZmAccountsPage.prototype._handleWhenInFolder =
2387 function(evt) {
2388 	this._setWhenInFolderControls();
2389 };
2390 
2391 ZmAccountsPage.prototype._handleFolderButton =
2392 function(evt) {
2393 	if (!this._folderAddCallback) {
2394 		this._folderAddCallback = new AjxCallback(this, this._handleFolderAdd);
2395 	}
2396 	var dialog = appCtxt.getChooseFolderDialog();
2397 	var params = {overviewId: dialog.getOverviewId(ZmApp.MAIL), appName:ZmApp.MAIL};
2398 	ZmController.showDialog(dialog, this._folderAddCallback, params);
2399 };
2400 
2401 ZmAccountsPage.prototype._handleFolderAdd =
2402 function(folder) {
2403 	var section = this._currentSection;
2404 	var folders = this._getControlValue("WHEN_IN_FOLDER_LIST", section);
2405 	if (!folders) return;
2406 
2407 	folders.push(folder.id);
2408 	this._setControlValue("WHEN_IN_FOLDER_LIST", section, folders);
2409 	appCtxt.getChooseFolderDialog().popdown();
2410 };
2411 
2412 // pre-save callbacks
2413 
2414 ZmAccountsPage.prototype._preSave =
2415 function(continueCallback) {
2416     // make sure that the current object proxy is up-to-date
2417     this._setAccountFields(this._currentAccount, this._currentSection);
2418 
2419     if (appCtxt.isOffline) {
2420         // skip account tests  
2421         this._preSaveCreateFolders(continueCallback);
2422     } else {
2423         // perform account tests
2424         this._preSaveTest(continueCallback);
2425     }
2426 
2427 };
2428 
2429 ZmAccountsPage.prototype._preSaveTest =
2430 function(continueCallback) {
2431 	// get dirty external accounts
2432 	var dirtyAccounts = [];
2433 	var accounts = this._accounts.getArray();
2434 	for (var i = 0; i < accounts.length; i++) {
2435 		var account = accounts[i];
2436 		if (account.type == ZmAccount.TYPE_POP || account.type == ZmAccount.TYPE_IMAP) {
2437 			if (account._new || account._dirty) {
2438 				dirtyAccounts.push(account);
2439 			}
2440 		}
2441 	}
2442 
2443 	// test for invalid name
2444 	for (var i = 0; i < dirtyAccounts.length; i++) {
2445 		var account = dirtyAccounts[i];
2446 		if (account.type == ZmAccount.TYPE_IMAP && account.name.match(/^\s*inbox\s*$/i)) {
2447 			var params = {
2448 				msg: AjxMessageFormat.format(ZmMsg.accountNameReserved, [AjxStringUtil.htmlEncode(account.name)]),
2449 				level: ZmStatusView.LEVEL_CRITICAL
2450 			};
2451 			appCtxt.setStatusMsg(params);
2452 			continueCallback.run(false);
2453 			return;
2454 		}
2455 	}
2456 
2457 	// test external accounts
2458 	if (dirtyAccounts.length > 0) {
2459 		var okCallback = new AjxCallback(this, this._preSaveTestOk, [continueCallback, dirtyAccounts]);
2460 		var cancelCallback = new AjxCallback(this, this._preSaveTestCancel, [continueCallback]);
2461 		this._testAccounts(dirtyAccounts, okCallback, cancelCallback);
2462 	}
2463 
2464 	// perform next step
2465 	else {
2466 		this._preSaveCreateFolders(continueCallback);
2467 	}
2468 };
2469 
2470 ZmAccountsPage.prototype._preSaveTestOk =
2471 function(continueCallback, accounts, successes) {
2472 	// en/disable accounts based on test results 
2473 	for (var i = 0; i < accounts.length; i++) {
2474 		accounts[i].enabled = successes[i];
2475 	}
2476 
2477 	// continue
2478 	this._preSaveCreateFolders(continueCallback);
2479 };
2480 
2481 ZmAccountsPage.prototype._preSaveTestCancel =
2482 function(continueCallback) {
2483 	if (continueCallback) {
2484 		continueCallback.run(false);
2485 	}
2486 };
2487 
2488 ZmAccountsPage.prototype._preSaveCreateFolders =
2489 function(continueCallback) {
2490 	var batchCmd;
2491 	var root = appCtxt.getById(ZmOrganizer.ID_ROOT);
2492 	var accounts = this._accounts.getArray();
2493 	for (var i = 0; i < accounts.length; i++) {
2494 		var account = accounts[i];
2495 		if (account.type == ZmAccount.TYPE_POP || account.type == ZmAccount.TYPE_IMAP) {
2496 			if (account.folderId != ZmOrganizer.ID_INBOX) {
2497 				var name = AjxStringUtil.trim(account.getName());
2498 				if (!batchCmd) { batchCmd = new ZmBatchCommand(false); }
2499 
2500 				// avoid folder create if it already exists
2501 				var folder = root.getByName(name, true);
2502 				if (folder) {
2503 					var folderId = folder.id;
2504 					if (folder.type !== ZmId.ORG_FOLDER) {
2505 						if (folder.parent.id == ZmOrganizer.ID_ROOT) {
2506 							//e.g. calendar that's child of root - wouldn't be able to create the folder for the account with that same name. (despite it being mail). So bail.
2507 							var params = {
2508 								msg: AjxMessageFormat.format(ZmMsg.errorAlreadyExists, [AjxStringUtil.htmlEncode(name), ZmMsg[folder.type.toLowerCase()]]),
2509 								level: ZmStatusView.LEVEL_CRITICAL
2510 							};
2511 							appCtxt.setStatusMsg(params);
2512 							continueCallback.run(false);
2513 							return;
2514 						}
2515 						//otherwise this continues on with creating the folder since this folder is not interfering (it's not a root child folder)
2516 					}
2517 					else if (folderId != ZmOrganizer.ID_INBOX && Number(folderId) < 256) {
2518 						var params = {
2519 							msg: AjxMessageFormat.format(ZmMsg.accountNameReserved, [AjxStringUtil.htmlEncode(name)]),
2520 							level: ZmStatusView.LEVEL_CRITICAL
2521 						};
2522 						appCtxt.setStatusMsg(params);
2523 						continueCallback.run(false);
2524 						return;
2525 					}
2526 					// if there already is a folder by this name in Trash, rename the trashed folder
2527 					else if (folder.isInTrash()) {
2528 						folder.rename(folder.name+"_");
2529 					}
2530 					else {
2531 						account.folderId = folder.id;
2532 						continue;
2533 					}
2534 				}
2535 
2536 				// this means user modified name of the folder, so let's rename it
2537 				folder = account._object_ && appCtxt.getById(account._object_.folderId);
2538 				if (folder && Number(folder.id) >= 256) {
2539 					if (folder.name != name) {
2540 						var soapDoc = AjxSoapDoc.create("FolderActionRequest", "urn:zimbraMail");
2541 						var actionNode = soapDoc.set("action");
2542 						actionNode.setAttribute("op", "rename");
2543 						actionNode.setAttribute("id", folder.id);
2544 						actionNode.setAttribute("name", name);
2545 
2546 						var callback = new AjxCallback(this, this._handleRenameFolderResponse, [account, folder]);
2547 						batchCmd.addNewRequestParams(soapDoc, callback, callback);
2548 					}
2549 					else {
2550 						account.folderId = folder.id;
2551 					}
2552 				} else {
2553 					var soapDoc = AjxSoapDoc.create("CreateFolderRequest", "urn:zimbraMail");
2554 					var folderEl = soapDoc.set("folder");
2555 					folderEl.setAttribute("l", ZmOrganizer.ID_ROOT);
2556 					folderEl.setAttribute("name", name);
2557 					folderEl.setAttribute("fie", "1"); // fetch-if-exists
2558 
2559 					var callback = new AjxCallback(this, this._handleCreateFolderResponse, [account]);
2560 					batchCmd.addNewRequestParams(soapDoc, callback, callback);
2561 				}
2562 			}
2563 		}
2564 	}
2565 
2566 	// continue
2567 	if (batchCmd && batchCmd.size() > 0) {
2568 		// HACK: Don't know a better way to set an error condition
2569 		this.__hack_preSaveSuccess = true;
2570 		var callback = new AjxCallback(this, this._preSaveFinish, [continueCallback]);
2571 		batchCmd.run(callback);
2572 	}
2573 	else {
2574 		this._preSaveFinish(continueCallback);
2575 	}
2576 };
2577 
2578 ZmAccountsPage.prototype._handleCreateFolderResponse =
2579 function(dataSource, result) {
2580 	var resp = result && result._data && result._data.CreateFolderResponse;
2581 	if (resp) {
2582 		dataSource.folderId = ZmOrganizer.normalizeId(resp.folder[0].id);
2583 	}
2584 	else {
2585 		// HACK: Don't know a better way to set an error condition
2586 		this.__hack_preSaveSuccess = false;
2587 	}
2588 };
2589 
2590 ZmAccountsPage.prototype._handleRenameFolderResponse =
2591 function(dataSource, folder, result) {
2592 	var resp = result && result._data && result._data.FolderActionResponse;
2593 	if (resp) {
2594 		dataSource.folderId = ZmOrganizer.normalizeId(folder.id);
2595 	}
2596 	else {
2597 		// HACK: Don't know a better way to set an error condition
2598 		this.__hack_preSaveSuccess = false;
2599 	}
2600 };
2601 
2602 ZmAccountsPage.prototype._preSaveFinish =
2603 function(continueCallback, result, exceptions) {
2604 	// HACK: Don't know a better way to set an error condition
2605 	continueCallback.run(this.__hack_preSaveSuccess && (!exceptions || exceptions.length == 0));
2606 };
2607 
2608 // save callbacks
2609 
2610 ZmAccountsPage.prototype._handleSaveAccount =
2611 function(account, resp) {
2612 
2613 	delete account._dirty;
2614 
2615 	var mboxes = appCtxt.accountList.getAccounts();
2616 	var active = appCtxt.getActiveAccount();
2617 
2618 	// Save data from old proxies to proxied objects
2619 	for (var j in mboxes) {
2620 		var acct = mboxes[j];
2621 		if (active.isMain || acct == active) {
2622 			var acct = ZmAccountsPage.__unProxy(mboxes[j]._proxy_);
2623 			if (acct) {
2624 				mboxes[j] = acct;
2625 			}
2626 		}
2627 	}
2628 };
2629 
2630 ZmAccountsPage.prototype._handleSaveVisibleAccount =
2631 function() {
2632 	var accounts = this._accounts.getArray();
2633 	for (var i = 0; i < accounts.length; i++) {
2634 		var account = accounts[i];
2635 		account._object_.visible = account.visible;
2636 		delete account._visibleDirty;
2637 	}
2638 	var setting = appCtxt.getSettings().getSetting(ZmSetting.CHILD_ACCTS_VISIBLE);
2639 	setting._notify(ZmEvent.E_MODIFY);
2640 };
2641 
2642 ZmAccountsPage.prototype._handleCreateAccount =
2643 function(account, resp) {
2644 	delete account._new;
2645 	account._needsSync = true;
2646 };
2647 
2648 ZmAccountsPage.prototype._promptToDeleteFolder = function(organizer) {
2649 	var dialog = appCtxt.getConfirmationDialog();
2650 	var prompt = AjxMessageFormat.format(ZmMsg.accountDeleteFolder, [organizer.getName()]);
2651 	var callback = new AjxCallback(this, this._handleDeleteFolder, [organizer]);
2652 	dialog.popup(prompt, callback);
2653 };
2654 
2655 ZmAccountsPage.prototype._handleDeleteFolder = function(organizer) {
2656 	var trash = appCtxt.getById(ZmOrganizer.ID_TRASH);
2657 	organizer.move(trash);
2658 };
2659 
2660 
2661 ZmAccountsPage.prototype._postSave =
2662 function() {
2663 	var needsSync = [];
2664 	var accounts = this._accounts.getArray();
2665 	for (var i = 0; i < accounts.length; i++) {
2666 		var account = accounts[i];
2667 		if (account._needsSync) {
2668 			needsSync.push(account);
2669 		}
2670 		if (account instanceof ZmDataSource) {
2671 			delete account.password;
2672 		}
2673 	}
2674 
2675 	if (needsSync.length) {
2676 	    var dsCollection = AjxDispatcher.run("GetDataSourceCollection");
2677 		for (var i = 0; i < needsSync.length; i++) {
2678 			var account = needsSync[i];
2679 			dsCollection.importMailFor(account.folderId);
2680 			delete account._needsSync;
2681 		}
2682 	}
2683 };
2684 
2685 ZmAccountsPage.prototype._handleAddApplicationCode =
2686 function() {
2687 	if (!this._addApplicationCodeDlg) {
2688 		var appPasscodeCallback = this._getAppSpecificPasswords.bind(this);
2689 		this._addApplicationCodeDlg = new ZmApplicationCodeDialog(appPasscodeCallback);
2690 	}
2691 	this._addApplicationCodeDlg.popup();
2692 };
2693 
2694 ZmAccountsPage.prototype._revokeApplicationCode =
2695 function() {
2696 	var item = this.appPasscodeList.getSelection()[0];
2697 	if (item) {
2698 		var jsonObj = {RevokeAppSpecificPasswordRequest : {_jsns : "urn:zimbraAccount", appName : item.appName}};
2699 		var respCallback = this._handleRevokeApplicationCode.bind(this);
2700 		appCtxt.getAppController().sendRequest({jsonObj:jsonObj, asyncMode:true, callback:respCallback});
2701 	}
2702 };
2703 
2704 ZmAccountsPage.prototype._handleRevokeApplicationCode =
2705 function() {
2706 	this._getAppSpecificPasswords();
2707 };
2708 
2709 ZmAccountsPage.prototype._getAppSpecificPasswords =
2710 function() {
2711 	var jsonObj = {GetAppSpecificPasswordsRequest : {_jsns : "urn:zimbraAccount"}};
2712 	var respCallback = this._getAppSpecificPasswordsCallback.bind(this);
2713 	appCtxt.getAppController().sendRequest({jsonObj:jsonObj, asyncMode:true, callback:respCallback});
2714 };
2715 
2716 ZmAccountsPage.prototype._getAppSpecificPasswordsCallback =
2717 function(appSpecificPasswords) {
2718 	var applicationCodesElement = document.getElementById(this._htmlElId + "_APPLICATION_CODES") || (this.appPasscodeList && this.appPasscodeList.getHtmlElement());
2719 	var appSpecificPasswordsList = this._setAppSpecificPasswords(appSpecificPasswords);
2720 	if (!appSpecificPasswordsList) {
2721 		appSpecificPasswordsList = new AjxVector();
2722 	}
2723 	var appPasscodeList = new ZmAccountAppPasscodeListView(this);
2724 	this.appPasscodeList = appPasscodeList;
2725 	appPasscodeList.replaceElement(applicationCodesElement);
2726 	appPasscodeList.set(appSpecificPasswordsList);
2727 	appPasscodeList.addSelectionListener(this._appPasscodeSelectionListener.bind(this));
2728 	this.revokeApplicationCodeButton.setEnabled(false);
2729 };
2730 
2731 ZmAccountsPage.prototype._setAppSpecificPasswords =
2732 function(appSpecificPasswords) {
2733 	var response = appSpecificPasswords.getResponse();
2734 	if (!response || !response.GetAppSpecificPasswordsResponse) {
2735 		return;
2736 	}
2737 	var appSpecificPasswordsResponse = response.GetAppSpecificPasswordsResponse;
2738 	var passwordData =  appSpecificPasswordsResponse && appSpecificPasswordsResponse.appSpecificPasswords && appSpecificPasswordsResponse.appSpecificPasswords.passwordData;
2739 	if (!passwordData) {
2740 		return;
2741 	}
2742 	var vector = AjxVector.fromArray(passwordData);
2743 	vector.sort(this._compareAppPasscodes);
2744 	return vector;
2745 };
2746 
2747 ZmAccountsPage.prototype._compareAppPasscodes =
2748 function(a, b) {
2749 	return a.appName < b.appName ? -1 : (a.appName > b.appName ? 1 : 0);
2750 };
2751 
2752 ZmAccountsPage.prototype._appPasscodeSelectionListener =
2753 function() {
2754 	this.revokeApplicationCodeButton.setEnabled(!!this.appPasscodeList.getSelectionCount());
2755 };
2756 
2757 //
2758 // Private functions
2759 //
2760 
2761 ZmAccountsPage.__ACCOUNT_COMPARATOR =
2762 function(a, b) {
2763 	if (a.type == ZmAccount.TYPE_ZIMBRA && (a.isMain || appCtxt.isOffline)) return -1;
2764 	if (b.type == ZmAccount.TYPE_ZIMBRA && (b.isMain || appCtxt.isOffline)) return 1;
2765 	return a.getName().localeCompare(b.getName());
2766 };
2767 
2768 ZmAccountsPage.__createProxy =
2769 function(account) {
2770 	delete account._object_;
2771 	var identity = account.getIdentity();
2772 	var identityProxy = AjxUtil.createProxy(identity);
2773 	var proxy = AjxUtil.createProxy(account);
2774 	proxy._identity = identityProxy;
2775 	account._proxy_ = proxy;
2776 	proxy.getIdentity = AjxCallback.simpleClosure(function(){return this._identity}, proxy, identityProxy);
2777 	return proxy;
2778 };
2779 
2780 ZmAccountsPage.__proxy_getIdentity =
2781 function(identity) {
2782 	return identity;
2783 };
2784 
2785 ZmAccountsPage.__unProxy =
2786 function(accountProxy) {
2787 	var account = AjxUtil.unProxy(accountProxy);
2788 	if (!account) return accountProxy;
2789 	var identityProxy = accountProxy._identity;
2790 	var identity = AjxUtil.unProxy(identityProxy);
2791 	account.getIdentity = AjxCallback.simpleClosure(ZmAccountsPage.__proxy_getIdentity, account, identity);
2792 	return account;
2793 }
2794 
2795 //
2796 // Classes
2797 //
2798 
2799 ZmAccountsListView = function(parent, className, posStyle, noMaximize) {
2800 	className = className || "DwtListView";
2801 	className += " ZOptionsItemsListView";
2802 	DwtListView.call(this, {parent:parent, className:className, posStyle:posStyle,
2803 							headerList:this._getHeaderList(), noMaximize:noMaximize});
2804 	this.setMultiSelect(false);
2805 	this._view = ZmId.VIEW_ACCOUNT;
2806 };
2807 ZmAccountsListView.prototype = new DwtListView;
2808 ZmAccountsListView.prototype.constructor = ZmAccountsListView;
2809 
2810 ZmAccountsListView.prototype.toString =
2811 function() {
2812 	return "ZmAccountsListView";
2813 };
2814 
2815 // Constants
2816 
2817 ZmAccountsListView.WIDTH_NAME	= ZmMsg.COLUMN_WIDTH_NAME_ACC;
2818 ZmAccountsListView.WIDTH_STATUS	= ZmMsg.COLUMN_WIDTH_STATUS_ACC;
2819 ZmAccountsListView.WIDTH_TYPE	= ZmMsg.COLUMN_WIDTH_TYPE_ACC;
2820 
2821 // Public methods
2822 
2823 ZmAccountsListView.prototype.getCellElement =
2824 function(account, field) {
2825 	return document.getElementById(this._getCellId(account, field));
2826 };
2827 
2828 ZmAccountsListView.prototype.setCellContents =
2829 function(account, field, html) {
2830 	var el = this.getCellElement(account, field);
2831 	if (!el) { return; }
2832 
2833 	if (field == ZmItem.F_NAME) {
2834 		el = document.getElementById(this._getCellId(account, field)+"_name");
2835     }
2836     if(field == ZmItem.F_EMAIL) {
2837         html = "<div style='margin-left: 10px;'>"+ html +"</div>";    
2838     }
2839 	el.innerHTML = html;
2840 };
2841 
2842 // Protected methods
2843 
2844 ZmAccountsListView.prototype._getCellContents =
2845 function(buffer, i, item, field, col, params) {
2846 	if (field == ZmItem.F_NAME) {
2847 		var cellId = this._getCellId(item, field);
2848 		buffer[i++] = "<div id='";
2849 		buffer[i++] = cellId;
2850 		buffer[i++] = "_name'>";
2851 		buffer[i++] = AjxStringUtil.htmlEncode(item.getName());
2852 		buffer[i++] = "</div>";
2853 		return i;
2854 	}
2855 	if (field == ZmItem.F_STATUS) {
2856 		if (item instanceof ZmDataSource && !item.isStatusOk()) {
2857 			buffer[i++] = "<table border=0 cellpadding=0 cellpadding=0><tr>";
2858 			buffer[i++] = "<td><div class='ImgCritical_12'></div></td><td>";
2859 			buffer[i++] = ZmMsg.ALT_ERROR;
2860 			buffer[i++] = "</td></tr></table>";
2861 		}
2862 		else {
2863 			buffer[i++] = AjxMsg.ok;
2864 		}
2865 		return i;
2866 	}
2867 	if (field == ZmItem.F_EMAIL) {
2868         var email = item.getEmail();
2869         var identity = item.getIdentity();
2870         if (!item.isMain && identity.sendFromAddressType == ZmSetting.SEND_ON_BEHALF_OF) email = appCtxt.getActiveAccount().name + " " + ZmMsg.sendOnBehalfOf + " " + email;
2871 		buffer[i++] = "<div style='margin-left: 10px;'>"+ AjxStringUtil.htmlEncode(email) +"</div>";
2872 		return i;
2873 	}
2874 	if (field == ZmItem.F_TYPE) {
2875 		buffer[i++] = this._getAccountType(item);
2876 		return i;
2877 	}
2878 	return DwtListView.prototype._getCellContents.apply(this, arguments);
2879 };
2880 
2881 ZmAccountsListView.prototype._getCellId =
2882 function(item, field, params) {
2883 	return DwtId.getListViewItemId(DwtId.WIDGET_ITEM_CELL, this._view, item.id, field);
2884 };
2885 
2886 ZmAccountsListView.prototype._getHeaderList =
2887 function() {
2888 	return [
2889 		new DwtListHeaderItem({field:ZmItem.F_NAME, text:ZmMsg.accountName, width:ZmAccountsListView.WIDTH_NAME}),
2890 		new DwtListHeaderItem({field:ZmItem.F_STATUS, text:ZmMsg.status, width:ZmAccountsListView.WIDTH_STATUS, align:"center"}),
2891 		new DwtListHeaderItem({field:ZmItem.F_EMAIL, text:ZmMsg.emailAddr}),
2892 		new DwtListHeaderItem({field:ZmItem.F_TYPE, text:ZmMsg.type, width:ZmAccountsListView.WIDTH_TYPE})
2893 	];
2894 };
2895 
2896 ZmAccountsListView.prototype._getAccountType =
2897 function(account) {
2898 	var provider = ZmDataSource.getProviderForAccount(account);
2899 	return (provider && AjxStringUtil.htmlEncode(provider.name)) || (account.isMain ? ZmMsg.accountTypePrimary : ZmAccount.getTypeName(account.type));
2900 };
2901 
2902 // Delegate Permissions
2903 
2904 ZmAccountDelegatesListView = function(parent, className, posStyle, noMaximize) {
2905 	className = className || "DwtListView";
2906 	className += " ZOptionsItemsListView";
2907 	DwtListView.call(this, {parent:parent, className:className, posStyle:posStyle,
2908 							headerList:this._getHeaderList(), noMaximize:noMaximize});
2909 	this.setMultiSelect(false);
2910 };
2911 ZmAccountDelegatesListView.prototype = new DwtListView;
2912 ZmAccountDelegatesListView.prototype.constructor = ZmAccountDelegatesListView;
2913 
2914 ZmAccountDelegatesListView.prototype.toString =
2915 function() {
2916 	return "ZmAccountDelegatesListView";
2917 };
2918 
2919 // Constants
2920 
2921 ZmAccountDelegatesListView.WIDTH_NAME	= ZmMsg.COLUMN_WIDTH_NAME_ACC;
2922 ZmAccountDelegatesListView.WIDTH_STATUS	= ZmMsg.COLUMN_WIDTH_STATUS_ACC;
2923 ZmAccountDelegatesListView.WIDTH_TYPE	= ZmMsg.COLUMN_WIDTH_TYPE_ACC;
2924 
2925 // Public methods
2926 
2927 ZmAccountDelegatesListView.prototype.getCellElement =
2928 function(account, field) {
2929 	return document.getElementById(this._getCellId(account, field));
2930 };
2931 
2932 ZmAccountDelegatesListView.prototype.setCellContents =
2933 function(account, field, html) {
2934 	var el = this.getCellElement(account, field);
2935 	if (!el) { return; }
2936 
2937 	if (field == ZmItem.F_NAME) {
2938 		el = document.getElementById(this._getCellId(account, field)+"_name");
2939     }
2940     if(field == ZmItem.F_EMAIL) {
2941         html = "<div style='margin-left: 10px;'>"+ html +"</div>";
2942     }
2943 	el.innerHTML = html;
2944 };
2945 
2946 // Protected methods
2947 
2948 ZmAccountDelegatesListView.prototype._getCellContents =
2949 function(buffer, i, item, field, col, params) {
2950 	if (field == ZmItem.F_NAME) {
2951 		var cellId = this._getCellId(item, field);
2952 		buffer[i++] = "<div id='";
2953 		buffer[i++] = cellId;
2954 		buffer[i++] = "_name' style='margin:0 5px; overflow:hidden;'>";
2955 		buffer[i++] = AjxStringUtil.htmlEncode(item.user);
2956 		buffer[i++] = "</div>";
2957 		return i;
2958 	}
2959 	if (field == ZmItem.F_TYPE) {
2960         var cellId = this._getCellId(item, field);
2961 		buffer[i++] = "<div id='";
2962 		buffer[i++] = cellId;
2963 		buffer[i++] = "_type' style='margin:0 5px;'>";
2964 		buffer[i++] = (item.sendAs && item.sendOnBehalfOf)  ? ZmMsg.sendAsAndSendOnBehalfOf : (item.sendAs ? ZmMsg.sendAs : ZmMsg.sendOnBehalfOflbl);
2965 		buffer[i++] = "</div>";
2966 		return i;
2967 	}
2968 	return DwtListView.prototype._getCellContents.apply(this, arguments);
2969 };
2970 
2971 ZmAccountDelegatesListView.prototype._getCellId =
2972 function(item, field, params) {
2973     return DwtId.getListViewItemId(DwtId.WIDGET_ITEM_CELL, this._view, item.id, field);
2974 };
2975 
2976 ZmAccountDelegatesListView.prototype._getHeaderList =
2977 function() {
2978 	return [
2979 		new DwtListHeaderItem({field:ZmItem.F_NAME, text:ZmMsg.name, width:ZmMsg.COLUMN_WIDTH_NAME_DELEGATE, margin:"5px"}),
2980 		new DwtListHeaderItem({field:ZmItem.F_TYPE, text:ZmMsg.type})
2981 	];
2982 };
2983 
2984 ZmAccountDelegatesListView.prototype._doubleClickAction =
2985 function(){
2986    this.parent._editDelegateButton();
2987 }
2988 
2989 ZmAccountAppPasscodeListView = function(parent, className, posStyle, noMaximize) {
2990 	className = className || "DwtListView";
2991 	className += " ZOptionsItemsListView";
2992 	DwtListView.call(this, {parent:parent, className:className, posStyle:posStyle, headerList:this._getHeaderList(), noMaximize:noMaximize});
2993 	this.setMultiSelect(false);
2994 };
2995 ZmAccountAppPasscodeListView.prototype = new DwtListView;
2996 ZmAccountAppPasscodeListView.prototype.constructor = ZmAccountAppPasscodeListView;
2997 
2998 ZmAccountAppPasscodeListView.prototype.toString =
2999 function() {
3000 	return "ZmAccountAppPasscodeListView";
3001 };
3002 
3003 ZmAccountAppPasscodeListView.prototype._getHeaderList =
3004 function() {
3005 	return [
3006 		new DwtListHeaderItem({field:ZmItem.F_NAME, text:ZmMsg.name, margin:"5px", width:ZmMsg.COLUMN_WIDTH_NAME_APPLICATION}),
3007 		new DwtListHeaderItem({field:ZmItem.F_APP_PASSCODE_CREATED, text:ZmMsg.created, width:ZmMsg.COLUMN_WIDTH_NAME_APPLICATION}),
3008 		new DwtListHeaderItem({field:ZmItem.F_APP_PASSCODE_LAST_USED, text:ZmMsg.lastUsed, width:ZmMsg.COLUMN_WIDTH_NAME_APPLICATION})
3009 	];
3010 };
3011 
3012 ZmAccountAppPasscodeListView.prototype._getCellContents =
3013 function(buffer, i, item, field, col, params) {
3014 	if (field == ZmItem.F_NAME) {
3015 		var cellId = this._getCellId(item, field);
3016 		buffer[i++] = "<div id='";
3017 		buffer[i++] = cellId;
3018 		buffer[i++] = "_name' style='margin:0 5px; overflow:hidden;'>";
3019 		buffer[i++] = AjxStringUtil.htmlEncode(item.appName);
3020 		buffer[i++] = "</div>";
3021 		return i;
3022 	}
3023 	else if (field == ZmItem.F_APP_PASSCODE_CREATED) {
3024 		var cellId = this._getCellId(item, field);
3025 		buffer[i++] = "<div id='";
3026 		buffer[i++] = cellId;
3027 		buffer[i++] = "_type' style='margin:0 5px;'>";
3028 		buffer[i++] = AjxDateFormat.format(AjxDateFormat.SHORT, new Date(item.created));
3029 		buffer[i++] = "</div>";
3030 		return i;
3031 	}
3032 	else if (field == ZmItem.F_APP_PASSCODE_LAST_USED) {
3033 		var cellId = this._getCellId(item, field);
3034 		buffer[i++] = "<div id='";
3035 		buffer[i++] = cellId;
3036 		buffer[i++] = "_type' style='margin:0 5px;'>";
3037 		if (item.lastUsed) {
3038 			buffer[i++] = AjxDateFormat.format(AjxDateFormat.SHORT, new Date(item.lastUsed));
3039 		}
3040 		else {
3041 			buffer[i++] = "-";
3042 		}
3043 		buffer[i++] = "</div>";
3044 		return i;
3045 	}
3046 	return DwtListView.prototype._getCellContents.apply(this, arguments);
3047 };
3048 
3049 
3050 //
3051 // New data source class
3052 //
3053 
3054 ZmAccountsPage._defineClasses =
3055 function() {
3056 ZmNewDataSource = function() {
3057 	var number = ++ZmNewDataSource.ID;
3058 	this.setType(appCtxt.get(ZmSetting.POP_ACCOUNTS_ENABLED) ? ZmAccount.TYPE_POP : ZmAccount.TYPE_IMAP);
3059 	ZmDataSource.call(this, this.type, ("new-dsrc-"+number));
3060 	this.email = "";
3061 	this.name = AjxMessageFormat.format(ZmMsg.newExternalAccount, number);
3062 	this._new = true;
3063 	this.folderId = -1;
3064 	var identity = this.getIdentity();
3065 	identity.sendFromDisplay = appCtxt.get(ZmSetting.DISPLAY_NAME);
3066 	identity.sendFromAddress = appCtxt.get(ZmSetting.USERNAME);
3067 };
3068 ZmNewDataSource.prototype = new ZmDataSource;
3069 ZmNewDataSource.prototype.constructor = ZmNewDataSource;
3070 
3071 ZmNewDataSource.prototype.toString =
3072 function() {
3073 	return "ZmNewDataSource";
3074 };
3075 
3076 // Constants
3077 
3078 ZmNewDataSource.ID = 0;
3079 
3080 // Data
3081 
3082 ZmNewDataSource.prototype.ELEMENT_NAME = ZmPopAccount.prototype.ELEMENT_NAME;
3083 
3084 // Public methods
3085 
3086 ZmNewDataSource.prototype.setType =
3087 function(type) {
3088 	this.type = type;
3089 	var TYPE = this.type == ZmAccount.TYPE_POP ? ZmPopAccount : ZmImapAccount;
3090 	this.ELEMENT_NAME = TYPE.prototype.ELEMENT_NAME;
3091 	this.getDefaultPort = TYPE.prototype.getDefaultPort;
3092 };
3093 
3094 //
3095 // New persona class
3096 //
3097 
3098 ZmNewPersona = function() {
3099 	var number = ++ZmNewPersona.ID;
3100 	var id = "new-persona-"+number;
3101 	var name = AjxMessageFormat.format(ZmMsg.newPersona, number);
3102 	var identity = new ZmIdentity(name);
3103 	identity.id = id;
3104 	ZmPersona.call(this, identity);
3105 	this.id = id;
3106 	this._new = true;
3107 	identity.sendFromDisplay = appCtxt.get(ZmSetting.DISPLAY_NAME);
3108 	identity.sendFromAddress = appCtxt.get(ZmSetting.USERNAME);
3109 };
3110 ZmNewPersona.prototype = new ZmPersona;
3111 ZmNewPersona.prototype.constructor = ZmNewPersona;
3112 
3113 ZmNewPersona.prototype.toString =
3114 function() {
3115 	return "ZmNewPersona";
3116 };
3117 
3118 // Constants
3119 
3120 ZmNewPersona.ID = 0;
3121 
3122 /**
3123  * Fetches the list of personas added to account list view amongst other accounts.
3124  *
3125  *
3126  * @param	{String}	accountList		List of accounts added to account list view e.g. Persona and External (POP) account.
3127  *
3128  *
3129  * @public
3130  */
3131 ZmNewPersona.getPersonaList =
3132 function(accountList) {
3133     var personas = [];
3134 
3135     personas = AjxUtil.filter(accountList, function(accountItem) {
3136         if (accountItem instanceof ZmNewPersona || accountItem.type == ZmAccount.TYPE_PERSONA) {
3137             return accountItem;
3138         }
3139     });
3140 
3141     return personas;
3142 };
3143 
3144 }; // function ZmAccountsPage._defineClasses
3145 
3146 
3147 // GrantRights Dialog
3148 
3149 
3150 /**
3151  * Creates a grantRights Dialog.
3152  * @class
3153  * This class represents a rename folder dialog.
3154  *
3155  * @param	{DwtComposite}	parent		the parent
3156  * @param	{String}	className		the class name
3157  *
3158  * @extends		ZmDialog
3159  */
3160 ZmGrantRightsDialog = function(parent, className, callback) {
3161 
3162 	ZmDialog.call(this, {parent:parent, className:className, title:ZmMsg.grantRights, id:"GrantRightsDialog"});
3163     var id			= this.toString();
3164 	var inputId		= id + "_name";
3165 	var cellId		= id + "_name_cell";
3166 	var rowId		= id + "_name_row";
3167 	var sendAsId	= id + "_sendAs";
3168 	var sendOboId	= id + "_sendObo";
3169 
3170 	var aifParams = {
3171 		parent:					parent,
3172 		bubbleAddedCallback:	this._onChange.bind(this),
3173 		bubbleRemovedCallback:	this._onChange.bind(this),
3174 		singleBubble:			true,
3175 		inputId:				inputId,
3176 		type:					AjxEmailAddress.TO
3177 	}
3178 
3179 	this._editPermissions		= false;
3180 	this._okCallBack			= callback;
3181 
3182 	this._addrInputField		= new ZmAddressInputField(aifParams);
3183 	this._aifId					= this._addrInputField._htmlElId;
3184 
3185 	this._delegateEmailInput	= document.getElementById(inputId);
3186 	this._delegateEmailRow		= document.getElementById(rowId);
3187 	this._sendAs				= document.getElementById(sendAsId);
3188 	this._sendObo				= document.getElementById(sendOboId);
3189 
3190 	this._addrInputField.reparentHtmlElement(cellId);
3191 	this._initAutoComplete();
3192 };
3193 
3194 ZmGrantRightsDialog.prototype = new ZmDialog;
3195 ZmGrantRightsDialog.prototype.constructor = ZmGrantRightsDialog;
3196 
3197 ZmGrantRightsDialog.prototype.toString =
3198 function() {
3199 	return "ZmGrantRightsDialog";
3200 };
3201 
3202 /**
3203  * Pops-up the dialog.
3204  *
3205  */
3206 ZmGrantRightsDialog.prototype.popup =
3207 function() {
3208 	this._addrInputField.clear();
3209 	ZmDialog.prototype.popup.call(this);
3210     if (!this._editPermissions){
3211      this._delegateEmailInput.focus();
3212     } else {
3213      this._sendAs.focus();
3214     }
3215 };
3216 
3217 ZmGrantRightsDialog.prototype._contentHtml =
3218 function() {
3219     var subs = {id: this.toString()};
3220 	return AjxTemplate.expand("prefs.Pages#GrantRightsDialog",subs);
3221 };
3222 
3223 ZmGrantRightsDialog.prototype._okButtonListener =
3224 function(ev) {
3225 	// get email address from the bubble
3226 	var emailsFromBubbles = this._addrInputField.getAddresses(true);
3227 	var delegateEmail = emailsFromBubbles[0] && emailsFromBubbles[0].address;
3228 	if (!delegateEmail) {
3229 		// get email address from the plain text in input if no bubbles
3230 		var emailsFromText = AjxEmailAddress.getValidAddresses(this._delegateEmailInput.value).getArray();
3231 		delegateEmail = (emailsFromText[0] && emailsFromText[0].address) || this._delegateEmailInput.value;
3232 	}
3233 	this._okCallBack.run(delegateEmail, this._sendAs.checked, this._sendObo.checked);
3234 };
3235 
3236 ZmGrantRightsDialog.prototype.setData =
3237 function(item){
3238     if (item){
3239         this.setTitle(ZmMsg.editDelegatePermissions + " - " + item.user);
3240         this._delegateEmailRow.style.display ="none";
3241         this._sendAs.checked = item.sendAs;
3242         this._sendObo.checked = item.sendOnBehalfOf;
3243         this._editPermissions = true;
3244         this._prevData = item;
3245     } else {
3246         this.setTitle(ZmMsg.delegatePermissions);
3247         this._delegateEmailRow.style.display ="";
3248         this._delegateEmailInput.value="";
3249         this._sendAs.checked = false;
3250         this._sendObo.checked = false;
3251         this._editPermissions = false;
3252         Dwt.setHandler(this._delegateEmailInput, DwtEvent.ONCHANGE, this._onChange.bind(this));
3253     }
3254     this.getButton(DwtDialog.OK_BUTTON).setEnabled(false);
3255     Dwt.setHandler(this._sendAs, DwtEvent.ONCLICK, this._onChange.bind(this));
3256     Dwt.setHandler(this._sendObo, DwtEvent.ONCLICK, this._onChange.bind(this));
3257 };
3258 ZmGrantRightsDialog.prototype._initAutoComplete =
3259 function(){
3260 	if (appCtxt.get(ZmSetting.CONTACTS_ENABLED) || appCtxt.get(ZmSetting.GAL_ENABLED)) {
3261 		var params = {
3262 			parent:			appCtxt.getShell(),
3263 			dataClass:		appCtxt.getAutocompleter(),
3264 			options:		{type:ZmAutocomplete.AC_TYPE_GAL, acType:ZmAutocomplete.AC_TYPE_CONTACT, excludeGroups:true},
3265 			matchValue:		ZmAutocomplete.AC_VALUE_FULL,
3266 			separator:		"",
3267 			galType:		ZmSearch.GAL_ACCOUNT,
3268 			contextId:		this.toString()
3269 		};
3270 		this._acAddrSelectList = new ZmAutocompleteListView(params);
3271 		this._acAddrSelectList.handle(this._delegateEmailInput, this._aifId);
3272 		this._addrInputField.setAutocompleteListView(this._acAddrSelectList);
3273 	}
3274 };
3275 
3276 ZmGrantRightsDialog.prototype._onChange =
3277 function(){
3278     var enable = false;
3279     if (!this._editPermissions){
3280 		enable = (this._delegateEmailInput.value || this._addrInputField.getValue())
3281 			&& (this._sendAs.checked || this._sendObo.checked);
3282     } else {
3283 		enable = this._prevData.sendAs !== this._sendAs.checked
3284 			|| this._prevData.sendOnBehalfOf !== this._sendObo.checked;
3285     }
3286     this.getButton(DwtDialog.OK_BUTTON).setEnabled(enable);
3287 
3288 }
3289