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  * AjxUtil - static class with some utility methods. This is where to
 26  * put things when no other class wants them.
 27  *
 28  * 12/3/2004 At this point, it only needs AjxEnv to be loaded.
 29  * 
 30  * @private
 31  */
 32 AjxUtil = function() {
 33 };
 34 
 35 AjxUtil.FLOAT_RE = /^[+\-]?((\d+(\.\d*)?)|((\d*\.)?\d+))([eE][+\-]?\d+)?$/;
 36 AjxUtil.NOTFLOAT_RE = /[^\d\.]/;
 37 AjxUtil.NOTINT_RE = /[^0-9]+/;
 38 AjxUtil.LIFETIME_FIELD = /^([0-9])+([dhms]|ms)?$/;
 39 AjxUtil.INT_RE = /^\-?(0|[1-9]\d*)$/;
 40 
 41 AjxUtil.isSpecified 		= function(aThing) { return ((aThing !== void 0) && (aThing !== null)); };
 42 AjxUtil.isUndefined 		= function(aThing) { return (aThing === void 0); };
 43 AjxUtil.isNull 				= function(aThing) { return (aThing === null); };
 44 AjxUtil.isBoolean 			= function(aThing) { return (typeof(aThing) === 'boolean'); };
 45 AjxUtil.isString 			= function(aThing) { return (typeof(aThing) === 'string'); };
 46 AjxUtil.isNumber 			= function(aThing) { return (typeof(aThing) === 'number'); };
 47 AjxUtil.isObject 			= function(aThing) { return ((typeof(aThing) === 'object') && (aThing !== null)); };
 48 AjxUtil.isArray 			= function(aThing) { return AjxUtil.isInstance(aThing, Array); };
 49 AjxUtil.isArrayLike			= function(aThing) { return typeof aThing !== 'string' && typeof aThing.length === 'number'; };
 50 AjxUtil.isFunction 			= function(aThing) { return (typeof(aThing) === 'function'); };
 51 AjxUtil.isDate 				= function(aThing) { return AjxUtil.isInstance(aThing, Date); };
 52 AjxUtil.isLifeTime 			= function(aThing) { return AjxUtil.LIFETIME_FIELD.test(aThing); };
 53 AjxUtil.isNumeric 			= function(aThing) { return (!isNaN(parseFloat(aThing)) && AjxUtil.FLOAT_RE.test(aThing) && !AjxUtil.NOTFLOAT_RE.test(aThing)); };
 54 AjxUtil.isInt				= function(aThing) { return AjxUtil.INT_RE.test(aThing); };
 55 AjxUtil.isPositiveInt		= function(aThing) { return AjxUtil.isInt(aThing) && parseInt(aThing, 10) > 0; }; //note - assume 0 is not positive
 56 AjxUtil.isLong = AjxUtil.isInt;
 57 AjxUtil.isNonNegativeLong	= function(aThing) { return AjxUtil.isLong(aThing) && parseInt(aThing, 10) >= 0; };
 58 AjxUtil.isEmpty				= function(aThing) { return ( AjxUtil.isNull(aThing) || AjxUtil.isUndefined(aThing) || (aThing === "") || (AjxUtil.isArray(aThing) && (aThing.length==0))); };
 59 // REVISIT: Should do more precise checking. However, there are names in
 60 //			common use that do not follow the RFC patterns (e.g. domain
 61 //			names that start with digits).
 62 AjxUtil.IPv4_ADDRESS_RE = /^\d{1,3}(\.\d{1,3}){3}(\.\d{1,3}\.\d{1,3})?$/;
 63 AjxUtil.IPv4_ADDRESS_WITH_PORT_RE = /^\d{1,3}(\.\d{1,3}){3}(\.\d{1,3}\.\d{1,3})?:\d{1,5}$/;
 64 AjxUtil.IPv6_ADDRESS_RE = /^((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\3)::|:\b|(?:%[-\w.~]+)?$))|(?!\2\3)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4})(?:%[-\w.~]+)?$/i;
 65 AjxUtil.IPv6_ADDRESS_WITH_PORT_RE = new RegExp(AjxUtil.IPv6_ADDRESS_RE.source.replace('^', '^\\[').replace('$', '\\]:\\d{1,5}$'), 'i');
 66 AjxUtil.SUBNET_RE = /^\d{1,3}(\.\d{1,3}){3}(\.\d{1,3}\.\d{1,3})?\/\d{1,2}$/;
 67 AjxUtil.DOMAIN_NAME_SHORT_RE = /^[A-Za-z0-9\-]{2,}$/;
 68 AjxUtil.DOMAIN_NAME_FULL_RE = /^[A-Za-z0-9\-]{1,}(\.[A-Za-z0-9\-]{2,}){1,}$/;
 69 AjxUtil.HOST_NAME_RE = /^[A-Za-z0-9\-]{2,}(\.[A-Za-z0-9\-]{1,})*(\.[A-Za-z0-9\-]{2,})*$/;
 70 AjxUtil.HOST_NAME_WITH_PORT_RE = /^[A-Za-z0-9\-]{2,}(\.[A-Za-z0-9\-]{2,})*:([0-9])+$/;
 71 AjxUtil.EMAIL_SHORT_RE = /^[^@\s]+$/;
 72 AjxUtil.EMAIL_FULL_RE = /^[^@\s]+@[A-Za-z0-9\-]{2,}(\.[A-Za-z0-9\-]{2,})+$/;
 73 AjxUtil.FULL_URL_RE = /^[A-Za-z0-9]{2,}:\/\/[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)*(:([0-9])+)?(\/[\w\.\|\^\*\[\]\{\}\(\)\-<>~,'#_;@:!%]+)*(\/)?(\?[\w\.\|\^\*\+\[\]\{\}\(\)\-<>~,'#_;@:!%&=]*)?$/;
 74 AjxUtil.IP_FULL_URL_RE = /^[A-Za-z0-9]{2,}:\/\/\d{1,3}(\.\d{1,3}){3}(\.\d{1,3}\.\d{1,3})?(:([0-9])+)?(\/[\w\.\|\^\*\[\]\{\}\(\)\-<>~,'#_;@:!%]+)*(\/)?(\?[\w\.\|\^\*\+\[\]\{\}\(\)\-<>~,'#_;@:!%&=]*)?$/;
 75 AjxUtil.SHORT_URL_RE = /^[A-Za-z0-9]{2,}:\/\/[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)*(:([0-9])+)?$/;
 76 AjxUtil.IP_SHORT_URL_RE = /^[A-Za-z0-9]{2,}:\/\/\d{1,3}(\.\d{1,3}){3}(\.\d{1,3}\.\d{1,3})?(:([0-9])+)?$/;
 77 
 78 AjxUtil.isHostName 			= function(s) { return AjxUtil.HOST_NAME_RE.test(s); };
 79 AjxUtil.isDomainName = 
 80 function(s, shortMatch) {
 81 	return shortMatch 
 82 		? AjxUtil.DOMAIN_NAME_SHORT_RE.test(s) 
 83 		: AjxUtil.DOMAIN_NAME_FULL_RE.test(s);
 84 };
 85 
 86 AjxUtil.isEmailAddress = 
 87 function(s, nameOnly) {
 88 	return nameOnly 
 89 		? AjxUtil.EMAIL_SHORT_RE.test(s) 
 90 		: AjxUtil.EMAIL_FULL_RE.test(s);
 91 };
 92 
 93 AjxUtil.isValidEmailNonReg = 
 94 function(s) {
 95 	return ((s.indexOf ("@") > 0) && (s.lastIndexOf ("@") == s.indexOf ("@")) && (s.indexOf (".@") < 0));
 96 };
 97 
 98 /**
 99  * Return true if the given object is a plain hash.
100  *
101  * @param aThing	The object for testing.
102  */
103 AjxUtil.isHash =
104 function(aThing) {
105 	// Note: can't just look at prototype since that fails cross-window.
106 	// See http://stackoverflow.com/questions/10741618/how-to-check-if-argument-is-an-object-and-not-an-array, esp the part with isPlainObject()
107 	var str = aThing && aThing.toString ? aThing.toString() : Object.prototype.toString.call(aThing);
108 	return AjxUtil.isObject(aThing) && str === '[object Object]';
109 };
110 
111 AjxUtil.SIZE_GIGABYTES = "GB";
112 AjxUtil.SIZE_MEGABYTES = "MB";
113 AjxUtil.SIZE_KILOBYTES = "KB";
114 AjxUtil.SIZE_BYTES = "B";
115 
116 /**
117  * Formats a size (in bytes) to the largest whole unit. For example,
118  * AjxUtil.formatSize(302132199) returns "288 MB".
119  *
120  * @param size      The size (in bytes) to be formatted.
121  * @param round     True to round to nearest integer. Default is true.
122  * @param fractions Number of fractional digits to display, if not rounding.
123  *                  Trailing zeros after the decimal point are trimmed.
124  */
125 AjxUtil.formatSize = 
126 function(size, round, fractions) {
127 	if (round == null) round = true;
128 	if (fractions == null) fractions = 20; // max allowed for toFixed is 20
129 
130 	var formattedUnits = AjxMsg.sizeBytes;
131 	var units = AjxMsg.SIZE_BYTES;
132 	if (size >= 1073741824) {
133 		formattedUnits = AjxMsg.sizeGigaBytes;
134 		units = AjxUtil.SIZE_GIGABYTES;
135 	}
136 	else if (size >= 1048576) {
137 		formattedUnits = AjxMsg.sizeMegaBytes;
138 		units = AjxUtil.SIZE_MEGABYTES;
139 	}
140 	else if (size > 1023) {
141 		formattedUnits = AjxMsg.sizeKiloBytes;
142 		units = AjxUtil.SIZE_KILOBYTES;
143 	}
144 
145 
146 	var formattedSize = AjxUtil.formatSizeForUnits(size, units, round, fractions);
147 	return AjxMessageFormat.format(AjxMsg.formatSizeAndUnits, [formattedSize, formattedUnits]);
148 };
149 
150 /**
151  * Formats a size (in bytes) to a specific unit. Since the unit size is
152  * known, the unit is not shown in the returned string. For example,
153  * AjxUtil.formatSizeForUnit(302132199, AjxUtil.SIZE_MEGABYTES, false, 2) 
154  * returns "288.13".
155  *
156  * @param size      The size (in bytes) to be formatted.
157  * @param units     The unit of measure.
158  * @param round     True to round to nearest integer. Default is true.
159  * @param fractions Number of fractional digits to display, if not rounding.
160  *                  Trailing zeros after the decimal point are trimmed.
161  */
162 AjxUtil.formatSizeForUnits = 
163 function(size, units, round, fractions) {
164 	if (units == null) units = AjxUtil.SIZE_BYTES;
165 	if (round == null) round = true;
166 	if (fractions == null) fractions = 20; // max allowed for toFixed is 20
167 
168 	switch (units) {
169 		case AjxUtil.SIZE_GIGABYTES: { size /= 1073741824; break; }
170 		case AjxUtil.SIZE_MEGABYTES: { size /= 1048576; break; }
171 		case AjxUtil.SIZE_KILOBYTES: { size /= 1024; break; }
172 	}
173 	var dot = I18nMsg.numberSeparatorDecimal !='' ? I18nMsg.numberSeparatorDecimal : '.';
174 	var pattern = I18nMsg.formatNumber.replace(/\..*$/, ""); // Strip off decimal, we'll be adding one anyway
175 	pattern = pattern.replace(/,/, "");       // Remove the ,
176 	if (!round && fractions) {
177 		pattern = pattern += dot;
178 		for (var i = 0; i < fractions; i++) {
179 			pattern += "#";
180 		}
181 	}
182 	return AjxNumberFormat.format(pattern, size);
183 };
184 
185 /**
186  * Performs the opposite of AjxUtil.formatSize in that this function takes a 
187  * formatted size.
188  *
189  * @param units Unit constant: "GB", "MB", "KB", "B". Must be specified 
190  *              unless the formatted size ends with the size marker, in
191  *				which case the size marker in the formattedSize param
192  *				overrides this parameter.
193  */
194 AjxUtil.parseSize = 
195 function(formattedSize, units) {
196 	// NOTE: Take advantage of fact that parseFloat ignores bad chars
197 	//       after numbers
198 	if (typeof formattedSize != _STRING_) {
199 		formattedSize = formattedSize.toString() ;
200 	}
201 	var size = parseFloat(formattedSize.replace(/^\s*/,""));
202 
203 	var marker = /[GMK]?B$/i;
204 	var result = marker.exec(formattedSize);
205 	if (result) {
206 		//alert("units: "+units+", result[0]: '"+result[0]+"'");
207 		units = result[0].toUpperCase();
208 	}
209 	
210 	switch (units) {
211 		case AjxUtil.SIZE_GIGABYTES: size *= 1073741824; break;
212 		case AjxUtil.SIZE_MEGABYTES: size *= 1048576; break; 
213 		case AjxUtil.SIZE_KILOBYTES: size *= 1024; break;
214 	}
215 	
216 	//alert("AjxUtil#parseSize: formattedSize="+formattedSize+", size="+size);
217 	return size;
218 };
219 
220 AjxUtil.isInstance = 
221 function(aThing, aClass) { 
222 	return !!(aThing && aThing.constructor && (aThing.constructor === aClass)); 
223 };
224 
225 AjxUtil.assert = 
226 function(aCondition, aMessage) {
227 	if (!aCondition && AjxUtil.onassert) AjxUtil.onassert(aMessage);
228 };
229 
230 AjxUtil.onassert = 
231 function(aMessage) {
232 	// Create an exception object and set the message
233 	var myException = new Object();
234 	myException.message = aMessage;
235 	
236 	// Compile a stack trace
237 	var myStack = new Array();
238 	if (AjxEnv.isIE5_5up) {
239 		// On IE, the caller chain is on the arguments stack
240 		var myTrace = arguments.callee.caller;
241 		var i = 0; // stop at 20 since there might be somehow an infinite loop here. Maybe in case of a recursion. 
242 		while (myTrace && i++ < 20) {
243 		    myStack[myStack.length] = myTrace;
244 	    	myTrace = myTrace.caller;
245 		}
246 	} else {
247 		try {
248 			var myTrace = arguments.callee.caller;
249 			while (myTrace) {
250 				myStack[myStack.length] = myTrace;
251 				if (myStack.length > 2) break;
252 				myTrace = myTrace.caller;
253 		    }
254 		} catch (e) {
255 		}
256 	}
257 	myException.stack = myStack;
258 	
259 	// Alert with the message and a description of the stack
260 	var stackString = '';
261 	var MAX_LEN = 170;
262 	for (var i = 1; i < myStack.length; i++) {
263 		if (i > 1) stackString += '\n';
264 		if (i < 11) {
265 			var fs = myStack[i].toString();
266 			if (fs.length > MAX_LEN) {
267 				fs = fs.substr(0,MAX_LEN) + '...';
268 				fs = fs.replace(/\n/g, '');
269 			}
270 			stackString += i + ': ' + fs;
271 		} else {
272 			stackString += '(' + (myStack.length - 11) + ' frames follow)';
273 			break;
274 		}
275 	}
276 	alert('assertion:\n\n' + aMessage + '\n\n---- Call Stack ---\n' + stackString);
277 	
278 	// Now throw the exception
279 	throw myException;
280 };
281 
282 // IE doesn't define Node type constants
283 AjxUtil.ELEMENT_NODE		= 1;
284 AjxUtil.TEXT_NODE			= 3;
285 AjxUtil.DOCUMENT_NODE		= 9;
286 
287 AjxUtil.getInnerText = 
288 function(node) {
289  	if (AjxEnv.isIE)
290  		return node.innerText;
291 
292 	function f(n) {
293 		if (n) {
294 			if (n.nodeType == 3 /* TEXT_NODE */)
295 				return n.data;
296 			if (n.nodeType == 1 /* ELEMENT_NODE */) {
297 				if (/^br$/i.test(n.tagName))
298 					return "\r\n";
299 				var str = "";
300 				for (var i = n.firstChild; i; i = i.nextSibling)
301 					str += f(i);
302 				return str;
303 			}
304 		}
305 		return "";
306 	};
307 	return f(node);
308 };
309 
310 /**
311  * This method returns a proxy for the specified object. This is useful when
312  * you want to modify properties of an object and want to keep those values
313  * separate from the values in the original object. You can then iterate
314  * over the proxy's properties and use the <code>hasOwnProperty</code>
315  * method to determine if the property is a new value in the proxy.
316  * <p>
317  * <strong>Note:</strong>
318  * A reference to the original object is stored in the proxy as the "_object_" 
319  * property.
320  *
321  * @param object [object] The object to proxy.
322  * @param level  [number] The number of property levels deep to proxy.
323  *						  Defaults to zero.
324  */
325 AjxUtil.createProxy = 
326 function(object, level) {
327 	var proxy;
328 	var proxyCtor = function(){}; // bug #6517 (Safari doesnt like 'new Function')
329 	proxyCtor.prototype = object;
330 	if (object instanceof Array) {
331 		proxy  = new Array();
332 		var cnt  = object.length;
333 		for(var ix = 0; ix < cnt; ix++) {
334 			proxy[ix] = object[ix];
335 		}
336 	} else {
337 		proxy = new proxyCtor;
338 	}
339 	
340 	if (level) {
341 		for (var prop in object) {
342 			if (typeof object[prop] == "object" && object[prop] !== null)
343 				proxy[prop] = AjxUtil.createProxy(object[prop], level - 1);
344 		}
345 	}	
346 	
347 	proxy._object_ = object;
348 	return proxy;
349 };
350 
351 AjxUtil.unProxy =
352 function(proxy) {
353 	var object = proxy && proxy._object_;
354 	if (object) {
355 		for (var prop in proxy) {
356 			if (proxy.hasOwnProperty(prop) && prop!="_object_") {
357 				object[prop] = proxy[prop];
358 			}
359 		}
360 		return object;
361 	}
362 	return null;
363 }
364 
365 /**
366 * Returns a copy of a list with empty members removed.
367 *
368 * @param list	[array]		original list
369 */
370 AjxUtil.collapseList =
371 function(list) {
372 	var newList = [];
373 	for (var i = 0; i < list.length; i++) {
374 		if (list[i]) {
375 			newList.push(list[i]);
376 		}
377 	}
378 	return newList;
379 };
380 
381 AjxUtil.arrayAsHash =
382 function(array, valueOrFunc) {
383 	array = AjxUtil.toArray(array);
384 	var hash = {};
385 	var func = typeof valueOrFunc == "function" && valueOrFunc;
386 	var value = valueOrFunc || true; 
387 	for (var i = 0; i < array.length; i++) {
388 		var key = array[i];
389 		hash[key] = func ? func(key, hash, i, array) : value;
390 	}
391 	return hash;
392 };
393 
394 AjxUtil.arrayAdd = function(array, member, index) {
395 
396     array = array || [];
397 
398 	if (index == null || index < 0 || index >= array.length) {
399 		// index absent or is out of bounds - append object to the end
400 		array.push(member);
401 	} else {
402 		// otherwise, insert object
403 		array.splice(index, 0, member);
404 	}
405 };
406 
407 AjxUtil.arrayRemove =
408 function(array, member) {
409 	if (array) {
410 		for (var i = 0; i < array.length; i++) {
411 			if (array[i] == member) {
412 				array.splice(i, 1);
413 				return true;
414 			}
415 		}
416 	}
417 	return false;
418 };
419 
420 AjxUtil.indexOf =
421 function(array, object, strict) {
422 	if (array) {
423 		for (var i = 0; i < array.length; i++) {
424 			var item = array[i];
425 			if ((strict && item === object) || (!strict && item == object)) {
426 				return i;
427 			}
428 		}
429 	}
430 	return -1;
431 };
432 
433 AjxUtil.arrayContains =
434 function(array, object, strict) {
435 	return AjxUtil.indexOf(array, object, strict) != -1;
436 };
437 
438 AjxUtil.keys = function(object, acceptFunc) {
439     var keys = [];
440     for (var p in object) {
441 	    if (acceptFunc && !acceptFunc(p, object)) continue;
442         keys.push(p);
443     }
444     return keys;
445 };
446 AjxUtil.values = function(object, acceptFunc) {
447     var values = [];
448     for (var p in object) {
449 	    if (acceptFunc && !acceptFunc(p, object)) continue;
450         values.push(object[p]);
451     }
452     return values;
453 };
454 
455 /**
456  * Generate another hash mapping property values to their names. Each value
457  * should be unique; otherwise the results are undefined.
458  *
459  * @param obj                   An object, treated as a hash.
460  * @param func [function]       An optional function for filtering properties.
461  */
462 AjxUtil.valueHash = function(obj, acceptFunc) {
463     // don't rely on the value in the object itself
464     var hasown = Object.prototype.hasOwnProperty.bind(obj);
465 
466     var r = {};
467     for (var k in obj) {
468         var v = obj[k];
469 
470         if (!hasown(k) || (acceptFunc && !acceptFunc(k, obj)))
471             continue;
472         r[v] = k;
473     }
474     return r;
475 };
476 AjxUtil.backMap = AjxUtil.valueHash;
477 
478 /**
479  * Call a function with the the items in the given object, which special logic
480  * for handling of arrays.
481  *
482  * @param obj                   Array or other object
483  * @param func [function]       Called with index or key and value.
484  */
485 AjxUtil.foreach = function(obj, func) {
486 
487     if (!func || !obj) {
488         return;
489     }
490 
491     if (AjxUtil.isArrayLike(obj)) {
492         var array = obj;
493 
494         for (var i = 0; i < array.length; i++) {
495             func(array[i], i);
496         }
497     }
498     else {
499         // don't rely on the value in the object itself
500         var hasown = Object.prototype.hasOwnProperty.bind(obj);
501 
502         for (var k in obj) {
503             if (hasown(k)) {
504                 func(obj[k], k)
505             }
506         }
507     }
508 };
509 
510 AjxUtil.map = function(array, func) {
511 
512     if (!array) {
513         return [];
514     }
515 
516 	var narray = new Array(array.length);
517 	for (var i = 0; i < array.length; i++) {
518 		narray[i] = func ? func(array[i], i) : array[i];
519 	}
520 
521 	return narray;
522 };
523 
524 AjxUtil.uniq = function(array) {
525 
526     if (!array) {
527         return [];
528     }
529 
530     var object = {};
531 	for (var i = 0; i < array.length; i++) {
532 		object[array[i]] = true;
533 	}
534 
535 	return AjxUtil.keys(object);
536 };
537 
538 
539 /**
540  * Remove duplicates from the given array,
541  * <strong>in-place</strong>. Stable with regards to ordering.
542  *
543  * Please note that this method is O(n^2) if Array is backed by an
544  * array/vector data structure.
545  *
546  * @param array [array]     array to process
547  * @param keyfn [function]  used to extract a comparison key from each
548  *                          list element, default is to compare
549  *                          elements directly. if the comparison key
550  *                          is 'undefined', the element is always
551  *                          retained
552  */
553 AjxUtil.dedup = function(array, keyfn) {
554 
555     if (!array) {
556         return [];
557     }
558 
559     if (!keyfn) {
560 		keyfn = function(v) { return v; };
561     }
562 
563 	var seen = {};
564 
565 	for (var i = 0; i < array.length; i++) {
566 		var key = keyfn(array[i]);
567 
568 		if (key !== undefined && seen[key]) {
569 			array.splice(i, 1);
570 			i -= 1;
571 		}
572 
573 		seen[key] = true;
574 	}
575 };
576 
577 
578 AjxUtil.concat = function(array1 /* ..., arrayN */) {
579 
580 	var array = [];
581 	for (var i = 0; i < arguments.length; i++) {
582 		array.push.apply(array, arguments[i]);
583 	}
584 	return array;
585 };
586 
587 AjxUtil.union = function(array1 /* ..., arrayN */) {
588 	var array = [];
589 	return AjxUtil.uniq(array.concat.apply(array, arguments));
590 };
591 
592 AjxUtil.intersection = function(array1 /* ..., arrayN */) {
593 	var array = AjxUtil.concat.apply(this, arguments);
594 	var object = AjxUtil.arrayAsHash(array, AjxUtil.__intersection_count);
595 	for (var p in object) {
596 		if (object[p] == 1) {
597 			delete object[p];
598 		}
599 	}
600 	return AjxUtil.keys(object);
601 };
602 
603 AjxUtil.__intersection_count = function(key, hash, index, array) {
604 	return hash[key] != null ? hash[key] + 1 : 1;
605 };
606 
607 AjxUtil.complement = function(array1, array2) {
608 	var object1 = AjxUtil.arrayAsHash(array1);
609 	var object2 = AjxUtil.arrayAsHash(array2);
610 	for (var p in object2) {
611 		if (p in object1) {
612 			delete object2[p];
613 		}
614 	}
615 	return AjxUtil.keys(object2);
616 };
617 
618 /**
619  * Returns an array with all the members of the given array for which the filtering function returns true.
620  *
621  * @param {Array}       array       source array
622  * @param {Function}    func        filtering function
623  * @param {Object}      context     scope for filtering function
624  *
625  * @returns {Array} array of members for which the filtering function returns true
626  */
627 AjxUtil.filter = function(array, func, context) {
628 
629 	var results = [];
630 	if (array == null) {
631 		return results;
632 	}
633 
634 	var nativeFilter = Array.prototype.filter;
635 	if (nativeFilter && array.filter === nativeFilter) {
636 		return array.filter(func, context);
637 	}
638 
639 	AjxUtil.foreach(array, function(value, index) {
640 		if (func.call(context, value, index)) {
641 			results.push(value);
642 		}
643 	});
644 
645 	return results;
646 };
647 
648 AjxUtil.getFirstElement = function(parent, ename, aname, avalue) {
649     for (var child = parent.firstChild; child; child = child.nextSibling) {
650         if (child.nodeType != AjxUtil.ELEMENT_NODE) continue;
651         if (ename && child.nodeName != ename) continue;
652         if (aname) {
653             var attr = child.getAttributeNode(aname);
654             if (attr.nodeName != aname) continue;
655             if (avalue && attr.nodeValue != avalue) continue;
656         }
657         return child;
658     }
659     return null;
660 };
661 
662 /**
663  * @param params	[hash]		hash of params:
664  *        relative	[boolean]*	if true, return a relative URL
665  *        protocol	[string]*	protocol (trailing : is optional)
666  *        host		[string]*	server hostname
667  *        port		[int]*		server port
668  *        path		[string]*	URL path
669  *        qsReset	[boolean]*	if true, clear current query string
670  *        qsArgs	[hash]*		set of query string names and values
671  */
672 AjxUtil.formatUrl =
673 function(params) {
674 	params = params || {};
675 	var url = [];
676 	var i = 0;
677 	if (!params.relative) {
678 		var proto = params.protocol || location.protocol;
679 		if (proto.indexOf(":") == -1) {
680 			proto = proto + ":";
681 		}
682 		url[i++] = proto;
683 		url[i++] = "//";
684 		url[i++] = params.host || location.hostname;
685 		var port = Number(params.port || location.port);
686 		if (port &&
687 			((proto == ZmSetting.PROTO_HTTP && port != ZmSetting.HTTP_DEFAULT_PORT) ||
688 			 (proto == ZmSetting.PROTO_HTTPS && port != ZmSetting.HTTPS_DEFAULT_PORT))) {
689 			url[i++] =  ":";
690 			url[i++] = port;
691 		}
692 	}
693 	url[i++] = params.path || location.pathname;
694 	var qs = "";
695 	if (params.qsArgs) {
696 		qs = AjxStringUtil.queryStringSet(params.qsArgs, params.qsReset);
697 	} else {
698 		qs = params.qsReset ? "" : location.search;
699 	}
700 	url[i++] = qs;
701 	
702 	return url.join("");
703 };
704 
705 AjxUtil.byNumber = function(a, b) {
706 	return Number(a) - Number(b);
707 };
708 
709 /**
710  * <strong>Note:</strong>
711  * This function <em>must</em> be wrapped in a closure that passes
712  * the property name as the first argument.
713  *
714  * @param {string}  prop    Property name.
715  * @param {object}  a       Object A.
716  * @param {object}  b       Object B.
717  */
718 AjxUtil.byStringProp = function(prop, a, b) {
719     return a[prop].localeCompare(b[prop]);
720 };
721 
722 /**
723  * returns the size of the given array, i.e. the number of elements in it, regardless of whether the array is associative or not.
724  * so for example for array that is set simply by a = []; a[50] = "abc"; arraySize(a) == 1. For b = []; b["abc"] = "def"; arraySize(b) == 1 too.
725  * Incredibly JavasCript does not have a built in simple way to get that.
726  * @param arr
727  */
728 AjxUtil.arraySize =
729 function(a) {
730 	var size = 0;
731 	for(var e in a) {
732 		if (a.hasOwnProperty(e)) {
733 			size ++;
734 		}
735 	}
736 	return size;
737 };
738 
739 /**
740  * mergesort+dedupe
741 **/
742 AjxUtil.mergeArrays =
743 function(arr1, arr2, orderfunc) {
744 	if(!orderfunc) {
745 		orderfunc = AjxUtil.__mergeArrays_orderfunc;
746 	}
747  	var tmpArr1 = [];
748  	var cnt1 = arr1.length;
749  	for(var i = 0; i < cnt1; i++) {
750  		tmpArr1.push(arr1[i]);
751  	}
752 
753  	var tmpArr2 = [];
754  	var cnt2 = arr2.length;
755  	for(var i = 0; i < cnt2; i++) {
756  		tmpArr2.push(arr2[i]);
757  	} 	
758 	var resArr = [];
759 	while (tmpArr1.length>0 && tmpArr2.length>0) {
760 		if (orderfunc(tmpArr1[0],resArr[resArr.length-1])==0) {
761 			tmpArr1.shift();
762 			continue;
763 		}
764 		
765 		if (orderfunc(tmpArr2[0],resArr[resArr.length-1])==0) {
766 			tmpArr2.shift();
767 			continue;
768 		}		
769 			
770 		if (orderfunc(tmpArr1[0], tmpArr2[0])<0) {
771 			resArr.push(tmpArr1.shift());
772 		} else if (orderfunc(tmpArr1[0],tmpArr2[0])==0) {
773 			resArr.push(tmpArr1.shift());
774 			tmpArr2.shift();
775 		} else {
776 			resArr.push(tmpArr2.shift());
777 		}
778 	}
779 		
780 	while (tmpArr1.length>0) {
781 		if (orderfunc(tmpArr1[0],resArr[resArr.length-1])==0) {
782 			tmpArr1.shift();
783 			continue;
784 		}		
785 		resArr.push(tmpArr1.shift());
786 	}
787 		
788 	while (tmpArr2.length>0) {
789 		if (orderfunc(tmpArr2[0], resArr[resArr.length-1])==0) {
790 			tmpArr2.shift();
791 			continue;
792 		}			
793 		resArr.push(tmpArr2.shift());
794 	}
795 	return resArr;	
796 };
797 
798 AjxUtil.__mergeArrays_orderfunc = function (val1,val2) {
799     if(val1>val2)    return  1;
800     if (val1 < val2) return -1;
801     if(val1 == val2) return  0;
802 };
803 
804 AjxUtil.arraySubtract = function (arr1, arr2, orderfunc) {
805 	if(!orderfunc) {
806 		orderfunc = function (val1,val2) {
807 			if(val1>val2)
808 				return 1;
809 			if (val1 < val2)
810 				return -1;
811 			if(val1 == val2)
812 				return 0;
813 		}
814 	}
815  	var tmpArr1 = [];
816  	var cnt1 = arr1.length;
817  	for(var i = 0; i < cnt1; i++) {
818  		tmpArr1.push(arr1[i]);
819  	}
820 
821  	var tmpArr2 = [];
822  	var cnt2 = arr2.length;
823  	for(var i = 0; i < cnt2; i++) {
824  		tmpArr2.push(arr2[i]);
825  	} 	
826  	tmpArr2.sort(orderfunc);
827  	tmpArr1.sort(orderfunc);
828 	var resArr = [];
829 	while(tmpArr1.length > 0 && tmpArr2.length > 0) {
830 		if(orderfunc(tmpArr1[0],tmpArr2[0])==0) {
831 			tmpArr1.shift();
832 			tmpArr2.shift();
833 			continue;
834 		}
835 		
836 		if(orderfunc(tmpArr1[0],tmpArr2[0]) < 0) {
837 			resArr.push(tmpArr1.shift());
838 			continue;
839 		}
840 		
841 		if(orderfunc(tmpArr1[0],tmpArr2[0]) > 0) {
842 			tmpArr2.shift();
843 			continue;
844 		}
845 	}
846 	
847 	while(tmpArr1.length > 0) {
848 		resArr.push(tmpArr1.shift());
849 	}
850 	
851 	return resArr;
852 };
853 
854 // Support deprecated, misspelled version
855 AjxUtil.arraySubstract = AjxUtil.arraySubtract;
856 
857 /**
858  * Returns the keys of the given hash as a sorted list.
859  *
860  * @param hash		[hash]
861  */
862 AjxUtil.getHashKeys =
863 function(hash) {
864 
865 	var list = [];
866 	for (var key in hash) {
867 		list.push(key);
868 	}
869 	list.sort();
870 
871 	return list;
872 };
873 
874 /**
875  * Does a shallow comparison of two arrays.
876  *
877  * @param arr1	[array]
878  * @param arr2	[array]
879  */
880 AjxUtil.arrayCompare =
881 function(arr1, arr2) {
882 	if ((!arr1 || !arr2) && (arr1 != arr2)) {
883 		return false;
884 	}
885 	if (arr1.length != arr2.length) {
886 		return false;
887 	}
888 	for (var i = 0; i < arr1.length; i++) {
889 		if (arr1[i] != arr2[i]) {
890 			return false;
891 		}
892 	}
893 	return true;
894 };
895 
896 /**
897  * Does a shallow comparison of two hashes.
898  *
899  * @param hash1	[hash]
900  * @param hash2	[hash]
901  */
902 AjxUtil.hashCompare =
903 function(hash1, hash2) {
904 
905 	var keys1 = AjxUtil.getHashKeys(hash1);
906 	var keys2 = AjxUtil.getHashKeys(hash2);
907 	if (!AjxUtil.arrayCompare(keys1, keys2)) {
908 		return false;
909 	}
910 	for (var i = 0, len = keys1.length; i < len; i++) {
911 		var key = keys1[i];
912 		if (hash1[key] != hash2[key]) {
913 			return false;
914 		}
915 	}
916 	return true;
917 };
918 
919 /**
920  * Returns a shallow copy of the given hash.
921  *
922  * @param {Object}  hash    source hash
923  * @param {Array}   omit    Keys to skip (blacklist)
924  * @param {Array}   keep 	Keys to keep (whitelist)
925  */
926 AjxUtil.hashCopy = function(hash, omit, keep) {
927 
928     omit = omit && AjxUtil.arrayAsHash(omit);
929     keep = keep && AjxUtil.arrayAsHash(keep);
930 
931 	var copy = {};
932 	for (var key in hash) {
933         if ((!omit || !omit[key]) && (!keep || keep[key])) {
934     		copy[key] = hash[key];
935         }
936 	}
937 
938 	return copy;
939 };
940 
941 /**
942  * Updates one hash with values from another.
943  *
944  * @param {Object}  hash1		Hash to be updated
945  * @param {Object}  hash2 		Hash to update from (values from hash2 will be copied to hash1)
946  * @param {Boolean} overwrite 	Set to true if existing values in hash1 should be overwritten when keys match (defaults to false)
947  * @param {Array}   omit 	    Keys to skip (blacklist)
948  * @param {Array}   keep        Keys to keep (whitelist)
949  */
950 AjxUtil.hashUpdate = function(hash1, hash2, overwrite, omit, keep) {
951 
952     omit = omit && AjxUtil.arrayAsHash(omit);
953     keep = keep && AjxUtil.arrayAsHash(keep);
954 
955     for (var key in hash2) {
956 		if ((overwrite || !(key in hash1)) && (!omit || !omit[key]) && (!keep || keep[key])) {
957 			hash1[key] = hash2[key];
958 		}
959 	}
960 };
961 
962 // array check that doesn't rely on instanceof, since type info
963 // can get lost in new window
964 AjxUtil.isArray1 =
965 function(arg) {
966 	return !!(arg && (arg.length != null) && arg.splice && arg.slice);
967 };
968 
969 // converts the arg to an array if it isn't one
970 AjxUtil.toArray =
971 function(arg) {
972 	if (!arg) {
973 		return [];
974 	}
975 	else if (AjxUtil.isArray1(arg)) {
976 		return arg;
977 	}
978 	else if (AjxUtil.isArrayLike(arg)) {
979 		try {
980 			// fails in IE8
981 			return Array.prototype.slice.call(arg);
982 		} catch (e) {
983 			return AjxUtil.map(arg);
984 		}
985 	}
986 	else if (arg.isAjxVector) {
987         return arg.getArray();
988 	}
989 	else {
990 		return [arg];
991 	}
992 };
993 
994 /**
995  * Returns a sub-property of an object. This is useful to avoid code like
996  * the following:
997  * <pre>
998  * resp = resp && resp.BatchResponse;
999  * resp = resp && resp.GetShareInfoResponse;
1000  * resp = resp && resp[0];
1001  * </pre>
1002  * <p>
1003  * The first argument to this function is the source object while the
1004  * remaining arguments are the property names of the path to follow.
1005  * This is done instead of as a path string (e.g. "foo/bar[0]") to
1006  * avoid unnecessary parsing.
1007  *
1008  * @param {object}          object  The source object.
1009  * @param {string|number}   ...     The property of the current context object.
1010  */
1011 AjxUtil.get = function(object /* , propName1, ... */) {
1012     for (var i = 1; object && i < arguments.length; i++) {
1013         object = object[arguments[i]];
1014     }
1015     return object;
1016 };
1017 
1018 
1019 /**
1020  *  Convert non-ASCII characters to valid HTML UNICODE entities 
1021  * @param {string}
1022  * 
1023 */
1024 AjxUtil.convertToEntities = function (source){
1025 	var result = '';
1026 	var length = 0;
1027     
1028     if (!source || !(length = source.length)) return source;
1029     
1030 	for (var i = 0; i < length; i++) {
1031 		var charCode = source.charCodeAt(i);
1032 		// Encode non-ascii or double quotes
1033 		if ((charCode > 127) || (charCode == 34)) {
1034 			var temp = charCode.toString(10);
1035 			while (temp.length < 4) {
1036 				temp = '0' + temp;
1037 			}
1038 			result += '&#' + temp + ';';
1039 		} else {
1040 			result += source.charAt(i);
1041 		}
1042 	}
1043 	return result;
1044 };
1045 
1046 /**
1047  *  Get the class attribute string from the given class.
1048  * @param {array} - An array of class names to be converted to a class attribute.
1049  * returns the attribute string with the class names or empty string if no class is passed.
1050 	*
1051  */
1052 AjxUtil.getClassAttr = function (classes){
1053 	var attr = [];
1054 	if (classes && classes.length > 0) {
1055 		//remove duplicate css classes
1056 		classes = AjxUtil.uniq(classes);
1057 		return ["class='" , classes.join(" "), "'"].join("");
1058 	}
1059 	return "";
1060 };
1061 
1062 /**
1063  * converts datauri string to blob object used for uploading the image
1064  * @param {dataURI} - datauri string  data:image/png;base64,iVBORw0
1065  *
1066  */
1067 AjxUtil.dataURItoBlob =
1068 function (dataURI) {
1069 
1070     if (!(dataURI && typeof window.atob === "function" && typeof window.Blob === "function")) {
1071         return;
1072     }
1073 
1074     var dataURIArray = dataURI.split(",");
1075     if (dataURIArray.length === 2) {
1076         if (dataURIArray[0].indexOf('base64') === -1) {
1077             return;
1078         }
1079         // convert base64 to raw binary data held in a string
1080         // doesn't handle URLEncoded DataURIs
1081         try{
1082             var byteString = window.atob(dataURIArray[1]);
1083         }
1084         catch(e){
1085             return;
1086         }
1087         if (!byteString) {
1088             return;
1089         }
1090         // separate out the mime component
1091         var mimeString = dataURIArray[0].split(':');
1092         if (!mimeString[1]) {
1093             return;
1094         }
1095         mimeString = mimeString[1].split(';')[0];
1096         if (mimeString) {
1097             // write the bytes of the string to an ArrayBuffer
1098             var byteStringLength = byteString.length,
1099                 ab = new ArrayBuffer(byteStringLength),
1100                 ia = new Uint8Array(ab);
1101             for (var i = 0; i < byteStringLength; i++) {
1102                 ia[i] = byteString.charCodeAt(i);
1103             }
1104             return new Blob([ab], {"type" : mimeString});
1105         }
1106     }
1107 
1108 };
1109 
1110 AjxUtil.reduce = function(array, callback, opt_initialValue) {
1111 	var reducefn = Array.prototype.reduce;
1112 
1113 	if (reducefn) {
1114 		return reducefn.call(array, callback, opt_initialValue);
1115 	} else {
1116 		// polyfill from the Mozilla Developer Network for browsers without
1117 		// reduce -- i.e. IE8.
1118 
1119 		if (array === null || 'undefined' === typeof array) {
1120 			throw new TypeError('AjxUtil.reduce called on null or undefined');
1121 		}
1122 		if ('function' !== typeof callback) {
1123 			throw new TypeError(callback + ' is not a function');
1124 		}
1125 		var index, value,
1126 		length = array.length >>> 0,
1127 		isValueSet = false;
1128 		if (1 < arguments.length) {
1129 			value = opt_initialValue;
1130 			isValueSet = true;
1131 		}
1132 		for (index = 0; length > index; ++index) {
1133 			if (array.hasOwnProperty(index)) {
1134 				if (isValueSet) {
1135 					value = callback(value, array[index], index, array);
1136 				}
1137 				else {
1138 					value = array[index];
1139 					isValueSet = true;
1140 				}
1141 			}
1142 		}
1143 		if (!isValueSet) {
1144 			throw new TypeError('Reduce of empty array with no initial value');
1145 		}
1146 		return value;
1147 	}
1148 
1149 };
1150 
1151 
1152 /**
1153  * Returns a value for the brightness of the given color.
1154  *
1155  * @param   {string}    rgb     RGB value as #RRGGBB
1156  * @returns {number}    number between 0 and 255 (higher is brighter)
1157  */
1158 AjxUtil.getBrightness = function(rgb) {
1159 
1160 	var r, g, b;
1161 
1162 	if (rgb && rgb.length === 7 && rgb.indexOf('#') === 0) {
1163 		rgb = rgb.substr(1);
1164 	}
1165 	else {
1166 		return null;
1167 	}
1168 
1169 	r = parseInt(rgb.substr(0, 2), 16);
1170 	g = parseInt(rgb.substr(2, 2), 16);
1171 	b = parseInt(rgb.substr(4, 2), 16);
1172 
1173 	// http://alienryderflex.com/hsp.html
1174 	return Math.sqrt(
1175 		r * r * .299 +
1176 			g * g * .587 +
1177 			b * b * .114
1178 	);
1179 };
1180 
1181 /**
1182  * Returns the better foreground color based on contrast with the given background color.
1183  *
1184  * @param   {string}    bgColor     RGB value as #RRGGBB
1185  * @returns {string}    'black' or 'white'
1186  */
1187 AjxUtil.getForegroundColor = function(bgColor) {
1188 	var brightness = AjxUtil.getBrightness(bgColor);
1189 	return (brightness != null && brightness < 130) ? 'white' : 'black';
1190 };
1191 
1192 
1193