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 /**
 25  * @overview
 26  * This file contains a setting class.
 27  */
 28 
 29 /**
 30  * Creates a setting.
 31  * @class
 32  * This class represents a single setting. A setting's default value never changes; it
 33  * is available in case the user wishes to restore the current value to the default.
 34  * Most but not all settings have a corollary on the server side. Settings that don't
 35  * will depend on the environment or user activity to get their value.
 36  *
 37  * @author Conrad Damon
 38  * 
 39  * @param {String}	id				a unique ID
 40  * @param {Hash}	params			a hash of parameters
 41  * @param {String}	params.name				the name of the pref or attr on the server
 42  * @param {constant}	params.type			config, pref, or COS (see <code>ZmSetting.T_</code> constants)
 43  * @param {constant}	params.dataType			string, int, or boolean
 44  * @param {Object}	params.defaultValue		the default value
 45  * @param {Boolean}	params.isGlobal			if <code>true</code>, this setting is global across accounts
 46  * @param {Boolean}	params.isImplicit		if <code>true</code>, this setting is not represented in Preferences
 47  * 
 48  * @extends		ZmModel
 49  */
 50 ZmSetting = function(id, params) {
 51 
 52 	if (arguments.length == 0) return;
 53 	ZmModel.call(this, ZmEvent.S_SETTING);
 54 	
 55 	this.id = id;
 56 	this.name = params.name;
 57 	this.type = params.type;
 58 	this.dataType = params.dataType || ZmSetting.D_STRING;
 59 	this.defaultValue = params.defaultValue;
 60 	this.canPreset = params.canPreset;
 61 	if (this.type == ZmSetting.T_METADATA) {
 62 		this.section = params.section;
 63 	}
 64 	if (params.isGlobal) {
 65 		ZmSetting.IS_GLOBAL[id] = true;
 66 	}
 67 	if (params.isImplicit) {
 68 		ZmSetting.IS_IMPLICIT[id] = true;
 69 	}
 70 	
 71 	if (this.dataType == ZmSetting.D_HASH) {
 72 		this.value = {};
 73 		this.defaultValue = {};
 74 	} else if (this.dataType == ZmSetting.D_LIST) {
 75 		this.value = [];
 76 		this.defaultValue = [];
 77 	} else {
 78 		this.value = null;
 79 	}
 80 
 81     this.dontSaveDefault = params.dontSaveDefault;
 82 };
 83 
 84 ZmSetting.prototype = new ZmModel;
 85 ZmSetting.prototype.constructor = ZmSetting;
 86 
 87 // setting types
 88 /**
 89  * Defines the "config" type.
 90  */
 91 ZmSetting.T_CONFIG		= "config";
 92 /**
 93  * Defines the "COS" type.
 94  */
 95 ZmSetting.T_COS			= "cos";
 96 /**
 97  * Defines the "domain" type.
 98  */
 99 ZmSetting.T_DOMAIN		= "domain";
100 /**
101  * Defines the "meta-data" type.
102  */
103 ZmSetting.T_METADATA	= "meta";
104 /**
105  * Defines the "pref" type.
106  */
107 ZmSetting.T_PREF		= "pref";
108 /**
109  * Defines the "pseudo" type.
110  */
111 ZmSetting.T_PSEUDO		= "pseudo";
112 
113 // metadata sections
114 ZmSetting.M_IMPLICIT	= "implicit";
115 ZmSetting.M_OFFLINE		= "offline";
116 ZmSetting.M_ZIMLET		= "zimlet";
117 
118 // setting data types
119 ZmSetting.D_STRING		= "string"; // default type
120 ZmSetting.D_INT			= "int";
121 ZmSetting.D_BOOLEAN		= "boolean";
122 ZmSetting.D_LDAP_TIME 	= "ldap_time";
123 ZmSetting.D_HASH 		= "hash";
124 ZmSetting.D_LIST		= "list";
125 ZmSetting.D_NONE		= "NONE";	// placeholder setting
126 
127 // constants used as setting values
128 // TODO: these should be defined in their respective apps
129 /**
130  * Defines the "all" ACL grantee type.
131  * @type String
132  */
133 ZmSetting.ACL_AUTH				= "all";
134 /**
135  * Defines the "group" ACL grantee type.
136  * @type String
137  */
138 ZmSetting.ACL_GROUP				= "grp";
139 /**
140  * Defines the "none" ACL grantee type.
141  * @type String
142  */
143 ZmSetting.ACL_NONE				= "none";
144 /**
145  * Defines the "public" ACL grantee type.
146  * @type String
147  */
148 ZmSetting.ACL_PUBLIC			= "pub";
149 /**
150  * Defines the "domain" ACL grantee type.
151  * @type String
152  */
153 ZmSetting.ACL_DOMAIN			= "dom";
154 /**
155  * Defines the "user" ACL grantee type.
156  * @type String
157  */
158 ZmSetting.ACL_USER				= "usr";
159 ZmSetting.CAL_DAY				= "day";
160 ZmSetting.CAL_LIST				= "list";
161 ZmSetting.CAL_MONTH				= "month";
162 ZmSetting.CAL_WEEK				= "week";
163 ZmSetting.CAL_WORK_WEEK			= "workWeek";
164 ZmSetting.CAL_VISIBILITY_PRIV	= "private";
165 ZmSetting.CAL_VISIBILITY_PUB	= "public";
166 ZmSetting.CLIENT_ADVANCED		= "advanced";				// zimbraPrefClientType
167 ZmSetting.CLIENT_STANDARD		= "standard";
168 ZmSetting.COMPOSE_FONT_COLOR	= "#000000";	 			// zimbraPrefHtmlEditorDefaultFontColor
169 ZmSetting.COMPOSE_FONT_FAM 		= "arial,helvetica,sans-serif";		// zimbraPrefHtmlEditorDefaultFontFamily
170 ZmSetting.COMPOSE_FONT_SIZE 	= AjxMessageFormat.format(ZmMsg.pt,"12"); 			// zimbraPrefHtmlEditorDefaultFontSize
171 ZmSetting.LTR                   = "LTR";
172 ZmSetting.RTL                   = "RTL";
173 ZmSetting.COMPOSE_TEXT 			= "text";					// zimbraPrefComposeFormat
174 ZmSetting.COMPOSE_HTML 			= "html";
175 ZmSetting.CV_CARDS				= "cards"; 					// zimbraPrefContactsInitialView
176 ZmSetting.CV_LIST				= "list";
177 ZmSetting.DEDUPE_NONE			= "dedupeNone";				// zimbraPrefDedupeMessagesSentToSelf
178 ZmSetting.DEDUPE_SECOND			= "secondCopyifOnToOrCC";
179 ZmSetting.DEDUPE_INBOX			= "moveSentMessageToInbox";
180 ZmSetting.DEDUPE_ALL			= "dedupeAll";
181 ZmSetting.DELETE_SELECT_NEXT	= "next";					// zimbraPrefMailSelectAfterDelete
182 ZmSetting.DELETE_SELECT_PREV	= "previous";
183 ZmSetting.DELETE_SELECT_ADAPT	= "adaptive";
184 ZmSetting.GROUP_BY_CONV			= "conversation";			// zimbraPrefGroupMailBy
185 ZmSetting.GROUP_BY_MESSAGE		= "message";
186 ZmSetting.HTTP_DEFAULT_PORT		= 80;
187 ZmSetting.HTTPS_DEFAULT_PORT	= 443;
188 ZmSetting.INC_NONE				= "includeNone";			// zimbraPrefReplyIncludeOriginalText / zimbraPrefForwardIncludeOriginalText
189 ZmSetting.INC_ATTACH			= "includeAsAttachment";
190 ZmSetting.INC_BODY				= "includeBody";				// deprecated - same as includeBodyAndHeaders
191 ZmSetting.INC_BODY_ONLY			= "includeBodyOnly";
192 ZmSetting.INC_BODY_PRE			= "includeBodyWithPrefix";
193 ZmSetting.INC_BODY_HDR			= "includeBodyAndHeaders";
194 ZmSetting.INC_BODY_PRE_HDR		= "includeBodyAndHeadersWithPrefix";
195 ZmSetting.INC_SMART				= "includeSmart";
196 ZmSetting.INC_SMART_PRE			= "includeSmartWithPrefix";
197 ZmSetting.INC_SMART_HDR			= "includeSmartAndHeaders";
198 ZmSetting.INC_SMART_PRE_HDR		= "includeSmartAndHeadersWithPrefix";
199 ZmSetting.MARK_READ_NONE		= -1;						// zimbraPrefMarkMsgRead
200 ZmSetting.MARK_READ_NOW			= 0;						// zimbraPrefMarkMsgRead
201 ZmSetting.MARK_READ_TIME		= 1;						// zimbraPrefMarkMsgRead
202 ZmSetting.PRINT_FONT_SIZE 	    = AjxMessageFormat.format(ZmMsg.pt,"12"); 			// zimbraPrefDefaultPrintFontSize
203 ZmSetting.PROTO_HTTP			= "http:";
204 ZmSetting.PROTO_HTTPS			= "https:";
205 ZmSetting.PROTO_MIXED			= "mixed:";
206 ZmSetting.RIGHT_VIEW_FREE_BUSY	= "viewFreeBusy";
207 ZmSetting.RIGHT_INVITE			= "invite";
208 ZmSetting.RP_BOTTOM				= "bottom";					// zimbraPrefReadingPaneLocation / zimbraPrefConvReadingPaneLocation / zimbraPrefTasksReadingPaneLocation / zimbraPrefBriefcaseReadingPaneLocation
209 ZmSetting.RP_OFF				= "off";
210 ZmSetting.RP_RIGHT				= "right";
211 ZmSetting.SIG_INTERNET			= "internet";				// zimbraPrefMailSignatureStyle
212 ZmSetting.SIG_OUTLOOK			= "outlook";
213 
214 // values for the 'fetch' param of SearchConvRequest
215 ZmSetting.CONV_FETCH_NONE                       = "0";
216 ZmSetting.CONV_FETCH_FIRST_MATCHING             = "1";
217 ZmSetting.CONV_FETCH_FIRST                      = "!";
218 ZmSetting.CONV_FETCH_UNREAD                     = "u";
219 ZmSetting.CONV_FETCH_UNREAD_OR_FIRST_MATCHING   = "u1";
220 ZmSetting.CONV_FETCH_UNREAD_OR_FIRST            = "u!";
221 ZmSetting.CONV_FETCH_UNREAD_OR_BOTH_FIRST       = "u1!";
222 ZmSetting.CONV_FETCH_MATCHES                    = "hits";
223 ZmSetting.CONV_FETCH_MATCHES_OR_FIRST           = "hits!";
224 ZmSetting.CONV_FETCH_ALL                        = "all";
225 
226 // License status (network only)
227 ZmSetting.LICENSE_GOOD			= "OK";
228 ZmSetting.LICENSE_NOT_INSTALLED = "NOT_INSTALLED";
229 ZmSetting.LICENSE_NOT_ACTIVATED = "NOT_ACTIVATED";
230 ZmSetting.LICENSE_FUTURE		= "IN_FUTURE";
231 ZmSetting.LICENSE_EXPIRED		= "EXPIRED";
232 ZmSetting.LICENSE_BAD			= "INVALID";
233 ZmSetting.LICENSE_GRACE			= "LICENSE_GRACE_PERIOD";
234 ZmSetting.LICENSE_ACTIV_GRACE	= "ACTIVATION_GRACE_PERIOD";
235 
236 // warning messages for bad license statuses
237 ZmSetting.LICENSE_MSG									= {};
238 ZmSetting.LICENSE_MSG[ZmSetting.LICENSE_NOT_INSTALLED]	= ZmMsg.licenseNotInstalled;
239 ZmSetting.LICENSE_MSG[ZmSetting.LICENSE_NOT_ACTIVATED]	= ZmMsg.licenseNotActivated;
240 ZmSetting.LICENSE_MSG[ZmSetting.LICENSE_FUTURE]			= ZmMsg.licenseExpired;
241 ZmSetting.LICENSE_MSG[ZmSetting.LICENSE_EXPIRED]		= ZmMsg.licenseExpired;
242 ZmSetting.LICENSE_MSG[ZmSetting.LICENSE_BAD]			= ZmMsg.licenseExpired;
243 
244 // we need these IDs available when the app classes are parsed
245 ZmSetting.LOCALE_NAME			= "LOCALE_NAME";
246 ZmSetting.COMPOSE_INIT_DIRECTION= "COMPOSE_INIT_DIRECTION";
247 ZmSetting.SHOW_COMPOSE_DIRECTION_BUTTONS = "SHOW_COMPOSE_DIRECTION_BUTTONS";
248 ZmSetting.FONT_NAME				= "FONT_NAME";
249 ZmSetting.FONT_SIZE				= "FONT_SIZE";
250 ZmSetting.SKIN_NAME				= "SKIN_NAME";
251 
252 ZmSetting.BRIEFCASE_ENABLED		= "BRIEFCASE_ENABLED";
253 ZmSetting.CALENDAR_ENABLED		= "CALENDAR_ENABLED";
254 ZmSetting.CONTACTS_ENABLED		= "CONTACTS_ENABLED";
255 ZmSetting.MAIL_ENABLED			= "MAIL_ENABLED";
256 ZmSetting.OPTIONS_ENABLED		= "OPTIONS_ENABLED";
257 ZmSetting.PORTAL_ENABLED		= "PORTAL_ENABLED";
258 ZmSetting.SEARCH_ENABLED		= "SEARCH_ENABLED";
259 ZmSetting.SOCIAL_ENABLED		= "SOCIAL_ENABLED";
260 ZmSetting.TASKS_ENABLED			= "TASKS_ENABLED";
261 ZmSetting.VOICE_ENABLED			= "VOICE_ENABLED";
262 ZmSetting.TAGGING_ENABLED		= "TAGGING_ENABLED";
263 ZmSetting.CHAT_FEATURE_ENABLED  = "CHAT_FEATURE_ENABLED";
264 ZmSetting.CHAT_ENABLED          = "CHAT_ENABLED";
265 ZmSetting.CHAT_PLAY_SOUND       = "CHAT_PLAY_SOUND";
266 
267 ZmSetting.CALENDAR_UPSELL_ENABLED	= "CALENDAR_UPSELL_ENABLED";
268 ZmSetting.CONTACTS_UPSELL_ENABLED	= "CONTACTS_UPSELL_ENABLED";
269 ZmSetting.MAIL_UPSELL_ENABLED		= "MAIL_UPSELL_ENABLED";
270 ZmSetting.SOCIAL_EXTERNAL_ENABLED   = "SOCIAL_EXTERNAL_ENABLED";
271 ZmSetting.SOCIAL_EXTERNAL_URL	    = "SOCIAL_EXTERNAL_URL";
272 ZmSetting.VOICE_UPSELL_ENABLED		= "VOICE_UPSELL_ENABLED";
273 
274 //user selected font
275 ZmSetting.FONT_CLASSIC	= "classic";
276 ZmSetting.FONT_MODERN	= "modern";
277 ZmSetting.FONT_WIDE		= "wide";
278 ZmSetting.FONT_SYSTEM	= "system";
279 
280 //user selected font size
281 ZmSetting.FONT_SIZE_SMALL = "small";
282 ZmSetting.FONT_SIZE_NORMAL = "normal";
283 ZmSetting.FONT_SIZE_LARGE = "large";
284 ZmSetting.FONT_SIZE_LARGER = "larger";
285 
286 
287 // name for dynamic CSS class created from user font prefs
288 ZmSetting.USER_FONT_CLASS = "userFontPrefs";
289 
290 //task filterby setting
291 ZmSetting.TASK_FILTER_ALL = "";
292 ZmSetting.TASK_FILTER_TODO = "TODO";
293 ZmSetting.TASK_FILTER_COMPLETED = "COMPLETED";
294 ZmSetting.TASK_FILTER_WAITING = "WAITING";
295 ZmSetting.TASK_FILTER_DEFERRED = "DEFERRED";
296 ZmSetting.TASK_FILTER_INPROGRESS = "INPROGRESS";
297 ZmSetting.TASK_FILTER_NOTSTARTED = "NOTSTARTED";
298 
299 // hash of global settings
300 ZmSetting.IS_GLOBAL = {};
301 
302 // hash of implicit settings
303 ZmSetting.IS_IMPLICIT = {};
304 
305 // hash of implicit settings that have been changed during the current session
306 ZmSetting.CHANGED_IMPLICIT = {};
307 
308 // Send As and Send On Behalf Of settings
309 ZmSetting.SEND_AS = "sendAs";
310 ZmSetting.SEND_ON_BEHALF_OF = "sendOnBehalfOf";
311 
312 /**
313  * Returns a string representation of the object.
314  * 
315  * @return		{String}		a string representation of the object
316  */
317 ZmSetting.prototype.toString =
318 function() {
319 	return this.name + ": " + this.value;
320 };
321 
322 /**
323  * Gets the current value of this setting.
324  *
325  * @param {String}	key			the optional key for use by hash table data type
326  * @param {Boolean}	serialize		if <code>true</code>, serialize non-string value into string
327  * @return	{Object}	the value
328  */
329 ZmSetting.prototype.getValue =
330 function(key, serialize) {
331 
332 	var value = null;
333 	if (this.value != null) {
334 		value = key ? this.value[key] : this.value;
335 	} else if (this.defaultValue != null) {
336 		value = key ? this.defaultValue[key] : this.defaultValue;
337 	} else {
338 		return null;
339 	}
340 
341     if(this.dontSaveDefault && serialize && !key){
342         value = this.getRefinedValue(value);
343     }
344 
345 	return serialize ? ZmSetting.serialize(value, this.dataType) : value;
346 };
347 
348 ZmSetting.prototype.getRefinedValue =
349 function(value){
350     if(this.dataType == ZmSetting.D_HASH){
351         var refinedValue = {}, dValue = this.defaultValue;
352         for(var key in value){
353              refinedValue[key] = (dValue[key] != value[key]) ? value[key] : "";
354         }
355         return refinedValue;
356     }
357     return value;
358 };
359 
360 /**
361  * Gets the original value of this setting.
362  *
363  * @param {String}	key			the optional key for use by hash table data type
364  * @param {Boolean}	serialize		if <code>true</code>, serialize non-string value into string
365  * @return	{Object}	the value
366  */
367 ZmSetting.prototype.getOrigValue =
368 function(key, serialize) {
369 
370 	var origValue = null;
371 	if (this.origValue != null) {
372 		origValue = key ? this.origValue[key] : this.origValue;
373 	} else if (this.defaultValue != null) {
374 		origValue = key ? this.defaultValue[key] : this.defaultValue;
375 	} else {
376 		return null;
377 	}
378 
379 	return serialize ? ZmSetting.serialize(origValue, this.dataType) : origValue;
380 };
381 
382 /**
383  * Gets the default value of this setting.
384  *
385  * @param {String}	key 			the optional key for use by hash table data type
386  * @return	{Object}	the value
387  */
388 ZmSetting.prototype.getDefaultValue =
389 function(key, serialize) {
390 	var value = key ? this.defaultValue[key] : this.defaultValue;
391 	return serialize ? ZmSetting.serialize(value, this.dataType) : value;
392 };
393 
394 /**
395  * Sets the current value of this setting, performing any necessary data type conversion.
396  *
397  * @param {Object}	value			the new value for the setting
398  * @param {String}	key 			optional key for use by hash table data type
399  * @param {Boolean}	setDefault		if <code>true</code>, also set the default value
400  * @param {Boolean}	skipNotify		if <code>true</code>, do not notify listeners
401  * @param {Boolean}	skipImplicit		if <code>true</code>, do not check for change to implicit pref
402  */
403 ZmSetting.prototype.setValue =
404 function(value, key, setDefault, skipNotify, skipImplicit) {
405 
406 	var newValue = value;
407 	var changed = Boolean(newValue != this.value);
408 	if (this.dataType == ZmSetting.D_STRING) {
409 		this.value = newValue;
410 	} else if (this.dataType == ZmSetting.D_INT) {
411 		newValue = parseInt(value);
412 		if (isNaN(newValue)) { // revert to string if NaN
413 			newValue = value;
414 		}
415 		changed = Boolean(newValue != this.value);
416 		this.value = newValue;
417 	} else if (this.dataType == ZmSetting.D_BOOLEAN) {
418 		if (typeof(newValue) == "string") {
419 			newValue = (newValue.toLowerCase() === "true");
420 		}
421 		changed = Boolean(newValue != this.value);
422 		this.value = newValue;
423 	} else if (this.dataType == ZmSetting.D_LDAP_TIME) {
424 		var lastChar = (newValue.toLowerCase) ? lastChar = (newValue.toLowerCase()).charAt(newValue.length-1) : null;
425 		var num = parseInt(newValue);
426 		// convert to seconds
427 		if (lastChar == 'd') {
428 			newValue = num * 24 * 60 * 60;
429 		} else if (lastChar == 'h') {
430 			newValue = num * 60 * 60;
431 		} else if (lastChar == 'm') {
432 			newValue = num * 60;
433 		} else {
434 			newValue = num;	// default
435 		}
436 		changed = Boolean(newValue != this.value);
437 		this.value = newValue;
438 	} else if (this.dataType == ZmSetting.D_HASH) {
439 		changed = true;
440         if (key) {
441 			if (newValue) {
442                 changed = Boolean(newValue != this.value[key]);
443 				this.value[key] = newValue;
444 			} else {
445 				delete this.value[key];
446 			}
447 		} else {
448 			this.value = newValue;
449 		}
450 	} else if (this.dataType == ZmSetting.D_LIST) {
451 		if (newValue instanceof Array) {
452 			this.value = newValue;
453 		} else {
454 			this.value.push(newValue);
455 		}
456 		changed = true;
457 	}
458 
459 	if (setDefault) {
460 		if (key) {
461 			this.defaultValue[key] = this.value[key];
462 		} else {
463 			this.defaultValue = this.value;
464 		}
465 	}
466 	
467 	if (ZmSetting.IS_IMPLICIT[this.id] && changed && !skipImplicit) {
468 		if (skipNotify) {
469 			ZmSetting.CHANGED_IMPLICIT[this.id] = true;
470 		} else {
471 			this._notify(ZmEvent.E_MODIFY, key);
472 			return;
473 		}
474 	}
475 
476 	// Setting an internal pref is equivalent to saving it, so we should notify
477 	if (!this.name && !skipNotify) {
478 		this._notify(ZmEvent.E_MODIFY, key);
479 	}
480 };
481 
482 /**
483  * Handles modify notification.
484  * 
485  * @param	{Object}	obj		the object
486  */
487 ZmSetting.prototype.notifyModify = 
488 function(obj) {
489 	if (this.id == ZmSetting.QUOTA_USED && obj._name == "mbx" && obj.s != null) {
490 		this.setValue(obj.s);
491 		this._notify(ZmEvent.E_MODIFY, {account:obj.account});
492 	}
493 };
494 
495 ZmSetting.prototype.copyValue =
496 function() {
497 
498 	if (this.dataType == ZmSetting.D_HASH) {
499 		return AjxUtil.hashCopy(this.value);
500 	} else if (this.dataType == ZmSetting.D_LIST) {
501 		return this.value.concat();
502 	} else {
503 		return this.value;
504 	}
505 };
506 
507 ZmSetting.serialize =
508 function(value, dataType) {
509 
510 	if (dataType == ZmSetting.D_BOOLEAN) {
511 		value = value ? "TRUE" : "FALSE";
512 	} else if (dataType == ZmSetting.D_HASH) {
513 		var keys = [];
514 		for (var key in value) {
515 			keys.push(key);
516 		}
517 		keys.sort();
518 		var pairs = [];
519 		for (var j = 0; j < keys.length; j++) {
520 			var key = keys[j];
521 			pairs.push([key, value[key]].join(":"));
522 		}
523 		value = pairs.join(",");
524 	} else if (dataType == ZmSetting.D_LIST) {
525 		value = value.join(",");
526 	}
527 
528 	return value;
529 };
530