Source: Tracker/utils.js

/**
 * @class
 * @classdesc Utility methods.
 * @name Utils
 * @public
 */
var Utils = function () {

    var self = this;

    var rvalidtokens = /(,)|(\[|{)|(}|])|"(?:[^"\\\r\n]|\\["\\\/bfnrt]|\\u[\da-fA-F]{4})*"\s*:?|true|false|null|-?(?!0\d)\d+(?:\.\d+|)(?:[eE][+-]?\d+|)/g;
    var whitespace = "[\\x20\\t\\r\\n\\f]";
    var rtrim = new RegExp("^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g");

    /**
     * Serialize any element.
     * @memberof Utils#
     * @param obj {Object} Object to serialize
     * @return {String}
     * @private
     */
    function serialJSON(obj) {
        var t = typeof obj;
        if (t !== 'object' || obj === null) {
            if (t === 'string') {
                obj = '"' + obj + '"';
            }
            return String(obj);
        } else {
            var n, v, json = [],
                arr = (obj.constructor === Array);
            for (n in obj) {
                if (obj.hasOwnProperty(n)) {
                    v = obj[n];
                    t = typeof (v);
                    if (t !== 'function' && t !== 'undefined') {
                        if (t === 'string') {
                            v = '"' + v.replace(/[^\\]"/g, '\\"') + '"';
                        } else if (t === 'object' && v !== null) {
                            v = serialJSON(v);
                        }
                        json.push((arr ? '' : '"' + n + '":') + String(v));
                    }
                }
            }
            return (arr ? '[' : '{') + String(json) + (arr ? ']' : '}');
        }
    }

    /**
     * Trim JSON string.
     * @memberof Utils#
     * @param text {String} Text to trim
     * @return {String}
     * @private
     */
    function trimJSONString(text) {
        return text === null ?
            '' :
            (text + '').replace(rtrim, '');
    }

    /**
     * Parse any string.
     * @memberof Utils#
     * @param data {string} String to parse
     * @return {*}
     * @private
     */
    function parseJSON(data) {
        var requireNonComma,
            depth = null,
            str = trimJSONString(data + '');
        // Guard against invalid (and possibly dangerous) input by ensuring that nothing remains
        // after removing valid tokens
        return str && !trimJSONString(str.replace(rvalidtokens, function (token, comma, open, close) {
            // Force termination if we see a misplaced comma
            if (requireNonComma && comma) {
                depth = 0;
            }
            // Perform no more replacements after returning to outermost depth
            if (depth === 0) {
                return token;
            }
            // Commas must not follow "[", "{", or ","
            requireNonComma = open || comma;
            // Determine new depth
            // array/object open ("[" or "{"): depth += true - false (increment)
            // array/object close ("]" or "}"): depth += false - true (decrement)
            // other cases ("," or primitive): depth += true - true (numeric cast)
            depth += !close - !open;
            // Remove this token
            return '';
        })) ?
            (Function('return ' + str))() :
            null;
    }

    /**
     * Check if local storage is both supported and available
     * @name isLocalStorageAvailable
     * @memberof Utils#
     * @return {boolean}
     * @public
     */
    self.isLocalStorageAvailable = function () {
        try {
            var storage = localStorage,
                x = '__storage_test__';
            storage.setItem(x, x);
            storage.removeItem(x);
            return true;
        } catch (e) {
            return false;
        }
    };

    /**
     * Check if beacon method is supported and available
     * @name isBeaconMethodAvailable
     * @memberof Utils#
     * @return {boolean}
     * @public
     */
    self.isBeaconMethodAvailable = function () {
        return (window.navigator && typeof window.navigator.sendBeacon === "function");
    };

    /**
     * Get a stored value from name
     * @name getStorageData
     * @memberof Utils#
     * @function
     * @param name {String} Name of the stored data
     * @return {*} Return null if the stored data does not exist
     * @private
     */
    function getStorageData(name) {
        var storedData = null;
        if (self.isLocalStorageAvailable()) {
            storedData = localStorage.getItem(name)
        }
        if (storedData === null) {
            var cookies = document.cookie;
            var regExp = new RegExp('(?:^| )' + name + '=([^;]+)');
            var result = regExp.exec(cookies);
            if (result !== null) {
                storedData = result[1];
            }
        }
        if (storedData !== null) {
            try {
                storedData = decodeURIComponent(storedData);
            } catch (e) {
            }
        }
        return storedData;
    }

    /**
     * Base64 encode / decode
     * *http://www.webtoolkit.info/
     * @memberof Utils#
     * @Object
     * @public
     */
    self.Base64 = {
        // private property
        _keyStr: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
        // public method for encoding
        encode: function (input) {
            var output = "";
            var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
            var i = 0;
            input = self.Base64._utf8_encode(input);
            while (i < input.length) {
                chr1 = input.charCodeAt(i++);
                chr2 = input.charCodeAt(i++);
                chr3 = input.charCodeAt(i++);
                enc1 = chr1 >> 2;
                enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
                enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
                enc4 = chr3 & 63;
                if (isNaN(chr2)) {
                    enc3 = enc4 = 64;
                } else if (isNaN(chr3)) {
                    enc4 = 64;
                }
                output = output +
                    this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
                    this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);
            }
            return output;
        },
        // public method for decoding
        decode: function (input) {
            var output = "";
            var chr1, chr2, chr3;
            var enc1, enc2, enc3, enc4;
            var i = 0;
            input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
            while (i < input.length) {
                enc1 = this._keyStr.indexOf(input.charAt(i++));
                enc2 = this._keyStr.indexOf(input.charAt(i++));
                enc3 = this._keyStr.indexOf(input.charAt(i++));
                enc4 = this._keyStr.indexOf(input.charAt(i++));
                chr1 = (enc1 << 2) | (enc2 >> 4);
                chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
                chr3 = ((enc3 & 3) << 6) | enc4;
                output = output + String.fromCharCode(chr1);
                if (enc3 != 64) {
                    output = output + String.fromCharCode(chr2);
                }
                if (enc4 != 64) {
                    output = output + String.fromCharCode(chr3);
                }
            }
            output = self.Base64._utf8_decode(output);
            return output;
        },
        // private method for UTF-8 encoding
        _utf8_encode: function (string) {
            string = string.replace(/\r\n/g, "\n");
            var utftext = "";
            for (var n = 0; n < string.length; n++) {
                var c = string.charCodeAt(n);
                if (c < 128) {
                    utftext += String.fromCharCode(c);
                } else if ((c > 127) && (c < 2048)) {
                    utftext += String.fromCharCode((c >> 6) | 192);
                    utftext += String.fromCharCode((c & 63) | 128);
                } else {
                    utftext += String.fromCharCode((c >> 12) | 224);
                    utftext += String.fromCharCode(((c >> 6) & 63) | 128);
                    utftext += String.fromCharCode((c & 63) | 128);
                }
            }
            return utftext;
        },
        // private method for UTF-8 decoding
        _utf8_decode: function (utftext) {
            var string = "";
            var i = 0;
            var c, c2, c3;
            c = c2 = c3 = 0;
            while (i < utftext.length) {
                c = utftext.charCodeAt(i);
                if (c < 128) {
                    string += String.fromCharCode(c);
                    i++;
                } else if ((c > 191) && (c < 224)) {
                    c2 = utftext.charCodeAt(i + 1);
                    string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
                    i += 2;
                } else {
                    c2 = utftext.charCodeAt(i + 1);
                    c3 = utftext.charCodeAt(i + 2);
                    string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
                    i += 3;
                }
            }
            return string;
        }
    };

    /**
     * Load a script.
     * @name loadScript
     * @memberof Utils#
     * @function
     * @param scriptObj {object} Object containing script properties (url)
     * @param callback {function} Function to call on success or on error
     * @public
     */
    self.loadScript = function (scriptObj, callback) {
        var newScript;
        callback = callback || function () {
        };

        var errorCallback = function (event) {
            newScript.onload = newScript.onreadystatechange = newScript.onerror = null;
            callback({msg: 'script not loaded', event: event}); // first param not null means an error has occured
        };
        var onloadCallback = function (event) {
            event = event || window.event;
            // Check for different events in IEs
            if (event.type === "load" || (/loaded|complete/.test(newScript.readyState) && (!document.documentMode || document.documentMode < 9))) {
                newScript.onload = newScript.onreadystatechange = newScript.onerror = null;
                callback(null, event);
            }
        };
        newScript = document.createElement("script");
        newScript.type = "text/javascript";
        newScript.src = scriptObj.url;
        newScript.async = false;
        newScript.defer = false;
        newScript.onload = newScript.onreadystatechange = onloadCallback;
        newScript.onerror = errorCallback;
        var head = document.head || document.getElementsByTagName("head")[0];
        head.insertBefore(newScript, head.lastChild);
    };

    // We deliberately ignore the second parameter reserved for us :)
    /**
     * Copy an object which doesnt' contain functions/objects.
     * @name cloneSimpleObject
     * @memberof Utils#
     * @inner
     * @function
     * @param inst {Object} Item that you want to clone
     * @param delUndefined {boolean} If true, undefined properties are not cloned
     * @return {Object} clone of the instance
     * @public
     */
    self.cloneSimpleObject = function (inst, delUndefined) {
        /*Si l'instance source n'est pas un objet ou qu'elle ne vaut rien c'est une feuille donc on la retourne*/
        if (typeof (inst) !== 'object' || inst === null || inst instanceof Date) {
            return inst;
        }
        /*On appelle le constructeur de l'instance source pour créer une nouvelle instance de la même classe*/
        var newInst = new inst.constructor;
        /*On parcourt les propriétés de l'objet et on les recopie dans la nouvelle instance*/
        for (var i in inst) {
            if (inst.hasOwnProperty(i)) {
                //On gère le cas où il a été demandé (delUndefined) de ne pas copier les propriétés undefined
                if (i !== undefined && (!delUndefined || inst[i] !== undefined)) {
                    newInst[i] = self.cloneSimpleObject(inst[i]);
                }
            }
        }
        /*On retourne la nouvelle instance*/
        return newInst;
    };

    /**
     * Check if object is empty.
     * @name isEmptyObject
     * @memberof Utils#
     * @function
     * @param tagObject {Object}
     * @return {boolean}
     * @public
     */
    self.isEmptyObject = function (tagObject) {
        for (var p in tagObject) {
            if (tagObject.hasOwnProperty(p)) {
                return false;
            }
        }
        return true;
    };

    /**
     * Check if parameter is an object.
     * @name isObject
     * @memberof Utils#
     * @function
     * @param contextualObject {*}
     * @return {boolean}
     * @public
     */
    self.isObject = function (contextualObject) {
        return ((contextualObject !== null) && (typeof contextualObject === 'object') && !(contextualObject instanceof Array));
    };

    // Constants to manage flattening
    self.ATVALUE = '_ATVALUE';
    self.ATPREFIX = '_ATPREFIX';

    /**
     * Adding flatten property with value from source object into destination.
     * @memberof Utils#
     * @function
     * @param source {Object} Source object
     * @param parentPath {string|null} Path from parent
     * @param destination {Object} Final object
     * @param parentPrefix {string|null} Prefix from parent
     * @param toLower {boolean} Put the key to the property in lower case
     * @public
     */
    self.object2Flatten = function (source, parentPath, destination, parentPrefix, toLower) {
        var splittedObject = {};
        var prefix = '';
        var path = '';
        var levels = [];
        var newPath = '';
        var i = 0;
        for (var sourceKey in source) {
            if (source.hasOwnProperty(sourceKey)) {
                splittedObject = self.splitProtocolAndKey(sourceKey, toLower);
                prefix = splittedObject.prefix || parentPrefix || '';
                path = (parentPath ? parentPath + '_' : '') + splittedObject.key;
                if (self.isObject(source[sourceKey])) {
                    self.object2Flatten(source[sourceKey], path, destination, prefix, toLower);
                } else {
                    levels = path.split('_');
                    newPath = '';
                    for (i = 0; i < levels.length; i++) {
                        splittedObject = self.splitProtocolAndKey(levels[i], toLower);
                        prefix = splittedObject.prefix || prefix;
                        newPath += splittedObject.key + ((i < levels.length - 1) ? '_' : '');
                    }
                    path = newPath || path;
                    destination[path] = destination[path] || {};
                    destination[path][self.ATVALUE] = source[sourceKey];
                    destination[path][self.ATPREFIX] = prefix;
                }
            }
        }
    };

    /**
     * Create object from flatten property.
     * @memberof Utils#
     * @function
     * @param destination {Object} Final object
     * @param sourceKey {string} Key to process
     * @param sourceValue {Object} Value to set
     * @public
     */
    self.flatten2Object = function (destination, sourceKey, sourceValue) {

        var levels = sourceKey.split('_');
        var parentObject = destination;
        var currentLevel;

        // Scrolling through data object
        var i;
        for (i = 0; i < levels.length - 1; i++) {
            currentLevel = levels[i];
            if (!parentObject[currentLevel]) {
                parentObject[currentLevel] = {};
            }
            parentObject = parentObject[currentLevel];
        }

        // Update parent object if necessary
        if (parentObject.hasOwnProperty(self.ATVALUE)) {
            var parentValue = parentObject[self.ATVALUE];
            var parentPrefix = parentObject[self.ATPREFIX];
            delete parentObject[self.ATVALUE];
            delete parentObject[self.ATPREFIX];
            parentObject['$'] = {};
            parentObject['$'][self.ATVALUE] = parentValue;
            parentObject['$'][self.ATPREFIX] = parentPrefix;
        }

        // Adding current value
        var sourceCopyValue = self.cloneSimpleObject(sourceValue);
        if (parentObject[levels[i]]) {
            parentObject[levels[i]]['$'] = sourceCopyValue;
        } else {
            parentObject[levels[i]] = sourceCopyValue;
        }

    };

    /**
     * Get formatted content object from flatten source.
     * @memberof ATInternet.Tracker.Plugins.AvInsights#
     * @function
     * @param source {Object} Source object
     * @public
     */
    self.getFormattedObject = function (source) {
        var content = {};
        var newKey;
        for (var key in source) {
            if (source.hasOwnProperty(key)) {
                if (!source[key].hasOwnProperty(self.ATVALUE)) {
                    content[key] = self.getFormattedObject(source[key]);
                } else {
                    newKey = source[key][self.ATPREFIX] ? source[key][self.ATPREFIX] + ':' + key : key;
                    content[newKey] = source[key][self.ATVALUE];
                }
            }
        }
        return content;
    };

    /**
     * Complete the first level of an object with another.
     * @name completeFstLevelObj
     * @memberof Utils#
     * @function
     * @param target {object}
     * @param source {object}
     * @param overload {boolean} If true, properties of the target will be overloaded by source ones if they exist
     * @return {object}
     * @public
     */
    self.completeFstLevelObj = function (target, source, overload) {
        if (target) {
            if (source) {
                for (var key in source) {
                    if (source.hasOwnProperty(key)) {
                        if (!target[key] || overload) {
                            target[key] = source[key];
                        }
                    }
                }
            }
        } else {
            target = source;
        }
        return target;
    };

    /**
     * Get all keys from object.
     * @name getObjectKeys
     * @memberof Utils#
     * @function
     * @param object {object}
     * @return {Array}
     * @public
     */
    self.getObjectKeys = function (object) {
        var keys = [];
        for (var elem in object) {
            if (object.hasOwnProperty(elem)) {
                keys.push(elem);
            }
        }
        return keys;
    };

    /**
     * Change the keys of object to lowercase
     * @name objectToLowercase
     * @memberof Utils#
     * @function
     * @param tagObject {Object} Object from tag
     * @return {Object}
     * @public
     */
    self.objectToLowercase = function (tagObject) {
        var objUtil = {};
        for (var key in tagObject) {
            if (tagObject.hasOwnProperty(key)) {
                if (self.isObject(tagObject[key])) {
                    objUtil[key.toLowerCase()] = self.objectToLowercase(tagObject[key]);
                } else {
                    objUtil[key.toLowerCase()] = tagObject[key];
                }
            }
        }
        return objUtil;
    };

    /**
     * Process event key.
     * @name splitProtocolAndKey
     * @memberof Utils#
     * @function
     * @param key {String} Event key to split
     * @param toLower {boolean} Put the key to the property in lower case
     * @return {Object}
     * @public
     */
    self.splitProtocolAndKey = function (key, toLower) {
        var _prefix, _key;
        if ((key.length < 2) || (key[1] !== ':')) {
            _prefix = '';
            _key = key;
        } else if ((key.length < 4) || (key[3] !== ':')) {
            _prefix = key.substring(0, 1);
            _key = key.substring(2, key.length);
        } else {
            _prefix = key.substring(0, 3);
            _key = key.substring(4, key.length);
        }
        if (toLower) {
            _prefix = _prefix.toLowerCase();
            _key = _key.toLowerCase();
        }
        return {
            'prefix': _prefix,
            'key': _key
        };
    };

    /**
     * Serialize any element. Use JSON.stringify if possible.
     * @name jsonSerialize
     * @memberof Utils#
     * @function
     * @param obj {Object} Javascript object that you want to serialize to JSON
     * @return {Object|String} Serialized JSON
     * @public
     */
    self.jsonSerialize = function (obj) {
        try {
            if (typeof JSON !== 'undefined' && JSON.stringify) {
                return JSON.stringify(obj);
            } else {
                return serialJSON(obj);
            }
        } catch (e) {
            return null;
        }
    };

    /**
     * Parse a string. Use JSON.parse if possible.
     * @name jsonParse
     * @memberof Utils#
     * @function
     * @param str {String} JSON string that you want to parse to Javascript object
     * @return {Object}
     * @public
     */
    self.jsonParse = function (str) {
        try {
            if (typeof JSON !== 'undefined' && JSON.parse) {
                return JSON.parse(str + '');
            } else {
                return parseJSON(str);
            }
        } catch (e) {
            return null;
        }
    };

    /**
     * Trim a string. Use String.prototype.trim if possible.
     * @name trim
     * @memberof Utils#
     * @function
     * @param str {String} string to trim
     * @return {String}
     * @public
     */
    self.trim = function (str) {
        try {
            if (!String.prototype.trim) {
                // Polyfill
                return str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
            }
            return str.trim();
        } catch (e) {
            return str;
        }
    };

    /**
     * Search an element in an array. Use Array.indexOf if possible.
     * @name arrayIndexOf
     * @memberof Utils#
     * @function
     * @param arr {Array}
     * @param elemToSearch {*}
     * @return {number}
     * @public
     */
    self.arrayIndexOf = function (arr, elemToSearch) {
        if (Array.prototype.indexOf) {
            var index = -1;
            if (typeof arr.indexOf(elemToSearch) !== 'undefined') {
                index = arr.indexOf(elemToSearch)
            }
            return index;
        } else {
            return (function (searchElement) {
                "use strict";
                if (this == null) {
                    throw new TypeError();
                }
                var t = Object(this);
                var len = t.length >>> 0;
                if (len === 0) {
                    return -1;
                }
                var n = 0;
                if (arguments.length > 1) {
                    n = Number(arguments[1]);
                    if (n != n) { // shortcut for verifying if it's NaN
                        n = 0;
                    } else if (n != 0 && n != Infinity && n != -Infinity) {
                        n = (n > 0 || -1) * Math.floor(Math.abs(n));
                    }
                }
                if (n >= len) {
                    return -1;
                }
                var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
                for (; k < len; k++) {
                    if (k in t && t[k] === searchElement) {
                        return k;
                    }
                }
                return -1;
            }).apply(arr, [elemToSearch]);
        }
    };

    /**
     * Generate UUID.
     * @name uuid
     * @memberof Utils#
     * @function
     * @return {function}
     * @public
     */
    self.uuid = function () {

        var _cryptoObj = window.crypto || window.msCrypto; // IE11
        var _isCryptoAvailable = (_cryptoObj !== null && typeof _cryptoObj === 'object');

        /* @if test */
        if (typeof self.isCryptoAvailable === 'boolean') {
            _isCryptoAvailable = self.isCryptoAvailable;
        }

        /* @endif */

        /**
         * Generate GUID with alphanumeric characters (v4 format) with Crypto or Math random.
         * @name _crypto
         * @memberof Utils#
         * @function
         * @return {string}
         * @public
         */
        function _v4() {
            try {
                if (_isCryptoAvailable) {
                    return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, function (c) {
                        return (c ^ _cryptoObj.getRandomValues(new Uint32Array(1))[0] & 15 >> c / 4).toString(16);
                    });
                }
            } catch (e) {
            }
            return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
                var r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
                return v.toString(16);
            });
        }

        /**
         * Generate a random number of n digits with Crypto or Math random.
         * @name _mathRand
         * @memberof Utils#
         * @function
         * @return {number}
         * @public
         */
        function _rand(n) {
            var rand = Math.random();
            try {
                if (_isCryptoAvailable) {
                    rand = _cryptoObj.getRandomValues(new Uint32Array(1))[0] / Math.pow(2, 32);
                }
            } catch (e) {
            }
            return Math.floor((rand * 9 + 1) * Math.pow(10, n - 1));
        }

        /**
         * Generate GUID with numeric characters.
         * @name _num
         * @memberof Utils#
         * @function
         * @param len {number} Length of the generated GUID
         * @return {string}
         * @public
         */
        function _num(len) {
            var d = new Date();
            var format = function (a) {
                a = a - Math.floor(a / 100) * 100;
                if (a < 10) {
                    return '0' + a;
                } else {
                    return String(a);
                }
            };
            return format(d.getHours()) + '' + format(d.getMinutes()) + '' + format(d.getSeconds()) + '' + _rand(len - 6);
        }

        return {
            v4: _v4,
            num: _num
        };
    };

    /**
     * Check if we are in Safari previewing case.
     * @name isPreview
     * @memberof Utils#
     * @function
     * @return {boolean}
     * @public
     */
    self.isPreview = function () {
        return (window.navigator && window.navigator.loadPurpose === 'preview');
    };

    /**
     * Check if we are in Chrome or IE prerendering case.
     * @name isPrerender
     * @memberof Utils#
     * @function
     * @param callback {function}
     * @return {boolean}
     * @public
     */
    self.isPrerender = function (callback) {
        var visibilityChangeEvent;
        var isPrerender = false;
        //Prefixes: Chrome, IE
        var prefixes = ['webkit', 'ms'];
        if (document.visibilityState === 'prerender') {
            //Opera 12.10 and Firefox 18 and later support
            visibilityChangeEvent = 'visibilitychange';
        } else {
            for (var i = 0; i < prefixes.length; i++) {
                if (document[prefixes[i] + 'VisibilityState'] === 'prerender') {
                    visibilityChangeEvent = prefixes[i] + 'visibilitychange';
                }
            }
        }
        if (typeof visibilityChangeEvent !== 'undefined') {
            var _manageCallback = function (event) {
                callback(event);
                self.removeEvtListener(document, visibilityChangeEvent, _manageCallback);
            };
            self.addEvtListener(document, visibilityChangeEvent, _manageCallback);
            isPrerender = true;
        }
        return isPrerender;
    };

    /**
     * Add an event listener to an object.
     * @name addEvtListener
     * @memberof Utils#
     * @function
     * @param obj {Object} DOM Element on which you want to add the event listener
     * @param event {string} Event you need to listen
     * @param callback {function} When event is triggered, it takes one parameter which is the event object
     * @public
     */
    self.addEvtListener = function (obj, event, callback) {
        if (obj.addEventListener) {
            obj.addEventListener(event, callback, false);
        } else if (obj.attachEvent) {
            obj.attachEvent('on' + event, callback);
        }
    };

    /**
     * Remove an event listener from an object.
     * @name removeEvtListener
     * @memberof Utils#
     * @function
     * @param obj {Object} DOM Element on which you want to add the event listener
     * @param event {string} Event you need to listen
     * @param callback {function}
     * @public
     */
    self.removeEvtListener = function (obj, event, callback) {
        if (obj.removeEventListener) {
            obj.removeEventListener(event, callback, false);
        } else if (obj.detachEvent) {
            obj.detachEvent('on' + event, callback);
        }
    };

    /**
     * Make a unique number with a given string.
     * @name hashcode
     * @memberof Utils#
     * @function
     * @param str
     * @return {number}
     * @public
     */
    self.hashcode = function (str) {
        var hash = 0;
        if (str.length === 0) return hash;
        for (var i = 0; i < str.length; i++) {
            var character = str.charCodeAt(i);
            hash = ((hash << 5) - hash) + character;
            hash |= 0;
        }
        return hash;
    };

    /**
     * Force document location.
     * @name setLocation
     * @memberof Utils#
     * @function
     * @param contextObj {Object} Redirection's url and target (properties location & target)
     * @public
     */
    self.setLocation = function (contextObj) {
        var loc = contextObj['location'];
        var obj = window[contextObj['target']] || window;
        if (loc) {
            obj.location.href = loc;
        }
    };

    /****************************** Callback management *************************/

    /**
     * Dispatch event for callbacks
     * @name dispatchCallbackEvent
     * @memberof Utils#
     * @function
     * @param name {string} Callback's name
     * @public
     */
    self.dispatchCallbackEvent = function (name) {
        // Create the event.
        var event;
        if (typeof window.Event === 'function') {
            event = new Event('ATCallbackEvent');
        } else {
            try {
                event = document.createEvent('Event');
                // Define that the event name is 'ATCallbackEvent'.
                // Deprecated.
                event.initEvent && event.initEvent('ATCallbackEvent', true, true);
            } catch (e) {
            }
        }
        if (event && (typeof document.dispatchEvent === 'function')) {
            event.name = name;
            document.dispatchEvent(event);
        }
    };

    /**
     * Add callback event
     * @name addCallbackEvent
     * @memberof Utils#
     * @function
     * @param func {function} function to execute
     * @public
     */
    self.addCallbackEvent = function (func) {
        // Listen to the event.
        self.addEvtListener(document, 'ATCallbackEvent', func);
    };

    /**
     * Remove callback event
     * @name removeCallbackEvent
     * @memberof Utils#
     * @param func {function} function to remove
     * @function
     * @public
     */
    self.removeCallbackEvent = function (func) {
        self.removeEvent('ATCallbackEvent', func);
    };

    /****************************** Custom Event management *************************/

    /**
     * Polyfill the CustomEvent() constructor functionality in Internet Explorer 9 and higher
     * @memberof Utils#
     * @function
     * @private
     */
    (function () {
        if (typeof window.CustomEvent === 'function') {
            window.ATCustomEvent = window.CustomEvent;
            return;
        }

        function ATCustomEvent(evt, params) {
            params = params || {bubbles: false, cancelable: false, detail: undefined};
            var event;
            try {
                event = document.createEvent('CustomEvent');
                event.initCustomEvent(evt, params.bubbles, params.cancelable, params.detail);
            } catch (e) {
            }
            return event;
        }

        if (typeof window.Event === "function") {
            ATCustomEvent.prototype = window.Event.prototype;
        }
        window.ATCustomEvent = ATCustomEvent;
    })();

    /**
     * Add custom event
     * @name addEvent
     * @memberof Utils#
     * @param eventName {string} event name
     * @param detailName {string} detail name
     * @param detailID {number} detail uuid
     * @param func {function} function to execute
     * @function
     * @public
     */
    self.addEvent = function (eventName, detailName, detailID, func) {
        self[eventName] = new ATCustomEvent(eventName, {
            detail: {
                name: detailName,
                id: detailID
            }
        });
        // Listen to the event.
        self.addEvtListener(document, eventName, func);
    };

    /**
     * Remove custom event
     * @name removeEvent
     * @memberof Utils#
     * @param eventName {string} event name
     * @param func {function} function to execute
     * @function
     * @public
     */
    self.removeEvent = function (eventName, func) {
        // Remove the event listener.
        self.removeEvtListener(document, eventName, func);
    };

    /**
     * Dispatch custom event
     * @name dispatchEvent
     * @memberof Utils#
     * @param eventName {string} event name
     * @param detailName {string} detail name
     * @function
     * @public
     */
    self.dispatchEvent = function (eventName, detailName) {
        // Init the event.
        self[eventName] = self[eventName] || new ATCustomEvent(eventName, {
            detail: {
                name: detailName,
                id: -1
            }
        });
        try {
            document.dispatchEvent(self[eventName]);
        } catch (e) {
        }
    };

    /****************************** Privacy management *************************/

    /**
     * Object used to manage authority
     * @name Privacy
     * @memberof Utils#
     * @inner
     * @constructor
     * @property {function} processStorageParam Authority helper, see details here {@link Utils#Privacy.processStorageParam}
     * @property {function} processBufferParam Authority helper, see details here {@link Utils#Privacy.processBufferParam}
     * @property {function} setParameters Authority helper, see details here {@link Utils#Privacy.setParameters}
     * @property {function} getParameters Authority helper, see details here {@link Utils#Privacy.getParameters}
     * @property {function} resetParameters Authority helper, see details here {@link Utils#Privacy.resetParameters}
     * @private
     */
    var Privacy = function () {

        /* -------- Privacy properties -------- */
        var _thisPrivacy = this;
        var _privacyParameters = {
            storageParams: null,
            bufferParams: null
        };
        _thisPrivacy.CONSENTNO = 'Consent-NO';
        _thisPrivacy.ALL = '*';

        /* -------- Privacy internal methods -------- */

        /**
         * Test if parameter can be stored
         * @memberof Utils#
         * @param param {string} Parameter to store
         * @param prop {string} Property to store
         * @param toInclude {Object} Object containing parameters and properties to be included
         * @function
         * @return {Object}
         * @private
         * @example
         * // param
         * // "atredir"
         * //
         * // prop
         * // 'at'
         * //
         * // toInclude
         * // {"atredir":["at"]}
         */
        function _testStorageParamObject(param, prop, toInclude) {
            var _storageParamsArray;
            for (var storageObjectProperty in toInclude) {
                if (toInclude.hasOwnProperty(storageObjectProperty)) {
                    if (param === storageObjectProperty) {
                        if (!prop) {
                            return {"toSetInStorage": true};
                        }
                        _storageParamsArray = [];
                        if (toInclude[storageObjectProperty] instanceof Array) {
                            _storageParamsArray = toInclude[storageObjectProperty];
                        } else {
                            _storageParamsArray.push(toInclude[storageObjectProperty]);
                        }
                        for (var j = 0; j < _storageParamsArray.length; j++) {
                            if (_storageParamsArray[j] === prop) {
                                return {"toSetInStorage": true};
                            }
                        }
                    }
                }
            }
            return {"toSetInStorage": false};
        }

        /**
         * Process storage parameters
         * @memberof Utils#
         * @param param {string} Parameter to store
         * @param value {Object} Parameter value to process
         * @param toInclude {string|Array} Properties to be included
         * @param delCallback {function} Callback to delete in storage
         * @param getCallback {function} Callback to get from storage
         * @function
         * @private
         * @example
         * // param :
         * // "atidvisitor"
         * //
         * // value :
         * // {
         * //      "vrn": "-410501--123456-",
         * //      "at": "abcd123",
         * //      "ac": "21"
         * //  }
         * //
         * // toInclude :
         * // "an"
         * // ["an", "at"]
         */
        function _processStorageParamObject(param, value, toInclude, delCallback, getCallback) {
            if (typeof toInclude !== 'undefined') {
                var _toIncludeArray = [];
                if (toInclude instanceof Array) {
                    _toIncludeArray = toInclude;
                } else {
                    _toIncludeArray.push(toInclude);
                }
                // We browse the list of keys contained in the value
                for (var key in value) {
                    // An entry is deleted if it is not part of the inclusion list
                    if (value.hasOwnProperty(key) && self.arrayIndexOf(_toIncludeArray, key) === -1) {
                        delCallback && delCallback([param, key]);
                    }
                }
                // A parameter is completely deleted if its value is empty
                if (delCallback && getCallback && self.isEmptyObject(getCallback(param))) {
                    delCallback(param);
                }
            }
        }

        /**
         * Get a cleaned array
         * @memberof Utils#
         * @param value {Array} Parameter value to process
         * @param toInclude {Array} Parameters to include
         * @function
         * @return {Array}
         * @private
         */
        function _getCleanedArray(value, toInclude) {
            var _newArray = [];
            var _flattenObject;
            var _temporaryObject;
            var _finalObject = {};
            for (var i = 0; i < value.length; i++) {
                _flattenObject = {};
                self.object2Flatten(value[i], null, _flattenObject, null, true);
                // We browse the list of keys contained in the value
                for (var flattenObjectKey1 in _flattenObject) {
                    // An entry is deleted if it is not part of the inclusion list
                    if (_flattenObject.hasOwnProperty(flattenObjectKey1) && self.arrayIndexOf(toInclude, flattenObjectKey1) === -1) {
                        delete _flattenObject[flattenObjectKey1];
                    }
                }
                // A parameter cannot be set if its value is empty
                if (!self.isEmptyObject(_flattenObject)) {
                    _temporaryObject = {};
                    for (var flattenObjectKey2 in _flattenObject) {
                        if (_flattenObject.hasOwnProperty(flattenObjectKey2)) {
                            self.flatten2Object(_temporaryObject, flattenObjectKey2, _flattenObject[flattenObjectKey2]);
                        }
                    }
                    _finalObject = self.getFormattedObject(_temporaryObject);
                    _newArray.push(_finalObject);
                }
            }
            return _newArray;
        }

        /**
         * Test if parameter can be can be saved in the buffer
         * @memberof Utils#
         * @param param {string} Parameter to buffer
         * @param value {string} Parameter value to process
         * @param toInclude {Object} Object containing parameters and properties to be included
         * @function
         * @return {Object}
         * @private
         * @example
         * // param
         * // "stc"
         * //
         * // value
         * // '"{\"key1\":\"val1\",\"key2\":\"val2\"}"'
         * // '"[\"key1\",\"key2\"]"'
         * //
         * // toInclude
         * // {"stc":["key1"]}
         */
        function _testBufferParamObject(param, value, toInclude) {
            if (self.isObject(toInclude)) {
                var _value;
                var _valueArray = [];
                var _isArray = false;
                var _cleanedArray;
                var _newValue;
                for (var bufferProperty in toInclude) {
                    if (toInclude.hasOwnProperty(bufferProperty) && (param === bufferProperty)) {
                        _value = value;
                        if (typeof _value === 'string') {
                            _value = self.jsonParse(_value) || _value;
                        }
                        if (typeof _value === 'object') {
                            if (_value instanceof Array) {
                                _valueArray = _value;
                                _isArray = true
                            } else {
                                _valueArray.push(_value);
                            }
                            _cleanedArray = _getCleanedArray(_valueArray, toInclude[bufferProperty]);
                            // A parameter cannot be set if its value is empty
                            if (_cleanedArray.length === 0) {
                                return {"toSetInBuffer": false};
                            } else {
                                // Otherwise we update its value
                                _newValue = _isArray ? _cleanedArray : _cleanedArray[0];
                                return {"toSetInBuffer": true, "value": self.jsonSerialize(_newValue)};
                            }
                        }
                    }
                }
            }
            return {"toSetInBuffer": false};
        }

        /**
         * Process buffer parameters
         * @memberof Utils#
         * @param param {Object} Parameter to buffer
         * @param value {Object} Parameter value to process
         * @param toInclude {string|Array} Properties to be included
         * @param delCallback {function} Callback to delete in buffer
         * @param setCallback {function} Callback to set in buffer
         * @function
         * @private
         * @example
         * // param :
         * // "stc"
         * //
         * // value :
         * //  {
         * //      "_value": "{\"key1\":\"val1\",\"key2\":\"val2\"}",
         * //      "_options": {
         * //          "hitType": ["page"],
         * //          "encode": true,
         * //          "separator": ",",
         * //          "truncate": true
         * //      }
         * //  }
         * //
         * // toInclude :
         * // "key1"
         * // ["key1", "key2"]
         */
        function _processBufferParamObject(param, value, toInclude, delCallback, setCallback) {
            if (typeof toInclude !== 'undefined') {
                var _toIncludeArray = [];
                var _value = value._value;
                var _valueArray = [];
                var _isArray = false;
                var _cleanedArray;
                var _newValue;
                if (toInclude instanceof Array) {
                    _toIncludeArray = toInclude;
                } else {
                    _toIncludeArray.push(toInclude);
                }
                if (typeof _value === 'string') {
                    _value = self.jsonParse(_value) || _value;
                }
                if (typeof _value === 'object') {
                    if (_value instanceof Array) {
                        _valueArray = _value;
                        _isArray = true
                    } else {
                        _valueArray.push(_value);
                    }
                    _cleanedArray = _getCleanedArray(_valueArray, _toIncludeArray);
                    // A parameter cannot be set if its value is empty
                    if (_cleanedArray.length === 0) {
                        delCallback && delCallback(param);
                    } else {
                        // Otherwise we update its value
                        _newValue = _isArray ? _cleanedArray : _cleanedArray[0];
                        setCallback && setCallback(param, self.jsonSerialize(_newValue), value._options);
                    }
                }
            }
        }

        /**
         * Get keys and values from parameters
         * @memberof Utils#
         * @param privacyParams {Array} Parameters to include
         * @function
         * @return {Object}
         * @private
         */
        function _getParamKeysAndValues(privacyParams) {
            // Listing of the main keys of the privacy table
            var _paramsMainKeys = [];
            // Listing of the values of the objects in the privacy table
            var _paramsMainObjectValues = {};
            for (var i = 0; i < privacyParams.length; i++) {
                if (typeof privacyParams[i] === 'string') {
                    _paramsMainKeys.push(privacyParams[i]);
                } else {
                    for (var key in privacyParams[i]) {
                        if (privacyParams[i].hasOwnProperty(key)) {
                            _paramsMainKeys.push(key);
                            _paramsMainObjectValues[key] = (_paramsMainObjectValues[key] || []).concat(privacyParams[i][key]);
                        }
                    }
                }
            }
            return {
                "keys": _paramsMainKeys,
                "values": _paramsMainObjectValues
            };
        }

        /* -------- Privacy helpers -------- */

        /**
         * Test if parameter can be stored
         * @alias Privacy.testStorageParam
         * @memberof Utils#
         * @param param {string} Parameter to store
         * @param prop {string} Property to store
         * @function
         * @return {Object}
         * @public
         * @example
         * ATInternet.Utils.privacy.testStorageParam(param, prop);
         * // param
         * // "atredir"
         * //
         * // prop
         * // 'at'
         * //
         * // _privacyParameters.storageParams
         * // ["atidvisitor",{"atredir":["at"]}]
         */
        _thisPrivacy.testStorageParam = function (param, prop) {
            if (_privacyParameters.storageParams instanceof Array) {
                var _storageParam, _toSetInStorageObject;
                for (var i = _privacyParameters.storageParams.length - 1; i >= 0; i--) {
                    _storageParam = _privacyParameters.storageParams[i];
                    if (typeof _storageParam === 'string') {
                        if (_storageParam === param || _storageParam === _thisPrivacy.ALL) {
                            return {"toSetInStorage": true};
                        }
                    } else {
                        _toSetInStorageObject = _testStorageParamObject(param, prop, _storageParam);
                        if (_toSetInStorageObject.toSetInStorage) {
                            return {"toSetInStorage": true};
                        }
                    }
                }
                return {"toSetInStorage": false};
            }
            return {"toSetInStorage": true};
        };

        /**
         * Process storage parameters
         * @alias Privacy.processStorageParams
         * @memberof Utils#
         * @param delCallback {function} Callback to delete in storage
         * @param getCallback {function} Callback to get from storage
         * @param getAllCallback {function} Callback to get all from storage
         * @function
         * @return {Object}
         * @public
         * @example
         * ATInternet.Utils.privacy.processStorageParams(delCallback, getCallback, getAllCallback);
         * // _allParams :
         * // {
         * //     "atidvisitor": {
         * //         "name": "atidvisitor",
         * //         "val": {
         * //             "vrn": "-410501--123456-",
         * //              "at": "abcd123",
         * //              "ac": "21"
         * //         },
         * //         "options": {
         * //             "path": "/",
         * //                 "session": 15724800,
         * //                 "end": 15724800
         * //         }
         * //     }
         * // }
         * //
         * // "_privacyParameters.storageParams": [
         * //     "atuserid",
         * //      {
         * //          "atidvisitor": ["an", "at"]
         * //      }
         * // ]
         */
        _thisPrivacy.processStorageParams = function (delCallback, getCallback, getAllCallback) {
            if (getAllCallback) {
                // Listing of parameters stored in cookie or localStorage
                var _allParams = getAllCallback();
                // Listing of parameters and values to be included
                var _params = _getParamKeysAndValues(_privacyParameters.storageParams);
                if (_params.keys[0] !== _thisPrivacy.ALL) {
                    for (var param in _allParams) {
                        if (_allParams.hasOwnProperty(param)) {
                            // If the parameter is not part of the inclusion list then it is deleted.
                            if (self.arrayIndexOf(_params.keys, param) === -1) {
                                delCallback && delCallback(param);
                            } else if (self.isObject(_allParams[param])) {
                                // Otherwise you check its keys
                                _processStorageParamObject(param, _allParams[param].val, _params.values[param], delCallback, getCallback);
                            }
                        }
                    }
                }
            }
        };

        /**
         * Test if parameter can be can be saved in the buffer
         * @alias Privacy.testBufferParam
         * @memberof Utils#
         * @param param {string} Parameter to buffer
         * @param value {string} Parameter value to process
         * @function
         * @return {Object}
         * @public
         * @example
         * ATInternet.Utils.privacy.testBufferParam(param, value);
         * // param
         * // "stc"
         * //
         * // value
         * // '"{\"key1\":\"val1\",\"key2\":\"val2\"}"'
         * // '"[\"key1\",\"key2\"]"'
         */
        _thisPrivacy.testBufferParam = function (param, value) {
            if (_privacyParameters.bufferParams instanceof Array) {
                // _privacyParameters.bufferParams
                // ["custom",{"stc":["key1"]}]
                var _bufferParam, _toSetInBufferObject;
                for (var i = _privacyParameters.bufferParams.length - 1; i >= 0; i--) {
                    _bufferParam = _privacyParameters.bufferParams[i];
                    if (typeof _bufferParam === 'string') {
                        if (_bufferParam === param || _bufferParam === _thisPrivacy.ALL) {
                            return {"toSetInBuffer": true, "value": value};
                        }
                    } else {
                        _toSetInBufferObject = _testBufferParamObject(param, value, _bufferParam);
                        if (_toSetInBufferObject.toSetInBuffer) {
                            return {"toSetInBuffer": true, "value": _toSetInBufferObject.value};
                        }
                    }
                }
                return {"toSetInBuffer": false};
            }
            return {"toSetInBuffer": true, "value": value};
        };

        /**
         * Process buffer parameters
         * @alias Privacy.processBufferParams
         * @memberof Utils#
         * @param delCallback {function} Callback to delete in buffer
         * @param getAllCallback {function} Callback to get all from buffer
         * @param setCallback {function} Callback to set in buffer
         * @function
         * @public
         * @example
         * ATInternet.Utils.privacy.processBufferParams(delCallback, getAllCallback, setCallback);
         * // _allParams :
         * // {
         * //     "stc": {
         * //         "_value": "{\"key1\":\"val1\",\"key2\":\"val2\"}",
         * //         "_options": {
         * //              "hitType": ["page"],
         * //              "encode": true,
         * //              "separator": ",",
         * //              "truncate": true
         * //         }
         * // }
         * //
         * // "_privacyParameters.bufferParams": [
         * //      "an",
         * //      {
         * //          "stc": [
         * //              "key1",
         * //              "key2"
         * //          ]
         * //      }
         * //  ]
         */
        _thisPrivacy.processBufferParams = function (delCallback, getAllCallback, setCallback) {
            if (getAllCallback) {
                // Listing of parameters stored in the buffer
                var _allParams = getAllCallback();
                // Listing of parameters and values to be included
                var _params = _getParamKeysAndValues(_privacyParameters.bufferParams);
                if (_params.keys[0] !== _thisPrivacy.ALL) {
                    for (var param in _allParams) {
                        if (_allParams.hasOwnProperty(param)) {
                            // If the parameter is not part of the inclusion list then it is deleted.
                            if (self.arrayIndexOf(_params.keys, param) === -1) {
                                delCallback && delCallback(param);
                            } else {
                                // Otherwise you check its keys
                                _processBufferParamObject(param, _allParams[param], _params.values[param], delCallback, setCallback);
                            }
                        }
                    }
                }
            }
        };

        /**
         * Set privacy parameters values
         * @alias Privacy.setParameters
         * @memberof Utils#
         * @param privacyParameters {Object} Privacy parameters to set
         * @function
         * @public
         * @example
         * ATInternet.Utils.privacy.setParameters(privacyParameters);
         */
        _thisPrivacy.setParameters = function (privacyParameters) {
            _privacyParameters = privacyParameters;
        };

        /**
         * Get privacy parameters values
         * @alias Privacy.getParameters
         * @memberof Utils#
         * @function
         * @return {Object}
         * @public
         * @example
         * ATInternet.Utils.privacy.getParameters();
         */
        _thisPrivacy.getParameters = function () {
            return _privacyParameters;
        };

        /**
         * Reset privacy parameters values
         * @alias Privacy.resetParameters
         * @memberof Utils#
         * @function
         * @public
         * @example
         * ATInternet.Utils.privacy.resetParameters();
         */
        _thisPrivacy.resetParameters = function () {
            _privacyParameters = {
                storageParams: null,
                bufferParams: null
            };
        };

        // For unit tests on private elements !!!
        /* @if test */
        _thisPrivacy._getPrivacyParameters = function () {
            return _privacyParameters;
        };
        _thisPrivacy._setPrivacyParameters = function (privacyParameters) {
            _privacyParameters = privacyParameters;
        };
        _thisPrivacy._testStorageParamObject = _testStorageParamObject;
        _thisPrivacy._processStorageParamObject = _processStorageParamObject;
        _thisPrivacy._getCleanedArray = _getCleanedArray;
        _thisPrivacy._testBufferParamObject = _testBufferParamObject;
        _thisPrivacy._processBufferParamObject = _processBufferParamObject;
        _thisPrivacy._getParamKeysAndValues = _getParamKeysAndValues;
        /* @endif */
    };

    /**
     * Privacy object
     * @name privacy
     * @memberof Utils#
     * @public
     */
    self.privacy = new Privacy();

    /****************************** OPT-OUT management *************************/

    /**
     * OPT-OUT value
     * @name optedOut
     * @memberof Utils#
     * @private
     */
    self.optedOut = null;

    /**
     * Test value from OPT-OUT stored object
     * @memberof Utils#
     * @function
     * @return {Boolean}
     * @private
     */
    function isOptoutInStorage() {
        var sItem = getStorageData('atuserid');
        if (sItem) {
            var item = self.jsonParse(sItem) || self.jsonParse(self.Base64.decode(sItem));
            if (item !== null) {
                return item.val === 'OPT-OUT';
            }
        }
        return false;
    }

    /**
     * Add OPT-OUT event
     * @name addOptOutEvent
     * @memberof Utils#
     * @param eventID {number} uuid
     * @param func {function} function to associate
     * @function
     * @public
     */
    self.addOptOutEvent = function (eventID, func) {
        self.addEvent('ATOptOutEvent', 'clientsideuserid', eventID, func);
    };

    /**
     * Remove OPT-OUT event
     * @name removeOptOutEvent
     * @memberof Utils#
     * @param func {function} function to remove
     * @function
     * @public
     */
    self.removeOptOutEvent = function (func) {
        self.removeEvent('ATOptOutEvent', func);
    };

    /**
     * Dispatch event for OPT-OUT
     * @name dispatchOptOutEvent
     * @memberof Utils#
     * @param active {boolean} Activate or deactivate OPT-OUT
     * @function
     * @private
     */
    self.dispatchOptOutEvent = function (active) {
        self.optedOut = active;
        self.dispatchEvent('ATOptOutEvent', 'clientsideuserid');
    };

    /**
     * Activate OPT-OUT mode adding OPT-OUT value in hit and storage for idclient parameter
     * @name userOptedOut
     * @memberof Utils#
     * @function
     * @public
     */
    self.userOptedOut = function () {
        self.dispatchOptOutEvent(true);
    };

    /**
     * Deactivate OPT-OUT mode
     * @name userOptedIn
     * @memberof Utils#
     * @function
     * @public
     */
    self.userOptedIn = function () {
        self.dispatchOptOutEvent(false);
    };

    /**
     * Get OPTOUT activation status
     * @name isOptedOut
     * @memberof Utils#
     * @function
     * @public
     */
    self.isOptedOut = function () {
        if (self.optedOut === null) {
            self.optedOut = isOptoutInStorage();
        }
        return !!self.optedOut;
    };

    /****************************** Consent Page management *************************/

    /**
     * Specify whether consent has been obtained preventing or allowing data to be stored in cookies or local storage
     * @name consentReceived
     * @memberof Utils#
     * @param active {boolean} false to prevent data to be stored, true to allow data to be stored
     * @function
     * @public
     */
    self.consentReceived = function (active) {
        self.consent = !!active;
    };

    /**
     * Consent page option value
     * @name consent
     * @memberof Utils#
     * @private
     */
    self.consent = true;

    /****************************** Click event management *************************/

    /**
     * Check if a key allowing a tab opening is pressed during a click
     * @name isTabOpeningAction
     * @memberof Utils#
     * @param event {Object} event object
     * @function
     * @public
     * @return {boolean}
     */
    self.isTabOpeningAction = function (event) {
        var isTabOpening = false;
        if (event && (event.ctrlKey ||
            event.shiftKey ||
            event.metaKey ||
            (event.button && event.button === 1))
        ) {
            isTabOpening = true;
        }
        return isTabOpening;
    };

// Constants for managing click actions according to the DOM Element
    self.CLICKS_REDIRECTION = 'redirection';
    self.CLICKS_FORM = 'form';
    self.CLICKS_MAILTO = 'mailto';

    /*******************************************************************************/

// For unit tests on private elements !!!
    /* @if test */
    self.serialJSON = serialJSON;
    self.parseJSON = parseJSON;
    self.getStorageData = getStorageData;
    self.isOptoutInStorage = isOptoutInStorage;
    /* @endif */
};

/**
 * Module with utility methods.
 * @name ATInternet.Utils
 * @memberof ATInternet
 * @type {Utils}
 * @public
 * @see {@link Utils}
 */
ATInternet.Utils = new Utils();