1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc.
  5  *
  6  * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at: https://www.zimbra.com/license
  9  * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15
 10  * have been added to cover use of software over a computer network and provide for limited attribution
 11  * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B.
 12  *
 13  * Software distributed under the License is distributed on an "AS IS" basis,
 14  * WITHOUT WARRANTY OF ANY KIND, either express or implied.
 15  * See the License for the specific language governing rights and limitations under the License.
 16  * The Original Code is Zimbra Open Source Web Client.
 17  * The Initial Developer of the Original Code is Zimbra, Inc.  All rights to the Original Code were
 18  * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015.
 19  *
 20  * All portions of the code are Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 ZmPref = function(id, name, dataType) {
 25 
 26 	ZmSetting.call(this, id, name, ZmSetting.T_PREF, dataType);
 27 
 28 	this.origValue = null;
 29 	this.isDirty = false;
 30 };
 31 
 32 ZmPref.prototype = new ZmSetting;
 33 ZmPref.prototype.constructor = ZmPref;
 34 
 35 ZmPref.prototype.isZmPref = true;
 36 ZmPref.prototype.toString = function() { return "ZmPref"; };
 37 
 38 ZmPref.KEY_ID				= "prefId_";
 39 
 40 ZmPref.TYPE_STATIC			= "STATIC"; // static text
 41 ZmPref.TYPE_INPUT			= "INPUT";
 42 ZmPref.TYPE_CHECKBOX		= "CHECKBOX";
 43 ZmPref.TYPE_COLOR			= "COLOR";
 44 ZmPref.TYPE_RADIO_GROUP		= "RADIO_GROUP";
 45 ZmPref.TYPE_SELECT			= "SELECT";
 46 ZmPref.TYPE_COMBOBOX		= "COMBOBOX";
 47 ZmPref.TYPE_TEXTAREA		= "TEXTAREA";
 48 ZmPref.TYPE_PASSWORD		= "PASSWORD";
 49 ZmPref.TYPE_IMPORT			= "IMPORT";
 50 ZmPref.TYPE_EXPORT			= "EXPORT";
 51 ZmPref.TYPE_SHORTCUTS		= "SHORTCUTS";
 52 ZmPref.TYPE_CUSTOM			= "CUSTOM";
 53 ZmPref.TYPE_LOCALES			= "LOCALES";
 54 ZmPref.TYPE_FONT			= "FONT";
 55 ZmPref.TYPE_FONT_SIZE		= "FONT_SIZE";
 56 
 57 ZmPref.ORIENT_VERTICAL		= "vertical";
 58 ZmPref.ORIENT_HORIZONTAL	= "horizontal";
 59 
 60 ZmPref.MAX_ROWS	= 7;
 61 
 62 // custom functions for loading and validation
 63 
 64 ZmPref.loadSkins =
 65 function(setup) {
 66 	var skins = appCtxt.get(ZmSetting.AVAILABLE_SKINS);
 67 	setup.options = []; // re-init otherwise we could possibly have dupes.
 68 	for (var i = 0; i < skins.length; i++) {
 69 		var skin = skins[i];
 70 		setup.options.push(skin);
 71 		var text = ZmMsg['theme-' + skin] || skin.substr(0, 1).toUpperCase() + skin.substr(1);
 72 		setup.displayOptions.push(text);
 73 	}
 74 };
 75 
 76 ZmPref.loadCsvFormats =
 77 function(setup){
 78     var formats = appCtxt.get(ZmSetting.AVAILABLE_CSVFORMATS);
 79 	if (!formats._options) {
 80 		var options = formats._options = [];
 81 		var displayOptions = formats._displayOptions = [];
 82 		for(var i=0; i<formats.length; i++){
 83 			options.push(formats[i]);
 84 		}
 85 		options.sort(ZmPref.__BY_CSVFORMAT);
 86 		for(var i=0; i < options.length; i++){
 87 			displayOptions.push((ZmMsg[options[i]] || options[i]));
 88 		}
 89 	}
 90 	setup.options = formats._options;
 91 	setup.displayOptions = formats._displayOptions;
 92 };
 93 ZmPref.__BY_CSVFORMAT = function(a, b) {
 94 	if (a.match(/^zimbra/)) return -1;
 95 	if (b.match(/^zimbra/)) return  1;
 96 	if (a.match(/^yahoo/))  return -1;
 97 	if (b.match(/^yahoo/))  return  1;
 98 	return a.localeCompare(b);
 99 };
100 
101 ZmPref.loadPageSizes =
102 function(setup) {
103 	var max = (setup.maxSetting && appCtxt.get(setup.maxSetting)) || 100;
104 	var list = [];
105 	for (var i = 0; i < ZmPref.PAGE_SIZES.length; i++) {
106 		var num = parseInt(ZmPref.PAGE_SIZES[i]);
107 		if (num <= max) {
108 			list.push(ZmPref.PAGE_SIZES[i]);
109 		}
110 	}
111 	if (max > ZmPref.PAGE_SIZES[ZmPref.PAGE_SIZES.length - 1]) {
112 		list.push(String(max));
113 	}
114 	setup.displayOptions = setup.options = list;
115 };
116 ZmPref.PAGE_SIZES = ["10", "25", "50", "100", "250", "500", "1000"];
117 
118 ZmPref.validateEmail =
119 function(emailStr) {
120 	if (emailStr) {
121 		// NOTE: Handle localhost for development purposes
122 		return emailStr.match(/\@localhost$/i) || AjxEmailAddress.parse(emailStr) != null;
123 	}
124 	return true;
125 };
126 
127 ZmPref.validateEmailList =
128 function(emailStrArray) {
129     for(var i in emailStrArray) {
130         if(!ZmPref.validateEmail(emailStrArray[i])) return false;
131     }
132     return true;
133 };
134 
135 ZmPref.downloadSinceDisplay =
136 function(dateStr) {
137 	if (!dateStr) { //usually it's "" in this case, but !dateStr would take care of 0 too (which is ZmMailApp.POP_DOWNLOAD_SINCE_ALL too) so changed it to !dateStr
138 		return ZmMailApp.POP_DOWNLOAD_SINCE_ALL;
139 	}
140 	if (dateStr === appCtxt.get(ZmSetting.POP_DOWNLOAD_SINCE)) {
141 		return ZmMailApp.POP_DOWNLOAD_SINCE_NO_CHANGE;
142 	}
143 	return ZmMailApp.POP_DOWNLOAD_SINCE_FROM_NOW;
144 };
145 ZmPref.downloadSinceValue =
146 function(value) {
147 	// == instead of === since the value is a string ("0") instead of a number (0) for some reason.
148 	if (value == ZmMailApp.POP_DOWNLOAD_SINCE_ALL) {
149 		return "";
150 	}
151 	if (value == ZmMailApp.POP_DOWNLOAD_SINCE_NO_CHANGE) {
152 		return appCtxt.get(ZmSetting.POP_DOWNLOAD_SINCE);
153 	}
154 	var date = new Date();
155 	date.setMinutes(date.getMinutes() + date.getTimezoneOffset());
156 	return AjxDateFormat.format("yyyyMMddHHmmss'Z'", date);
157 };
158 
159 ZmPref.validatePollingInterval =
160 function(interval) {
161 	var minimum = appCtxt.get(ZmSetting.MIN_POLLING_INTERVAL);
162 	if (interval && (!minimum || interval >= minimum)) {
163 		return true;
164 	} else {
165 		var min = minimum / 60;
166 		ZmPref.SETUP[ZmSetting.POLLING_INTERVAL].errorMessage = AjxMessageFormat.format(ZmMsg.invalidPollingInterval, min);
167 		return false;
168 	}
169 };
170 
171 ZmPref.pollingIntervalDisplay =
172 function(seconds) {
173     if (appCtxt.get(ZmSetting.INSTANT_NOTIFY) && seconds == appCtxt.get(ZmSetting.INSTANT_NOTIFY_INTERVAL))
174         return seconds;
175     else
176 	    return seconds / 60;
177 };
178 
179 ZmPref.pollingIntervalValue =
180 function(minutes) {
181     if (appCtxt.get(ZmSetting.INSTANT_NOTIFY) && minutes == appCtxt.get(ZmSetting.INSTANT_NOTIFY_INTERVAL))
182         return minutes;
183     else
184 	    return minutes * 60;
185 };
186 
187 
188 ZmPref.int2DurationDay =
189 function(intValue) {
190 	return intValue != null && intValue != 0 ? intValue + "d" : intValue;
191 };
192 
193 ZmPref.string2EmailList =
194 function(value) {
195     var emailList = [];
196     var addr,addrs = AjxEmailAddress.split(value);
197     if (addrs && addrs.length) {
198         for (var i = 0; i < addrs.length; i++) {
199             addr = addrs[i];
200             var email = AjxEmailAddress.parse(addr);
201             if(email) {
202                 addr = email.getAddress();
203             }
204             if(addr) emailList.push(AjxStringUtil.htmlEncode(addr));
205         }
206     }
207 	return emailList;
208 };
209 
210 ZmPref.durationDay2Int =
211 function(durValue) {
212 	return parseInt(durValue, 10); // NOTE: parseInt ignores non-digits
213 };
214 
215 ZmPref.approximateInterval =
216 function(value) {
217 	var values = [].concat(ZmPref.SETUP["POLLING_INTERVAL"].options);
218 	values.sort(ZmPref.__BY_NUMBER);
219 	return ZmPref.approximateValue(values, value);
220 };
221 
222 ZmPref.approximateValue =
223 function(sortedValues, value) {
224 	// find closest value
225 	for (var i = 0; i < sortedValues.length + 1; i++) {
226 		var a = sortedValues[i];
227 		var b = sortedValues[i+1];
228 		if (value < b) {
229 			var da = value - a;
230 			var db = b - value;
231 			return da < db ? a : b;
232 		}
233 	}
234 	return sortedValues[sortedValues.length - 1];
235 };
236 
237 ZmPref.validateLifetime =
238 function(value) {
239 	var globalValue = appCtxt.get(ZmSetting.MAIL_LIFETIME_GLOBAL);
240 	if (globalValue == "0") return true;
241 	return ZmPref.__BY_DURATION(value, globalValue) <= 0;
242 };
243 
244 ZmPref.validateLifetimeJunk =
245 function(value) {
246 	var globalValue = appCtxt.get(ZmSetting.MAIL_LIFETIME_JUNK_GLOBAL);
247 	if (globalValue == "0") return true;
248 	return ZmPref.__BY_DURATION(value, globalValue) <= 0 && ZmPref.validateLifetime(value);
249 };
250 
251 ZmPref.validateLifetimeTrash =
252 function(value) {
253 	var globalValue = appCtxt.get(ZmSetting.MAIL_LIFETIME_TRASH_GLOBAL);
254 	if (globalValue == "0") return true;
255 	return ZmPref.__BY_DURATION(value, globalValue) <= 0 && ZmPref.validateLifetime(value);
256 };
257 
258 ZmPref.approximateLifetimeInboxRead =
259 function(value) {
260 	return ZmPref.approximateLifetime("MAIL_LIFETIME_INBOX_READ", value, ZmPref.validateLifetime);
261 };
262 
263 ZmPref.approximateLifetimeInboxUnread =
264 function(value) {
265 	return ZmPref.approximateLifetime("MAIL_LIFETIME_INBOX_UNREAD", value, ZmPref.validateLifetime);
266 };
267 
268 ZmPref.approximateLifetimeJunk =
269 function(value) {
270 	return ZmPref.approximateLifetime("MAIL_LIFETIME_JUNK", value, ZmPref.validateLifetimeJunk, ZmPref.validateLifetime);
271 };
272 
273 ZmPref.approximateLifetimeSent =
274 function(value) {
275 	return ZmPref.approximateLifetime("MAIL_LIFETIME_SENT", value, ZmPref.validateLifetime);
276 };
277 
278 ZmPref.approximateLifetimeTrash =
279 function(value) {
280 	return ZmPref.approximateLifetime("MAIL_LIFETIME_TRASH", value, ZmPref.validateLifetimeTrash, ZmPref.validateLifetime);
281 };
282 
283 ZmPref.approximateLifetime =
284 function(prefId, duration, validateFunc1/*, ..., validateFuncN*/) {
285 	// convert durations to seconds
286 	var values = [].concat(ZmPref.SETUP[prefId].options);
287 	for (var i = 0; i < values.length; i++) {
288 		var value = values[i];
289 		values[i] = ZmPref.__DUR2SECS(value != "0" ? value+"d" : value);
290 	}
291 	values.sort(ZmPref.__BY_NUMBER);
292 
293 	// remove invalid options
294 	valuesLoop: for (var i = values.length - 1; i >= 0; i--) {
295 		for (var j = 2; j < arguments.length; j++) {
296 			var validateFunc = arguments[j];
297 			var value = ZmPref.__SECS2DUR(values[i]);
298 			if (!validateFunc(value)) {
299 				values.pop();
300 				continue valuesLoop;
301 			}
302 		}
303 		break;
304 	}
305 
306 	// if zero, the closest match is the greatest
307 	var seconds;
308 	if (duration == "0") {
309 		seconds = values[values.length - 1];
310 	}
311 
312 	// approximate to closest number of seconds
313 	else {
314 		seconds = ZmPref.approximateValue(values, ZmPref.__DUR2SECS(duration+"d"));
315 	}
316 
317 	// convert back to duration
318 	duration = ZmPref.__SECS2DUR(seconds);
319 	return duration != "0"  ? parseInt(duration, 10) : 0;
320 };
321 
322 ZmPref.markMsgReadDisplay =
323 function(value) {
324 	return (value > 0) ? 1 : value;
325 };
326 
327 ZmPref.markMsgReadValue =
328 function(value) {
329 	if (value == ZmSetting.MARK_READ_TIME) {
330 		var inputId = DwtId.makeId(ZmId.WIDGET_INPUT, ZmId.OP_MARK_READ);
331 		var input = DwtControl.fromElementId(inputId);
332 		if (input) {
333 			return input.getValue() || ZmSetting.MARK_READ_NOW;
334 		}
335 	}
336 	return value;
337 };
338 
339 ZmPref.setFormValue =
340 function(pref, value) {
341 	var app = appCtxt.getApp(ZmApp.PREFERENCES);
342 	var section = ZmPref.getPrefSectionWithPref(pref);
343 	if (app && section) {
344 		var page = app.getPreferencesPage(section.id);
345 		if (page) page.setFormValue(pref, value);
346 	}
347 };
348 
349 ZmPref.getFormValue =
350 function(pref) {
351 	var app = appCtxt.getApp(ZmApp.PREFERENCES);
352 	var section = ZmPref.getPrefSectionWithPref(pref);
353 	if (app && section) {
354 		var page = app.getPreferencesPage(section.id);
355 		if (page) return page.getFormValue(pref);
356 	}
357 };
358 
359 ZmPref.setIncludeOrig =
360 function(pref, value, list) {
361 
362 	pref.setValue(value);
363     pref.origValue = pref.copyValue();
364 	var settings = [ZmSetting.REPLY_INCLUDE_WHAT, ZmSetting.REPLY_USE_PREFIX, ZmSetting.REPLY_INCLUDE_HEADERS];
365 	var settingsHash = AjxUtil.arrayAsHash(settings);
366 	var mainSetting = ZmSetting.REPLY_INCLUDE_ORIG;
367 	if (!settingsHash[pref.id]) {
368 		settings = [ZmSetting.FORWARD_INCLUDE_WHAT, ZmSetting.FORWARD_USE_PREFIX, ZmSetting.FORWARD_INCLUDE_HEADERS];
369 		mainSetting = ZmSetting.FORWARD_INCLUDE_ORIG;
370 	}
371 
372 	var values = AjxUtil.map(settings, function(setting) { return appCtxt.get(setting); });
373 	var key = (values[0] == ZmSetting.INC_NONE || values[0] == ZmSetting.INC_ATTACH) ? values[0] : values.join("|");
374 	var newValue = ZmMailApp.INC_MAP_REV[key];
375 	var prefToChange = appCtxt.getSettings().getSetting(mainSetting);
376 	prefToChange.setValue(newValue);
377 	list.push(prefToChange);
378 };
379 
380 ZmPref.addOOOVacationExternalPrefToList = function(list, aPrefName, aNewPrefValue){
381     var lastPrefValue = appCtxt.get(aPrefName);     // prefvalue before saving ..
382     if (lastPrefValue !== aNewPrefValue) {          // donot add pref to list if pref value is not changed
383         prefToAdd = appCtxt.getSettings().getSetting(aPrefName);
384         prefToAdd.setValue(aNewPrefValue);
385         list.push(prefToAdd);
386     }
387 };
388 
389 /**
390  * On saving, for OOO vacation external reply, depending upon the option that the user has chosen in
391  * external select dropdown, we add the relevant pref that maps to the selected option in dropdown to the list that constructs the request.
392  */
393 ZmPref.addOOOVacationExternalPrefOnSave = function(pref, value, list, viewPage) {
394 
395     var externalSelect,
396         selectedText;
397 
398     ZmPref.addOOOVacationExternalPrefToList(list, ZmSetting.VACATION_EXTERNAL_SUPPRESS, value);
399     externalSelect = viewPage.getFormObject(ZmSetting.VACATION_EXTERNAL_SUPPRESS);
400     selectedText = externalSelect.getText();
401 
402     if (selectedText.indexOf(ZmMsg.vacationExternalReplySuppress) === -1) { //In external select, first three options are selected .
403         if (selectedText === ZmMsg.vacationExternalAllStandard) { //first  option is selected ..
404             ZmPref.addOOOVacationExternalPrefToList(list, ZmSetting.VACATION_EXTERNAL_MSG_ENABLED, false);
405             return;
406         }
407         if (selectedText === ZmMsg.vacationExternalAllCustom) { //second option ALL is selected ..
408             ZmPref.addOOOVacationExternalPrefToList(list, ZmSetting.VACATION_EXTERNAL_TYPE, 'ALL');
409         }
410         if (selectedText === ZmMsg.vacationExternalAllExceptABCustom) { //third option ALLNOTINAB is selected ..
411             ZmPref.addOOOVacationExternalPrefToList(list, ZmSetting.VACATION_EXTERNAL_TYPE, 'ALLNOTINAB');
412         }
413         ZmPref.addOOOVacationExternalPrefToList(list, ZmSetting.VACATION_EXTERNAL_MSG_ENABLED, true);
414     }
415 };
416 
417 /* For OOO section, this method is called only for the first time after reloading .
418    When OOO section loads, depending upon which OOO external select option the user has earlier saved in preferences,
419    we show the relevant OOO vacation external select option, and make the external text area hide /show correspondingly.
420    We get the value of OOO vacation external pref from appCtxt and then proceed.
421  */
422 ZmPref.initOOOVacationExternalSuppress = function() {
423     var section = ZmPref.getPrefSectionWithPref(ZmSetting.VACATION_EXTERNAL_MSG);
424     var view = appCtxt.getApp(ZmApp.PREFERENCES).getPrefController().getPrefsView().getView(section.id);
425     var externalTextArea  = view.getFormObject(ZmSetting.VACATION_EXTERNAL_MSG);
426     var externalSelect =  view.getFormObject(ZmSetting.VACATION_EXTERNAL_SUPPRESS);
427     if(appCtxt.get(ZmSetting.VACATION_EXTERNAL_SUPPRESS)){ // when last option is saved in preferences
428         externalTextArea.setVisible(false);
429         return;
430     }
431     if(!appCtxt.get(ZmSetting.VACATION_EXTERNAL_MSG_ENABLED)){ // handle when 1st option is selected
432         externalSelect.setSelected(0);
433         externalTextArea.setVisible(false);
434         return;
435     }
436     var stringToOptionValue = {
437         'ALL' : 1,
438         'ALLNOTINAB' : 2
439     };
440     externalSelect.setSelected(stringToOptionValue[appCtxt.get(ZmSetting.VACATION_EXTERNAL_TYPE)]);
441 };
442 
443 ZmPref.initIncludeWhat =
444 function(select, value) {
445 	// wait until prefix/headers checkboxes have been created
446 	AjxTimedAction.scheduleAction(new AjxTimedAction(this, function() {
447 		ZmPref._showIncludeOptions(select, (value == ZmSetting.INC_BODY || value == ZmSetting.INC_SMART));
448 	}), 100);
449 };
450 
451 ZmPref.onChangeIncludeWhat =
452 function(ev) {
453 	var nv = ev._args.newValue;
454 	var ov = ev._args.oldValue;
455 	var newAllowOptions = (nv == ZmSetting.INC_BODY || nv == ZmSetting.INC_SMART);
456 	var oldAllowOptions = (ov == ZmSetting.INC_BODY || ov == ZmSetting.INC_SMART);
457 	if (newAllowOptions != oldAllowOptions) {
458 		ZmPref._showIncludeOptions(ev._args.selectObj, newAllowOptions);
459 	}
460 };
461 
462 ZmPref._showIncludeOptions =
463 function(select, show) {
464 	var optionIds = (select._name == ZmSetting.REPLY_INCLUDE_WHAT) ?
465 						[ZmSetting.REPLY_USE_PREFIX, ZmSetting.REPLY_INCLUDE_HEADERS] :
466 						[ZmSetting.FORWARD_USE_PREFIX, ZmSetting.FORWARD_INCLUDE_HEADERS];
467 	for (var i = 0; i < optionIds.length; i++) {
468 		var cbox = select.parent._dwtObjects[optionIds[i]];
469 		if (cbox) {
470 			cbox.setVisible(show);
471 		}
472 	}
473 };
474 
475 ZmPref.getSendToFiltersActive =
476 function(ev, callback) {
477 	if (ev.target.checked) {
478 		if (callback)
479 			callback.run(false);
480 		return false;
481 	}
482 	AjxDispatcher.run("GetFilterController").hasOutgoingFiltersActive(callback);
483 };
484 
485 ZmPref.onChangeConfirm =
486 function(confirmMsg, showIfCallback, useCallback, revertCallback, ev) {
487 	var show = false;
488 	var callback = useCallback ? new AjxCallback(this, ZmPref._showOnChangeConfirm, [confirmMsg, revertCallback]) : null;
489 	if (AjxUtil.isFunction(showIfCallback))
490 		show = showIfCallback(ev, callback);
491 	else if (AjxUtil.isInstance(showIfCallback, AjxCallback))
492 		show = showIfCallback.run(ev, callback);
493 	else
494 		show = showIfCallback;
495 	ZmPref._showOnChangeConfirm(confirmMsg, revertCallback, show);
496 };
497 
498 ZmPref._showOnChangeConfirm =
499 function(confirmMsg, revertCallback, show) {
500 	if (show) {
501 		if (show) {
502 			var dialog = appCtxt.getYesNoMsgDialog();
503 			dialog.reset();
504 			dialog.setMessage(confirmMsg);
505 			dialog.setButtonListener(DwtDialog.NO_BUTTON, new AjxListener(null, ZmPref._handleOnChangeConfirmNo, [revertCallback]));
506 			dialog.popup();
507 		}
508 	}
509 };
510 
511 ZmPref._handleOnChangeConfirmNo =
512 function(revertCallback) {
513 	if (revertCallback)
514 		revertCallback.run();
515 	appCtxt.getYesNoMsgDialog().popdown();
516 };
517 
518 // Comparators
519 
520 ZmPref.__BY_NUMBER =
521 function(a, b) {
522 	if (a == b) return 0;
523 	if (a == Math.POSITIVE_INFINITY || b == Math.NEGATIVE_INFINITY) return 1;
524 	if (b == Math.POSITIVE_INFINITY || a == Math.NEGATIVE_INFINITY) return -1;
525 	return Number(a) - Number(b);
526 };
527 
528 ZmPref.__BY_DURATION =
529 function(a, b) {
530 	if (a == b) return 0;
531 	if (a == "0") return 1;
532 	if (b == "0") return -1;
533 	var asecs = ZmPref.__DUR2SECS(a);
534 	var bsecs = ZmPref.__DUR2SECS(b);
535 	return asecs - bsecs;
536 };
537 
538 // Converters
539 
540 ZmPref.__DURMULT = { "s": 1, "m": 60, "h": 3600, "d": 86400/*, "w": 604800*/ };
541 ZmPref.__DURDIV = { /*604800: "w",*/ 86400: "d", 3600: "h", 60: "m", 1: "s" };
542 
543 ZmPref.__DUR2SECS =
544 function(duration) {
545 	if (duration == "0") return Number.POSITIVE_INFINITY;
546 
547 	var type = duration.substring(duration.length - 1).toLowerCase();
548 	return parseInt(duration, 10) * ZmPref.__DURMULT[type];
549 };
550 
551 ZmPref.__SECS2DUR =
552 function(seconds, type) {
553 	if (seconds == Number.POSITIVE_INFINITY) return "0";
554 
555 	var divisors = ZmPref.__DURDIV;
556 	if (type) {
557 		type = {};
558 		type[ ZmPref.__DURMULT[type] ] = type;
559 	}
560 	for (var divisor in divisors) {
561 		var result = Math.floor(seconds / divisor);
562 		if (result > 0) {
563 			return [ result, divisors[divisor] ].join("");
564 		}
565 	}
566 
567 	return "0"+type;
568 };
569 
570 // maximum value lengths
571 ZmPref.MAX_LENGTH = {};
572 ZmPref.MAX_LENGTH[ZmSetting.INITIAL_SEARCH]	= 512;
573 ZmPref.MAX_LENGTH[ZmSetting.SIGNATURE]		= 1024;
574 ZmPref.MAX_LENGTH[ZmSetting.AWAY_MESSAGE]	= 8192;
575 
576 ZmPref.setPrefList =
577 function(prefsId, list) {
578 	ZmPref[prefsId] = list;
579 };
580 
581 /**
582  * The SETUP object for a pref gets translated into a form input.
583  * Available properties are:
584  *
585  * displayName			descriptive text
586  * displayFunc			A function that returns the descriptive text. Only implemented for checkboxes.
587  * displayContainer		type of form input: checkbox, select, input, or textarea
588  * options				values for a select input
589  * displayOptions		text for the select input's values
590  * validationFunction	function to validate the value
591  * errorMessage			message to show if validation fails
592  * precondition			pref will not be displayed unless precondition is true
593  * inputId              array of unique ids to be applied to input
594  */
595 ZmPref.SETUP = {};
596 
597 ZmPref.registerPref =
598 function(id, params) {
599 	ZmPref.SETUP[id] = params;
600 };
601 
602 /** Clears all of the preference sections. */
603 ZmPref.clearPrefSections =
604 function() {
605 	ZmPref._prefSectionMap = {};
606 	ZmPref._prefSectionArray = null;
607 };
608 
609 /**
610  * Registers a preferences section ("tab").
611  * <p>
612  * The <code>params</code> argument can have the following properties:
613  *
614  * @param title         [string]    The section title.
615  * @param templateId    [string]    (Optional) The template associated to this
616  *                                  section. If not specified, the id is used.
617  *                                  Note: The default template base is:
618  *                                  "prefs.Pages#".
619  * @param priority      [int]       The section priority used when determining
620  *                                  the order of the sections.
621  * @param precondition  [any]       (Optional) Specifies the precondition
622  *                                  under which this section is shown.
623  * @param prefs         [Array]     List of preferences that appear in this
624  *                                  section.
625  * @param manageChanges [boolean]   Determines whether this section manages
626  *                                  its own pref changes. If true, then
627  *                                  ZmPrefView#getChangedPrefs will not query
628  *                                  the section for changes.
629  * @param manageDirty   [boolean]   Determines whether this section manages
630  *                                  its "dirty" state (i.e. whether any prefs
631  *                                  have changed values). If true, then
632  *                                  ZmPrefView#getChangedPrefs will call
633  *                                  isDirty() on the section view.
634  */
635 ZmPref.registerPrefSection =
636 function(id, params) {
637 	if (!id || !params) { return; }
638 
639 	// set template for section
640 	var templateId = params.templateId || id;
641 	if (!templateId.match(/#/)) {
642 		templateId = ["prefs.Pages",templateId].join("#");
643 	}
644 	params.templateId = templateId;
645 	params.id = id;
646 
647 	// save section
648 	appCtxt.set(ZmSetting.PREF_SECTIONS, params, id);
649 	ZmPref._prefSectionArray = null;
650 };
651 
652 ZmPref.unregisterPrefSection =
653 function(id) {
654 	appCtxt.set(ZmSetting.PREF_SECTIONS, null, id);
655 	ZmPref._prefSectionArray = null;
656 };
657 
658 /** Returns the pref sections map. */
659 ZmPref.getPrefSectionMap =
660 function() {
661 	return appCtxt.get(ZmSetting.PREF_SECTIONS);
662 };
663 
664 /** Returns a sorted array of pref sections (based on priority). */
665 ZmPref.getPrefSectionArray =
666 function() {
667 	if (!ZmPref._prefSectionArray) {
668 		ZmPref._prefSectionArray = [];
669 		var prefSectionMap = appCtxt.get(ZmSetting.PREF_SECTIONS);
670 		for (var id in prefSectionMap) {
671 			ZmPref._prefSectionArray.push(prefSectionMap[id]);
672 		}
673 		ZmPref._prefSectionArray.sort(ZmPref.__BY_PRIORITY);
674 	}
675 	return ZmPref._prefSectionArray;
676 };
677 
678 /** Returns the section that contains the specified pref. */
679 ZmPref.getPrefSectionWithPref =
680 function(prefId) {
681 	var prefSectionMap = appCtxt.get(ZmSetting.PREF_SECTIONS);
682 	for (var sectionId in prefSectionMap) {
683 		var section = prefSectionMap[sectionId];
684 		if (section.prefs == null) continue;
685 
686 		for (var i = 0; i < section.prefs.length; i++) {
687 			if (section.prefs[i] == prefId) {
688 				return section;
689 			}
690 		}
691 	}
692 	return null;
693 };
694 
695 // Make sure the pref sections are init'd
696 ZmPref.clearPrefSections();
697 
698 //
699 // Private functions
700 //
701 
702 ZmPref.__BY_PRIORITY =
703 function(a, b) {
704 	return Number(a.priority) - Number(b.priority);
705 };
706 
707 ZmPref.regenerateSignatureEditor =
708 function( control ) {
709     var signaturePage = control.parent;
710     var valueEl = document.getElementById(signaturePage._htmlElId + "_SIG_EDITOR");
711     var htmlEditor = new ZmHtmlEditor({
712         parent: signaturePage,
713         parentElement: valueEl.parentNode,
714         textAreaId: "TEXTAREA_SIGNATURE",
715 		autoFocus: true,
716         attachmentCallback:
717             signaturePage._insertImagesListener.bind(signaturePage)
718     });
719     valueEl.parentNode.removeChild(valueEl);
720     signaturePage._sigEditor = htmlEditor;
721     signaturePage._populateSignatures();
722 };
723 
724 ZmPref._normalizeFontId = function(id, dontFallback) {
725 	var oldid = id;
726 	id = id.replace(/,\s/g,",").replace(/'/g,"").toLowerCase(); // Make sure all ids that are supposed to be found in ZmPref.FONT_FAMILY are actually found
727 	if (!dontFallback) {
728 		var map = ZmPref.FONT_FAMILY;
729 		if (map && !map[id]) {
730 			var keys = AjxUtil.keys(map);
731 			if (keys.length) {
732 				var splitId = id.split(","); // e.g. ["times new roman","helvetica"]
733 				for (var i=0; i<splitId.length; i++) { // Loop over input font names
734 					for (var j=0; j<keys.length; j++) { // Loop over candidate styles, e.g. ["arial,sans-serif","times new roman,serif"]
735 						if (keys[j].indexOf(splitId[i]) != -1) {
736 							return keys[j];
737 						}
738 					}
739 				}
740 				return keys[0];
741 			}
742 		}
743 	}
744 	return id;
745 };
746 ZmPref._normalizeFontName = function(fontId) {
747 	return ZmPref.FONT_FAMILY[ZmPref._normalizeFontId(fontId)].name;
748 };
749 ZmPref._normalizeFontValue = function(fontId) {
750 	return ZmPref.FONT_FAMILY[ZmPref._normalizeFontId(fontId)].value;
751 };
752 
753 ZmPref.handleOOOVacationExternalOptionChange = function(ev) {
754     var section = ZmPref.getPrefSectionWithPref(ZmSetting.VACATION_EXTERNAL_MSG);
755     var view = appCtxt.getApp(ZmApp.PREFERENCES).getPrefController().getPrefsView().getView(section.id);
756     var externalTextArea  = view.getFormObject(ZmSetting.VACATION_EXTERNAL_MSG);
757     var externalSelect =  view.getFormObject(ZmSetting.VACATION_EXTERNAL_SUPPRESS);
758     var selectedIndex = externalSelect.getSelectedOption().getItem()._optionIndex;
759 
760     if (selectedIndex === 0 || selectedIndex === 3 ) {
761         externalTextArea.setVisible(false);
762     } else {
763         externalTextArea.setVisible(true);
764     }
765 };
766 
767 // Keep NOTIF_ENABLED updated based on whether NOTIF_ADDRESS has a value
768 ZmPref.setMailNotificationAddressValue = function(pref, value, list, viewPage) {
769 
770 	pref.setValue(value);
771 	list.push(pref);
772 
773 	var notifEnabledSetting = appCtxt.getSettings().getSetting(ZmSetting.NOTIF_ENABLED),
774 		hasValue = !!value;
775 
776 	if (notifEnabledSetting.getValue() !== hasValue) {
777 		notifEnabledSetting.setValue(hasValue);
778 		list.push(notifEnabledSetting);
779 	}
780 };
781 
782 ZmPref.FONT_FAMILY = {};
783 (function() {
784 	var KEYS = [ "fontFamilyIntl", "fontFamilyBase" ];
785 	var i, j, key, value, name;
786 	for (j = 0; j < KEYS.length; j++) {
787 		for (i = 1; value = AjxMsg[KEYS[j]+i+".css"]; i++) {
788 			if (value.match(/^#+$/)) break;
789 			value = ZmPref._normalizeFontId(value,true);
790 			name = AjxMsg[KEYS[j]+i+".display"];
791 			ZmPref.FONT_FAMILY[value] = {name:name, value:value};
792 		}
793 	}
794 })();
795