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 ZmMailPrefsPage = function(parent, section, controller) {
 25 	ZmPreferencesPage.apply(this, arguments);
 26 
 27 	this._initialized = false;
 28 };
 29 
 30 ZmMailPrefsPage.prototype = new ZmPreferencesPage;
 31 ZmMailPrefsPage.prototype.constructor = ZmMailPrefsPage;
 32 
 33 ZmMailPrefsPage.prototype.isZmMailPrefsPage = true;
 34 ZmMailPrefsPage.prototype.toString = function() { return "ZmMailPrefsPage"; };
 35 
 36 //
 37 // ZmPreferencesPage methods
 38 //
 39 
 40 ZmMailPrefsPage.prototype.showMe =
 41 function() {
 42 	ZmPreferencesPage.prototype.showMe.call(this);
 43     if(appCtxt.isOffline){
 44         if(this._initializedAcctId != appCtxt.getActiveAccount().id) {
 45             this._initialized = false;
 46             this._initializedAcctId = appCtxt.getActiveAccount().id;
 47         }
 48     }
 49 	if (!this._initialized) {
 50 		this._initialized = true;
 51 		if (this._blackListControl && this._whiteListControl) {
 52 			var soapDoc = AjxSoapDoc.create("GetWhiteBlackListRequest", "urn:zimbraAccount");
 53 			var callback = new AjxCallback(this, this._handleResponseLoadWhiteBlackList);
 54 			appCtxt.getRequestMgr().sendRequest({soapDoc:soapDoc, asyncMode:true, callback:callback});
 55 		}
 56 	}
 57 };
 58 
 59 ZmMailPrefsPage.prototype.reset =
 60 function(useDefaults) {
 61     ZmPreferencesPage.prototype.reset.apply(this, arguments);
 62 
 63     this._duration = 0;
 64     var noDuration = true;
 65     if (this._startDateVal) {
 66         noDuration = (this._startDateVal.value == null || this._startDateVal.value == "");
 67         this._initStartEndDisplayFields();
 68     }
 69 
 70 
 71     var cbox = this.getFormObject(ZmSetting.VACATION_MSG_ENABLED);
 72     if (cbox) {
 73         this._handleEnableVacationMsg(cbox, noDuration);
 74         // HandleEnableVacationMsg will alter other (non-persisted) settings - update
 75         // their 'original' values so the section will not be thought dirty upon exit
 76         this._updateOriginalValue(ZmSetting.VACATION_DURATION_ENABLED);
 77         this._updateOriginalValue(ZmSetting.VACATION_CALENDAR_ENABLED);
 78     }
 79     this._initialAllDayFlag   = this._allDayCheckbox ? this._allDayCheckbox.isSelected() : true;
 80     this._updateOriginalValue(ZmSetting.VACATION_DURATION_ALL_DAY);
 81 
 82 
 83 	this._setPopDownloadSinceControls();
 84 
 85 	if (this._blackListControl && this._whiteListControl) {
 86 		this._blackListControl.reset();
 87 		this._whiteListControl.reset();
 88 	}
 89 };
 90 
 91 ZmMailPrefsPage.prototype.isDirty =
 92 function() {
 93 	var isDirty = ZmPreferencesPage.prototype.isDirty.call(this);
 94 	return (!isDirty) ? this.isWhiteBlackListDirty() : isDirty;
 95 };
 96 
 97 ZmMailPrefsPage.prototype.isWhiteBlackListDirty =
 98 function() {
 99 	if (this._blackListControl && this._whiteListControl) {
100 		return this._blackListControl.isDirty() ||
101 			   this._whiteListControl.isDirty();
102 	}
103 	return false;
104 };
105 
106 ZmMailPrefsPage.prototype.addCommand =
107 function(batchCmd) {
108 	if (this.isWhiteBlackListDirty()) {
109 		var soapDoc = AjxSoapDoc.create("ModifyWhiteBlackListRequest", "urn:zimbraAccount");
110 		this._blackListControl.setSoapContent(soapDoc, "blackList");
111 		this._whiteListControl.setSoapContent(soapDoc, "whiteList");
112 
113 		var respCallback = new AjxCallback(this, this._handleResponseModifyWhiteBlackList);
114 		batchCmd.addNewRequestParams(soapDoc, respCallback);
115 	}
116 };
117 
118 ZmMailPrefsPage.prototype._handleResponseModifyWhiteBlackList =
119 function(result) {
120 	this._blackListControl.saveLocal();
121 	this._whiteListControl.saveLocal();
122 };
123 
124 ZmMailPrefsPage.prototype._setPopDownloadSinceControls =
125 function() {
126 	var popDownloadSinceValue = this.getFormObject(ZmSetting.POP_DOWNLOAD_SINCE_VALUE);
127     var value = appCtxt.get(ZmSetting.POP_DOWNLOAD_SINCE);
128 	if (popDownloadSinceValue && value) {
129 		var date = AjxDateFormat.parse("yyyyMMddHHmmss'Z'", value);
130 		date.setMinutes(date.getMinutes() - date.getTimezoneOffset());
131 
132 		popDownloadSinceValue.setText(AjxMessageFormat.format(ZmMsg.externalAccessPopCurrentValue, date));
133         popDownloadSinceValue.setVisible(true);
134 	}  else if( popDownloadSinceValue ) {
135         popDownloadSinceValue.setVisible(false);
136     }
137 
138 	var popDownloadSince = this.getFormObject(ZmSetting.POP_DOWNLOAD_SINCE);
139 	if (popDownloadSince) {
140 		popDownloadSince.setSelectedValue(appCtxt.get(ZmSetting.POP_DOWNLOAD_SINCE));
141 	}
142 };
143 
144 ZmMailPrefsPage.prototype._createControls =
145 function() {
146     AjxDispatcher.require(["MailCore", "CalendarCore"]);
147 	ZmPreferencesPage.prototype._createControls.apply(this, arguments);
148 
149 	this._sId = this._htmlElId + "_startMiniCal";
150 	this._eId = this._htmlElId + "_endMiniCal";
151 
152 
153     this._startDateField = Dwt.byId(this._htmlElId + "_VACATION_FROM1");
154 	this._endDateField = Dwt.byId(this._htmlElId + "_VACATION_UNTIL1");
155 
156 	if (this._startDateField && this._endDateField) {
157 		this._startDateVal = Dwt.byId(this._htmlElId + "_VACATION_FROM");
158 		this._endDateVal = Dwt.byId(this._htmlElId + "_VACATION_UNTIL");
159         if(this._startDateVal.value.length < 15){
160             this._startDateVal.value = appCtxt.get(ZmSetting.VACATION_FROM);
161         }
162         if(this._endDateVal.value.length < 15){
163             this._endDateVal.value = appCtxt.get(ZmSetting.VACATION_UNTIL);            
164         }
165 
166 		this._formatter = new AjxDateFormat("yyyyMMddHHmmss'Z'");
167 
168 	    var timeSelectListener = new AjxListener(this, this._timeChangeListener);
169 	    this._startTimeSelect = new DwtTimeInput(this, DwtTimeInput.START);
170 	    this._startTimeSelect.reparentHtmlElement(this._htmlElId + "_VACATION_FROM_TIME");
171 	    this._endTimeSelect = new DwtTimeInput(this, DwtTimeInput.END);
172 	    this._endTimeSelect.reparentHtmlElement(this._htmlElId + "_VACATION_UNTIL_TIME");
173         this._startTimeSelect.addChangeListener(timeSelectListener);
174         this._endTimeSelect.addChangeListener(timeSelectListener);
175 
176         var noDuration = (this._startDateVal.value == null || this._startDateVal.value == "");
177         this._initStartEndDisplayFields();
178 
179 		var dateButtonListener = new AjxListener(this, this._dateButtonListener);
180 		var dateCalSelectionListener = new AjxListener(this, this._dateCalSelectionListener);
181 		var dateFieldListener = AjxCallback.simpleClosure(this._dateFieldListener, this);
182 
183 		this._startDateButton = ZmCalendarApp.createMiniCalButton(this, this._sId, dateButtonListener, dateCalSelectionListener);
184 		this._endDateButton = ZmCalendarApp.createMiniCalButton(this, this._eId, dateButtonListener, dateCalSelectionListener);
185 
186 		Dwt.setHandler(this._startDateField, DwtEvent.ONBLUR, dateFieldListener);
187 		Dwt.setHandler(this._endDateField, DwtEvent.ONBLUR, dateFieldListener);
188 
189 		this._durationCheckbox = this.getFormObject(ZmSetting.VACATION_DURATION_ENABLED);
190         this._allDayCheckbox = this.getFormObject(ZmSetting.VACATION_DURATION_ALL_DAY);
191         // Base initial _allDayCheckbox.checked on whether the start is Midnight and end
192         // is 23:59:59 (which fortunately cannot be directly specified by the user).
193         // Do this because we do not have any linkage to the OOO appt.
194         var prefStartDate = this._getPrefDate(ZmSetting.VACATION_FROM);
195         var prefEndDate   = this._getPrefDate(ZmSetting.VACATION_UNTIL);
196         if ((prefStartDate != null) && (prefEndDate != null)) {
197             var startDate = new Date(prefStartDate);
198             startDate.setHours(0, 0, 0, 0);  //we set this just to compare - took me a while to figure out
199             var endDate = new Date(prefEndDate);
200             endDate.setHours(23, 59, 59, 0); //we set this just to compare.
201             this._initialAllDayFlag = ((prefStartDate.getTime() == startDate.getTime()) &&
202                                        (prefEndDate.getTime()   == endDate.getTime()));
203         } else {
204             this._initialAllDayFlag = true;
205         }
206         this._allDayCheckbox.setSelected(this._initialAllDayFlag);
207         this._updateOriginalValue(ZmSetting.VACATION_DURATION_ALL_DAY);
208 	}
209 
210 
211 
212 	var cbox = this.getFormObject(ZmSetting.VACATION_MSG_ENABLED);
213 	if (cbox) {
214 		this._handleEnableVacationMsg(cbox, noDuration);
215         // HandleEnableVacationMsg will alter other (non-persisted) settings - update
216         // their 'orginal' values so the section will not be thought dirty upon exit
217         this._updateOriginalValue(ZmSetting.VACATION_DURATION_ENABLED);
218         this._updateOriginalValue(ZmSetting.VACATION_CALENDAR_ENABLED);
219 	}
220 
221 	// enable downloadSince appropriately based on presence of downloadSinceEnabled
222 	var downloadSinceCbox = this.getFormObject(ZmSetting.POP_DOWNLOAD_SINCE_ENABLED);
223 	if (downloadSinceCbox) {
224 		var downloadSince = this.getFormObject(ZmSetting.POP_DOWNLOAD_SINCE);
225 		if (downloadSince) {
226 			var enabled = downloadSince.getValue() != "";
227 			downloadSinceCbox.setSelected(enabled);
228 			downloadSince.setEnabled(enabled);
229 		}
230 	}
231 
232 	// Following code makes child nodes as siblings to separate the
233 	// event-handling between labels and input
234 	var inputId = DwtId.makeId(ZmId.WIDGET_INPUT, ZmId.OP_MARK_READ);
235 	var inputPlaceholder = Dwt.byId(inputId);
236 
237 	if (inputPlaceholder) {
238 		var radioButton = DwtControl.findControl(inputPlaceholder);
239 		var index = AjxUtil.indexOf(radioButton.parent.getChildren(),
240 		                            radioButton);
241 		var inputControl = new DwtInputField({
242 			parent: radioButton.parent,
243 			index: index + 1,
244 			id: inputId,
245 			size: 4,
246 			hint: '0',
247 		});
248 		inputControl.replaceElement(inputPlaceholder);
249 		inputControl.setDisplay(Dwt.DISPLAY_INLINE);
250 
251 		// Toggle the setting when editing the time input; it already recieves
252 		// focus on click, so this is mainly for keyboard navigation. (Except
253 		// on IE8, where the 'oninput' event doesn't work.)
254 		inputControl.setHandler(DwtEvent.ONINPUT, function(ev) {
255 			if (inputControl.getValue()) {
256 				this.setFormValue(ZmSetting.MARK_MSG_READ,
257 				                  ZmSetting.MARK_READ_TIME);
258 			}
259 		}.bind(this));
260 
261 		// If pref's value is number of seconds, populate the input
262 		var value = appCtxt.get(ZmSetting.MARK_MSG_READ);
263 		if (value > 0) {
264 		    inputControl.setValue(value);
265 		}
266 	}
267 
268 	var composeMore = Dwt.byId(this.getHTMLElId() + '_compose_more');
269 	if (composeMore) {
270 		var links = AjxUtil.toArray(composeMore.getElementsByTagName('A'));
271 
272 		for (var i = 0; i < links.length; i++) {
273 			var link = links[i];
274 			var accountsText = new DwtText({
275 				parent: this,
276 				className: 'FakeAnchor'
277 			});
278 			accountsText.setContent(AjxUtil.getInnerText(link));
279 			accountsText.setDisplay(Dwt.DISPLAY_INLINE);
280 			accountsText.replaceElement(link);
281 			accountsText._setEventHdlrs([DwtEvent.ONCLICK]);
282 			accountsText.addListener(DwtEvent.ONCLICK,
283 									 skin.gotoPrefs.bind(skin, "ACCOUNTS"));
284 		}
285 	}
286 
287 	this._setPopDownloadSinceControls();
288 };
289 
290 
291 ZmMailPrefsPage.prototype._getPrefDate =
292 function(settingName) {
293     var prefDate = null;
294     var prefDateText = appCtxt.get(settingName);
295     if (prefDateText && (prefDateText != "")) {
296         prefDate = this._formatter.parse(AjxDateUtil.dateGMT2Local(prefDateText));
297     }
298     return prefDate;
299 }
300 
301 ZmMailPrefsPage.prototype._initStartEndDisplayFields =
302 function() {
303     var startDate = new Date();
304     startDate.setHours(0,0,0,0);
305     this._initDateTimeDisplayField(this._startDateVal, this._startTimeSelect,
306         this._startDateField, startDate);
307 
308     var endDate = new Date(startDate);
309     // Defaulting to 11:59 PM. Ignored for all day
310     endDate.setHours(23,59,0,0);
311     this._initDateTimeDisplayField(this._endDateVal, this._endTimeSelect,
312         this._endDateField, endDate);
313 
314     this._calcDuration();
315 }
316 
317 ZmMailPrefsPage.prototype._initDateTimeDisplayField =
318 function(dateVal, timeSelect, dateField, date) {
319     var dateValue = (dateVal.value != null && dateVal.value != "")
320         ? (this._formatter.parse(dateVal.value)) : date;
321     timeSelect.set(dateValue);
322     dateValue = timeSelect.getValue(dateValue);
323     dateVal.value = this._formatter.format(dateValue);
324     dateField.value = AjxDateUtil.simpleComputeDateStr(dateValue);
325 }
326 
327 ZmMailPrefsPage.prototype._updateOriginalValue =
328 function(id) {
329     var cbox = this.getFormObject(id);
330     if (cbox) {
331         var pref = appCtxt.getSettings().getSetting(id);
332         pref.origValue = cbox.isSelected();
333         pref.value = pref.origValue;
334     }
335 };
336 
337 ZmMailPrefsPage.prototype._dateButtonListener =
338 function(ev) {
339 	var calDate = ev.item == this._startDateButton
340 		? this._fixAndGetValidDateFromField(this._startDateField)
341 		: this._fixAndGetValidDateFromField(this._endDateField);
342 
343 	var menu = ev.item.getMenu();
344 	var cal = menu.getItem(0);
345 	cal.setDate(calDate, true);
346 	ev.item.popup();
347 };
348 
349 ZmMailPrefsPage.prototype._fixAndGetValidDateFromField =
350 function(field) {
351 	var d = AjxDateUtil.simpleParseDateStr(field.value);
352 	if (!d || isNaN(d)) {
353 		d = new Date();
354 		field.value = AjxDateUtil.simpleComputeDateStr(d);
355 	}
356 	return d;
357 };
358 
359 ZmMailPrefsPage.prototype._dateCalSelectionListener =
360 function(ev) {
361 	var parentButton = ev.item.parent.parent;
362 
363 	var newDate = AjxDateUtil.simpleComputeDateStr(ev.detail);
364 
365 	if (parentButton == this._startDateButton) {
366 		this._startDateField.value = newDate;
367 	} else {
368 		if (ev.detail < new Date()) { return; }
369 		this._endDateField.value = newDate;
370 	}
371 
372     var sd = this._fixAndGetValidDateFromField(this._startDateField);
373     var ed = this._fixAndGetValidDateFromField(this._endDateField);
374     if(this._startTimeSelect && this._endTimeSelect){
375         sd = this._startTimeSelect.getValue(sd);
376         ed = this._endTimeSelect.getValue(ed);
377     }
378 
379 	
380 	this._fixDates(sd, ed, parentButton == this._endDateButton);
381     this._calcDuration();
382 
383 	if (this._durationCheckbox.isSelected()) {
384 		this._startDateVal.value = this._formatter.format(sd);
385         this._endDateVal.value = this._formatter.format(ed);
386 	}
387 };
388 
389 ZmMailPrefsPage.prototype._calcDuration =
390 function() {
391     var sd = this._fixAndGetValidDateFromField(this._startDateField);
392     var ed = this._fixAndGetValidDateFromField(this._endDateField);
393 
394     var sdDay = new Date(sd.getTime());
395     var edDay = new Date(ed.getTime());
396     sdDay.setHours(0, 0, 0, 0);
397     edDay.setHours(0, 0, 0, 0);
398     this._duration = edDay.getTime() - sdDay.getTime();
399     DBG.println(AjxDebug.DBG3, "ZmMailPrefsPage._calcDuration: Start=" + AjxDateUtil.simpleComputeDateStr(sdDay) +
400                 ", End=" + AjxDateUtil.simpleComputeDateStr(edDay) + ", duration=" + this._duration);
401 }
402 
403 ZmMailPrefsPage.prototype._dateFieldListener =
404 function(ev) {
405 	var sd = this._fixAndGetValidDateFromField(this._startDateField);
406 	var ed = this._fixAndGetValidDateFromField(this._endDateField);
407     if(this._startTimeSelect && this._endTimeSelect){
408         sd = this._startTimeSelect.getValue(sd);
409         ed = this._endTimeSelect.getValue(ed);
410     }
411     this._fixDates(sd, ed, DwtUiEvent.getTarget(ev) == this._endDateField);
412     this._calcDuration();
413     this._startDateVal.value = this._formatter.format(AjxDateUtil.simpleParseDateStr(this._startDateField.value));
414     this._endDateVal.value = this._formatter.format(AjxDateUtil.simpleParseDateStr(this._endDateField.value));
415 };
416 
417 ZmMailPrefsPage.prototype._timeChangeListener =
418 function(ev) {
419    var stDate = AjxDateUtil.simpleParseDateStr(this._startDateField.value);
420    var endDate = AjxDateUtil.simpleParseDateStr(this._endDateField.value);
421    stDate = this._startTimeSelect.getValue(stDate);
422    endDate = this._endTimeSelect.getValue(endDate);
423    this._startDateVal.value = this._formatter.format(stDate);
424    this._endDateVal.value = this._formatter.format(endDate);
425 };
426 
427 /* Fixes the field values so that end date always is later than or equal to start date
428  * @param startDate	{Date}	The value of the start date field or calendar selection
429  * @param endDate	{Date}	The value of the end date field or calendar selection
430  * @param endModified {boolean}	endDate was changed - don't preserve the duration
431 */
432 ZmMailPrefsPage.prototype._fixDates =
433 function(startDate, endDate, endModified) {
434     var startDateNoTimeOffset = new Date(startDate.getTime());
435     startDateNoTimeOffset.setHours(0,0,0,0);
436     var endDateNoTimeOffset   = new Date(endDate.getTime());
437     endDateNoTimeOffset.setHours(0,0,0,0);
438 
439 	if (startDateNoTimeOffset >= endDateNoTimeOffset) {
440         // Start date after end date
441 		if (endModified) {
442             // EndDate was set to be prior to the startDate - set them to be equal
443 			this._startDateField.value = AjxDateUtil.simpleComputeDateStr(endDate);
444             DBG.println(AjxDebug.DBG3, "ZmMailPrefsPage._fixDates: endModified, set start == end");
445 		} else {
446 			// StartDate modified - preserve the duration
447             var newEndDate = new Date(startDateNoTimeOffset.getTime() + this._duration);
448 			this._endDateField.value = AjxDateUtil.simpleComputeDateStr(newEndDate);
449             DBG.println(AjxDebug.DBG3, "ZmMailPrefsPage._fixDates: Start=" + AjxDateUtil.simpleComputeDateStr(startDate) +
450                 ", End=" + AjxDateUtil.simpleComputeDateStr(newEndDate) + ", duration=" + this._duration);
451 		}
452 	}
453 };
454 
455 ZmMailPrefsPage.prototype._setupCheckbox =
456 function(id, setup, value) {
457 	var cbox = ZmPreferencesPage.prototype._setupCheckbox.apply(this, arguments);
458 	if (id == ZmSetting.VACATION_CALENDAR_ENABLED ||
459         id == ZmSetting.VACATION_DURATION_ENABLED ||
460         id == ZmSetting.VACATION_DURATION_ALL_DAY)
461 	{
462 		cbox.addSelectionListener(new AjxListener(this, this._handleEnableVacationMsg, [cbox, false, id]));
463 	}
464 	return cbox;
465 };
466 
467 ZmMailPrefsPage.prototype._setupRadioGroup =
468 function(id, setup, value) {
469 	var control = ZmPreferencesPage.prototype._setupRadioGroup.apply(this, arguments);
470 	if (id == ZmSetting.POP_DOWNLOAD_SINCE) {
471 		var radioGroup = this.getFormObject(id);
472 		var radioButton = radioGroup.getRadioButtonByValue(ZmMailApp.POP_DOWNLOAD_SINCE_NO_CHANGE);
473 		radioButton.setVisible(false);
474 	}
475     else if (id == ZmSetting.VACATION_MSG_ENABLED) {
476         var radioGroup = this.getFormObject(id);
477         radioGroup.addSelectionListener(new AjxListener(this, this._handleEnableVacationMsg, [radioGroup, false, id]));
478     }
479 	return control;
480 };
481 
482 ZmMailPrefsPage.prototype._setupCustom =
483 function(id, setup, value) {
484 	if (id == ZmSetting.MAIL_BLACKLIST) {
485 		this._blackListControl = new ZmWhiteBlackList(this, id, "BlackList");
486 		return this._blackListControl;
487 	}
488 
489 	if (id == ZmSetting.MAIL_WHITELIST) {
490 		this._whiteListControl = new ZmWhiteBlackList(this, id, "WhiteList");
491 		return this._whiteListControl;
492 	}
493 };
494 
495 ZmMailPrefsPage.prototype._handleResponseLoadWhiteBlackList =
496 function(result) {
497 	var resp = result.getResponse().GetWhiteBlackListResponse;
498 	this._blackListControl.loadFromJson(resp.blackList[0].addr);
499 	this._whiteListControl.loadFromJson(resp.whiteList[0].addr);
500 };
501 
502 
503 //
504 // Protected methods
505 //
506 
507 ZmMailPrefsPage.prototype._handleEnableVacationMsg =
508 function(cbox, noDuration, id, evt) {
509 	var textarea = this.getFormObject(ZmSetting.VACATION_MSG);
510     var extTextarea = this.getFormObject(ZmSetting.VACATION_EXTERNAL_MSG);
511     var externalTypeSelect = this.getFormObject(ZmSetting.VACATION_EXTERNAL_SUPPRESS);
512 	if (textarea) {
513         if (id == ZmSetting.VACATION_DURATION_ALL_DAY) {
514             this._enableDateTimeControls(true);
515         } else if (id == ZmSetting.VACATION_DURATION_ENABLED) {
516             this._allDayCheckbox.setEnabled(cbox.isSelected());
517             this._enableDateTimeControls(cbox.isSelected());
518             var calCheckBox = this.getFormObject(ZmSetting.VACATION_CALENDAR_ENABLED);
519             calCheckBox.setEnabled(cbox.isSelected());
520             var calendarType = this.getFormObject(ZmSetting.VACATION_CALENDAR_TYPE);
521             calendarType.setEnabled(calCheckBox.isSelected() && cbox.isSelected());
522 		}else if(id == ZmSetting.VACATION_CALENDAR_ENABLED){
523             var calendarType = this.getFormObject(ZmSetting.VACATION_CALENDAR_TYPE);
524             calendarType.setEnabled(cbox.isSelected());
525          }else {
526 
527             // MESSAGE_ENABLED, main On/Off switch
528 			var enabled = cbox.getSelectedValue()=="true";
529 			textarea.setEnabled(enabled);
530 
531             this._durationCheckbox.setEnabled(enabled);
532             var val = this._startDateVal.value && enabled ? true : false;
533             this._durationCheckbox.setSelected((val && !noDuration));
534 
535             var calCheckBox = this.getFormObject(ZmSetting.VACATION_CALENDAR_ENABLED);
536             calCheckBox.setEnabled((this._durationCheckbox.isSelected() || appCtxt.get(ZmSetting.VACATION_DURATION_ENABLED)) && enabled);
537             calCheckBox.setSelected(enabled && (appCtxt.get(ZmSetting.VACATION_CALENDAR_APPT_ID) != "-1"));
538 
539             var calendarType = this.getFormObject(ZmSetting.VACATION_CALENDAR_TYPE);
540             calendarType.setEnabled(calCheckBox.isSelected() && this._durationCheckbox.isSelected() && enabled);
541             externalTypeSelect.setEnabled(enabled);
542             extTextarea.setEnabled(enabled);
543             this._allDayCheckbox.setEnabled(enabled && this._durationCheckbox.isSelected());
544             this._enableDateTimeControls(enabled && this._durationCheckbox.isSelected());
545 		}
546 	}
547 };
548 
549 ZmMailPrefsPage.prototype._enableDateTimeControls =
550 function(enableDate) {
551     this._setEnabledStartDate(enableDate);
552     this._setEnabledEndDate(enableDate);
553     var enableTime = enableDate && !this._allDayCheckbox.isSelected();
554     this._startTimeSelect.setEnabled(enableTime);
555     this._endTimeSelect.setEnabled(enableTime);
556 }
557 
558 ZmMailPrefsPage.prototype._setEnabledStartDate =
559 function(val) {
560 	var condition = val && this._durationCheckbox.isSelected();
561 	this._startDateField.disabled = !condition;
562 	this._startDateButton.setEnabled(condition);
563     var stDateVal = AjxDateUtil.simpleParseDateStr(this._startDateField.value);
564     if(this._startTimeSelect){stDateVal = this._startTimeSelect.getValue(stDateVal);}
565 	this._startDateVal.value = (!condition)
566 		? "" : (this._formatter.format(stDateVal));
567 };
568 
569 ZmMailPrefsPage.prototype._setEnabledEndDate =
570 function(val) {
571 	//this._endDateCheckbox.setEnabled(val);
572 	var condition = val && this._durationCheckbox.isSelected();
573 	this._endDateField.disabled = !condition;
574 	this._endDateButton.setEnabled(condition);
575     var endDateVal = AjxDateUtil.simpleParseDateStr(this._endDateField.value);
576     if(this._endTimeSelect){endDateVal = this._endTimeSelect.getValue(endDateVal);}
577 	this._endDateVal.value = (!condition)
578 		? "" : (this._formatter.format(endDateVal));
579 };
580 
581 
582 ZmMailPrefsPage.prototype.getPreSaveCallback =
583 function() {
584 	return new AjxCallback(this, this._preSave);
585 };
586 
587 ZmMailPrefsPage.prototype._preSave =
588 function(callback) {
589     if (this._startDateField && this._endDateField) {
590         var stDate = AjxDateUtil.simpleParseDateStr(this._startDateField.value);
591         var endDate = AjxDateUtil.simpleParseDateStr(this._endDateField.value);
592 
593         var allDay = this._allDayCheckbox.isSelected();
594         if (!allDay) {
595             // Add in time fields if not all-day
596             stDate = this._startTimeSelect.getValue(stDate);
597             endDate = this._endTimeSelect.getValue(endDate);
598         }  else  {
599             // For the prefs, need to set the all day end time
600             endDate.setHours(23, 59, 59, 0);
601         }
602         if (this._startDateField.disabled) {
603             this._startDateVal.value = "";
604         } else {
605             this._startDateVal.value = this._formatter.format(stDate);
606         }
607         if (this._endDateField.disabled) {
608              this._endDateVal.value = "";
609         } else {
610             this._endDateVal.value = this._formatter.format(endDate);
611         }
612 
613         this._oldStartDate = appCtxt.get(ZmSetting.VACATION_FROM);
614         this._oldEndDate   = appCtxt.get(ZmSetting.VACATION_UNTIL);
615     }
616 
617     if (callback) {
618         callback.run();
619     }
620 };
621 
622 ZmMailPrefsPage.prototype.getPostSaveCallback =
623 function() {
624 	return new AjxCallback(this, this._postSave);
625 };
626 
627 ZmMailPrefsPage.prototype._postSave =
628 function(changed) {
629     var form = this.getFormObject(ZmSetting.POLLING_INTERVAL);
630 	var polling = form && form.getSelectedOption() && form.getSelectedOption().getDisplayValue();
631 	if (polling) {
632 		// A polling value is specified - apply it
633 		if (polling == ZmMsg.pollInstant) {
634 			// Instant notify is selected
635 			if (appCtxt.get(ZmSetting.INSTANT_NOTIFY) && !appCtxt.getAppController().getInstantNotify()) {
636 				// Instant notify is not operating - Turn it on
637 				appCtxt.getAppController().setInstantNotify(true);
638 			}
639 		}  else if (appCtxt.getAppController().getInstantNotify()) {
640 			// Instant notify is not selected, but is currently operating - Turn it off
641 			appCtxt.getAppController().setInstantNotify(false);
642 		}
643 	}
644 
645 	var vacationChangePrefs = [
646 		ZmSetting.VACATION_MSG_ENABLED,
647 		ZmSetting.VACATION_FROM,
648 		ZmSetting.VACATION_UNTIL
649 	];
650 
651 	var modified = false;
652 	for (var i = 0; i < vacationChangePrefs.length; i++) {
653 		if (changed[vacationChangePrefs[i]]) {
654 			modified = true;
655 			break;
656 		}
657 	}
658 	if (modified) {
659         var soapDoc = AjxSoapDoc.create("ModifyPrefsRequest", "urn:zimbraAccount");
660         var node = soapDoc.set("pref", "TRUE");
661         node.setAttribute("name", "zimbraPrefOutOfOfficeStatusAlertOnLogin");
662         appCtxt.getAppController().sendRequest({soapDoc:soapDoc, asyncMode:true});
663     }
664 
665 	//if old end date was greater than today then fetch appt id from metadata and delete the old appointment
666 	var now = new Date();
667 	if (appCtxt.get(ZmSetting.VACATION_CALENDAR_APPT_ID) != "-1" && this._formatter && this._oldEndDate && 
668 		this._formatter.parse(AjxDateUtil.dateGMT2Local(this._oldEndDate)) > now)
669 	{
670 		ZmAppt.loadById(appCtxt.get(ZmSetting.VACATION_CALENDAR_APPT_ID),new AjxCallback(this, this._oooDeleteApptCallback));
671 	}
672     if (this._durationCheckbox && this._durationCheckbox.isSelected()) {
673         //Create calendar appointments for this out of office request.
674 	    var calCheckBox = this.getFormObject(ZmSetting.VACATION_CALENDAR_ENABLED);
675 	    if (calCheckBox && calCheckBox.isSelected()) {
676             var stDate  = this._getPrefDate(ZmSetting.VACATION_FROM);
677             var endDate = this._getPrefDate(ZmSetting.VACATION_UNTIL);
678 		    var allDay = this._allDayCheckbox.isSelected();
679             if (stDate != null && endDate != null) {
680                 if (allDay) {
681                     // Strip the time of day information - calendar view
682                     // creates all-day appt with just day info
683                     endDate.setHours(0,0,0,0);
684                 }
685 	            var calController = appCtxt.getApp(ZmApp.CALENDAR).getCalController();
686 	            calController.createAppointmentFromOOOPref(stDate,endDate, allDay, new AjxCallback(this, this._oooApptCallback));
687             }
688         }
689     }
690 };
691 
692 ZmMailPrefsPage.prototype._oooDeleteApptCallback = function(appt){
693 	if (appt) {
694 		var calController = appCtxt.getApp(ZmApp.CALENDAR).getCalController();
695 		calController._continueDelete(appt, ZmCalItem.MODE_DELETE);
696 		appCtxt.set(ZmSetting.VACATION_CALENDAR_APPT_ID, "-1");
697 	}
698 };
699 
700 ZmMailPrefsPage.prototype._oooApptCallback = function(response){
701 	//store the appt id as meta data
702 	if (response && response.apptId) {
703 		appCtxt.set(ZmSetting.VACATION_CALENDAR_APPT_ID, response.apptId);
704 	}
705     appCtxt.setStatusMsg(ZmMsg.oooStatus);
706 }
707 
708 ZmMailPrefsPage.prototype._convModeChangeYesCallback =
709 function(dialog) {
710 	dialog.popdown();
711 	window.onbeforeunload = null;
712 	var url = AjxUtil.formatUrl();
713 	DBG.println(AjxDebug.DBG1, "Conv mode change, redirect to: " + url);
714 	ZmZimbraMail.sendRedirect(url); // redirect to self to force reload
715 };
716 
717 // ??? SHOULD THIS BE IN A NEW FILE?       ???
718 // ??? IT IS ONLY USED BY ZmMailPrefsPage. ???
719 /**
720  * Custom control used to handle adding/removing addresses for white/black list
721  *
722  * @param parent
723  * @param id
724  *
725  * @private
726  */
727 ZmWhiteBlackList = function(parent, id, templateId) {
728 	DwtComposite.call(this, {parent:parent});
729 
730 	this._settingId = id;
731     switch(id) {
732         case ZmSetting.MAIL_BLACKLIST:
733             this._max = appCtxt.get(ZmSetting.MAIL_BLACKLIST_MAX_NUM_ENTRIES);
734             break;
735         case ZmSetting.MAIL_WHITELIST:
736             this._max = appCtxt.get(ZmSetting.MAIL_WHITELIST_MAX_NUM_ENTRIES);
737             break;
738         case ZmSetting.TRUSTED_ADDR_LIST:
739             this._max = appCtxt.get(ZmSetting.TRUSTED_ADDR_LIST_MAX_NUM_ENTRIES);
740             break;
741     }
742 	this._setContent(templateId);
743 
744 	this._list = [];
745 	this._add = {};
746 	this._remove = {};
747 };
748 
749 ZmWhiteBlackList.prototype = new DwtComposite;
750 ZmWhiteBlackList.prototype.constructor = ZmWhiteBlackList;
751 ZmWhiteBlackList.prototype.isZmWhiteBlackList = true;
752 
753 ZmWhiteBlackList.prototype.toString =
754 function() {
755 	return "ZmWhiteBlackList";
756 };
757 
758 ZmWhiteBlackList.prototype.reset =
759 function() {
760 	this._inputEl.setValue("");
761 	this._listView.set(AjxVector.fromArray(this._list).clone(), null, true);
762 	this._add = {};
763 	this._remove = {};
764 
765 	this.updateNumUsed();
766 };
767 
768 ZmWhiteBlackList.prototype.getValue =
769 function() {
770     return this._listView.getList().clone().getArray().join(',').replace(';', ',').split(',');
771 };
772 
773 
774 ZmWhiteBlackList.prototype.loadFromJson =
775 function(data) {
776 	if (data) {
777         for (var i = 0; i < data.length; i++) {
778             var content = AjxUtil.isSpecified(data[i]._content) ? data[i]._content : data[i];
779             if(content){
780 			    var item = this._addEmail(content);
781 			    this._list.push(item);
782             }
783 		}
784 	}
785 	this.updateNumUsed();
786 };
787 
788 ZmWhiteBlackList.prototype.setSoapContent =
789 function(soapDoc, method) {
790 	if (!this.isDirty()) { return; }
791 
792 	var methodEl = soapDoc.set(method);
793 
794 	for (var i in this._add) {
795 		var addrEl = soapDoc.set("addr", i, methodEl);
796 		addrEl.setAttribute("op", "+");
797 	}
798 
799 	for (var i in this._remove) {
800 		var addrEl = soapDoc.set("addr", i, methodEl);
801 		addrEl.setAttribute("op", "-");
802 	}
803 };
804 
805 ZmWhiteBlackList.prototype.isDirty =
806 function() {
807 	var isDirty = false;
808 
809 	for (var i in this._add) {
810 		isDirty = true;
811 		break;
812 	}
813 
814 	if (!isDirty) {
815 		for (var i in this._remove) {
816 			isDirty = true;
817 			break;
818 		}
819 	}
820 
821 	return isDirty;
822 };
823 
824 ZmWhiteBlackList.prototype.saveLocal =
825 function() {
826 	if (this.isDirty()) {
827 		this._list = this._listView.getList().clone().getArray();
828 		this._add = {};
829 		this._remove = {};
830 	}
831 };
832 
833 ZmWhiteBlackList.prototype.updateNumUsed =
834 function() {
835 	this._numUsedText.innerHTML = AjxMessageFormat.format(ZmMsg.whiteBlackNumUsed, [this._listView.size(), this._max]);
836 };
837 
838 ZmWhiteBlackList.prototype._setContent =
839 function(templateId) {
840 	this.getHtmlElement().innerHTML = AjxTemplate.expand("prefs.Pages#"+templateId, {id:this._htmlElId});
841 
842 	var id = this._htmlElId + "_EMAIL_ADDRESS";
843 	var el = document.getElementById(id);
844 	this._inputEl = new DwtInputField({parent:this, parentElement:id, size:35, hint:ZmMsg.enterEmailAddressOrDomain});
845 	this._inputEl.getInputElement().style.width = "210px";
846 	this._inputEl._showHint();
847 	this._inputEl.addListener(DwtEvent.ONKEYUP, new AjxListener(this, this._handleKeyUp));
848 	this.parent._addControlTabIndex(el, this._inputEl);
849 
850 	id = this._htmlElId + "_LISTVIEW";
851 	el = document.getElementById(id);
852 	this._listView = new DwtListView({parent:this, parentElement:id});
853 	this._listView.addClassName("ZmWhiteBlackList");
854 	this.parent._addControlTabIndex(el, this._listView);
855 
856 	id = this._htmlElId + "_ADD_BUTTON";
857 	el = document.getElementById(id);
858 	var addButton = new DwtButton({parent:this, parentElement:id});
859 	addButton.setText(ZmMsg.add);
860 	addButton.addSelectionListener(new AjxListener(this, this._addListener));
861 	this.parent._addControlTabIndex(el, addButton);
862 
863 	id = this._htmlElId + "_REMOVE_BUTTON";
864 	el = document.getElementById(id);
865 	var removeButton = new DwtButton({parent:this, parentElement:id});
866 	removeButton.setText(ZmMsg.remove);
867 	removeButton.addSelectionListener(new AjxListener(this, this._removeListener));
868 	this.parent._addControlTabIndex(el, removeButton);
869 
870 	id = this._htmlElId + "_NUM_USED";
871 	this._numUsedText = document.getElementById(id);
872 };
873 
874 ZmWhiteBlackList.prototype._addEmail =
875 function(addr) {
876 	var item = new ZmWhiteBlackListItem(addr);
877 	this._listView.addItem(item, null, true);
878 	return item;
879 };
880 
881 ZmWhiteBlackList.prototype._addListener =
882 function() {
883 	if (this._listView.size() >= this._max) {
884 		var dialog = appCtxt.getMsgDialog();
885 		dialog.setMessage(ZmMsg.errorWhiteBlackListExceeded);
886 		dialog.popup();
887 		return;
888 	}
889 
890 	var val,
891         items = AjxStringUtil.trim(this._inputEl.getValue(), true);
892 	if (items.length) {
893         items = AjxStringUtil.split(items, [',', ';', ' ']);
894         for(var i=0; i<items.length; i++) {
895             val = items[i];
896             if(val) {
897                 this._addEmail(AjxStringUtil.htmlEncode(val));
898                 if (!this._add[val]) {
899                     this._add[val] = true;
900                 }
901             }
902         }
903 		this._inputEl.setValue("", true);
904 		this._inputEl.blur();
905 		this._inputEl.focus();
906 
907 		this.updateNumUsed();
908 	}
909 };
910 
911 ZmWhiteBlackList.prototype._removeListener =
912 function() {
913 	var items = this._listView.getSelection();
914 	for (var i = 0; i < items.length; i++) {
915 		var item = items[i];
916 		this._listView.removeItem(item, true);
917 		var addr = item.toString();
918 		if (this._add[addr]) {
919 			delete this._add[addr];
920 		} else {
921 			this._remove[addr] = true;
922 		}
923 	}
924 
925 	this.updateNumUsed();
926 };
927 
928 ZmWhiteBlackList.prototype._handleKeyUp =
929 function(ev) {
930 	var charCode = DwtKeyEvent.getCharCode(ev);
931 	if (charCode == 13 || charCode == 3) {
932 		this._addListener();
933 	}
934 };
935 
936 // Helper
937 ZmWhiteBlackListItem = function(addr) {
938 	this.addr = addr;
939 	this.id = Dwt.getNextId();
940 };
941 
942 ZmWhiteBlackListItem.prototype.toString =
943 function() {
944 	return this.addr;
945 };
946