Source: Tracker/tag.js

/**
 * @class
 * @classdesc Main Tag Object.
 * @name ATInternet.Tracker.Tag
 * @memberof ATInternet.Tracker
 * @type {function}
 * @param config {object} Configuration of the Tag
 * @param context {object} Context of the Tag
 * @param trackerCallback {function} Executed when tracker is ready
 * @public
 */
var Tag = function (config, context, trackerCallback) {

    'use strict';

    context = context || {};

    var self = this;

    /**
     * Version of the tracker.
     * @name version
     * @memberof ATInternet.Tracker.Tag
     * @inner
     * @type {string}
     * @public
     */
    self.version = '#VERSION#';

    /**
     * Contains tagging configuration: context variable (like 'page', 'level2', ...)
     * @type {object}
     * @private
     */
    var _context = ATInternet.Utils.cloneSimpleObject(context);

    //instantiation of all sub objects

    /**
     * Module for triggers management.
     * @name triggers
     * @memberof ATInternet.Tracker.Tag#
     * @type {TriggersManager}
     * @public
     * @see {@link TriggersManager}
     */
    self.triggers = new TriggersManager(self);

    /**
     * Use to trigger event.
     * @name emit
     * @memberof ATInternet.Tracker.Tag#
     * @function
     * @param trigger {string} Trigger to emit
     * @param data {object} Data to transmit to listeners
     * @public
     * @example
     * <pre><code class="javascript">tag.emit('RichMedia:richMedia:remove', {
     *       lvl: 'DEBUG',
     *       msg: 'method ended',
     *       details: {playerId: plyr, media: mediaObject}
     *   });
     * </code></pre>
     * @see {@link TriggersManager#emit}
     */
    self.emit = self.triggers.emit;

    /**
     * Use to emit debug triggers.
     * @memberof ATInternet.Tracker.Tag#
     * @name debug
     * @function
     * @param trigger {string} Trigger that you want to emit
     * @param level {string} DEBUG, ERROR, WARNING
     * @param message {string}
     * @param details {*} Data you want to transmit to listeners
     * @public
     * @example
     * <pre><code class="javascript">tag.debug('RichMedia:richMedia:remove', 'DEBUG', 'method ended', {playerId: plyr, media: mediaObject});
     * </code></pre>
     */
    /* @if debug */
    self.debug = function (trigger, level, message, details) {
        self.emit(trigger, {
            lvl: level,
            msg: message,
            details: details
        });
    };
    /* @endif */

    /**
     * Use to add a trigger listener.
     * @name onTrigger
     * @memberof ATInternet.Tracker.Tag#
     * @function
     * @param trigger {string} Trigger you want to subscribe
     * @param callback {function} Method that you want to be triggered
     * @returns {number} Callback ID
     * @public
     * @example
     * <pre><code class="javascript">// It returns the ID of the callback so you can delete it if needed.
     * var idCallback = tag.onTrigger('myTrigger', callback(trig, data, idCallbackBis){});
     * </code></pre>
     * @see {@link TriggersManager#on}
     */
    self.onTrigger = self.triggers.on;

    /**
     * Internal configuration object.
     * @type {object}
     * @private
     */
    var _conf = ATInternet.Utils.cloneSimpleObject(dfltGlobalCfg) || {};

    //Overloading of the configuration with the one given in parameter
    for (var k in config) {
        if (config.hasOwnProperty(k)) {
            _conf[k] = config[k];
        }
    }

    /**
     * Get configuration.
     * @name getConfig
     * @memberof ATInternet.Tracker.Tag#
     * @function
     * @param key {string} Configuration property name
     * @returns {*} Configuration property value if exist, undefined if not
     * @public
     */
    self.getConfig = function (key) {
        return _conf[key];
    };

    /**
     * Set configuration.
     * @name setConfig
     * @memberof ATInternet.Tracker.Tag#
     * @function
     * @param key {string} Configuration property name
     * @param value {*} Configuration property value
     * @param ifNotExist {boolean} If true, the property will only be set if the configuration property doesn't exist
     * @returns {*}
     * @public
     */
    self.setConfig = function (key, value, ifNotExist) {
        if (_conf[key] === undefined || !ifNotExist) {
            self.emit('Tracker:Config:Set:' + key, {lvl: 'INFO', details: {bef: _conf[key], aft: value}});
            _conf[key] = value;
        }
    };

    /**
     * Set the specific configuration of a plugin.
     * If the configuration already exists, set only the undefined properties.
     * @name configPlugin
     * @memberof ATInternet.Tracker.Tag#
     * @function
     * @param plg {string} Name of the plugin concerned
     * @param cfg {object} Value of the plugin configuration
     * @param cbk {function} Function which be executed if the plugin configuration change
     * @returns {object}
     * @public
     */
    self.configPlugin = function (plg, cfg, cbk) {
        _conf[plg] = _conf[plg] || {};
        for (var key in cfg) {
            if (cfg.hasOwnProperty(key)) {
                if (_conf[plg][key] === undefined) {
                    _conf[plg][key] = cfg[key];
                }
            }
        }
        if (cbk) {
            cbk(_conf[plg]);
            self.onTrigger('Tracker:Config:Set:' + plg, function (trig, data) {
                cbk(data.details.aft);
            });
        }
        return _conf[plg];
    };

    /**
     * Get all context.
     * @name getAllContext
     * @memberof ATInternet.Tracker.Tag#
     * @function
     * @returns {*}
     * @public
     */
    self.getAllContext = function () {
        return _context;
    };

    /**
     * Get a context variable.
     * @name getContext
     * @memberof ATInternet.Tracker.Tag#
     * @function
     * @param key {string} Context variable name
     * @returns {*}
     * @public
     */
    self.getContext = function (key) {
        return _context[key];
    };

    /**
     * Set a context variable.
     * @name setContext
     * @memberof ATInternet.Tracker.Tag#
     * @function
     * @param {string} key context variable name
     * @param {*} value
     * @public
     */
    self.setContext = function (key, value) {
        self.emit('Tracker:Context:Set:' + key, {lvl: 'INFO', details: {bef: _context[key], aft: value}});
        _context[key] = value;
    };

    /**
     * Delete context value or parameter value in context(s).
     * @name delContext
     * @memberof ATInternet.Tracker.Tag#
     * @function
     * @param key1 {string} Context name
     * @param key2 {string} Context parameter name (first level)
     * @public
     * @example
     * <pre><code class="javascript">tag.delContext('myContext'); //=> Delete 'myContext' content
     * tag.delContext('myContext', 'param'); //=> Delete 'param' content in 'myContext'
     * tag.delContext(undefined, 'param');  //=> Delete 'param' content in all contexts
     * </code></pre>
     */
    self.delContext = function (key1, key2) {
        self.emit('Tracker:Context:Deleted:' + key1 + ':' + key2, {lvl: 'INFO', details: {key1: key1, key2: key2}});
        if (key1) {
            if (_context.hasOwnProperty(key1)) {
                if (key2) {
                    if (_context[key1] && _context[key1].hasOwnProperty(key2)) {
                        _context[key1][key2] = undefined;
                    }
                } else {
                    _context[key1] = undefined;
                }
            }
        } else if (key2) {
            var key;
            for (key in _context) {
                if (_context.hasOwnProperty(key)) {
                    if (_context[key] && _context[key].hasOwnProperty(key2)) {
                        _context[key][key2] = undefined;
                    }
                }
            }
        }
    };

    /**
     * Module for plugins management.
     * @name plugins
     * @memberof ATInternet.Tracker.Tag#
     * @type {PluginsManager}
     * @public
     * @see {@link PluginsManager}
     */
    self.plugins = new PluginsManager(self);

    /**
     * Module for buffer management.
     * @name buffer
     * @memberof ATInternet.Tracker.Tag#
     * @type {BufferManager}
     * @public
     * @see {@link BufferManager}
     */
    self.buffer = new BufferManager(self);

    /**
     * Set value for a hit variable (overrides if present).
     * @name setParam
     * @memberof ATInternet.Tracker.Tag#
     * @function
     * @param name {string} Name of the hit variable
     * @param value {string|number|function|Array} value of the hit variable
     * @param options {object} Configuration of the variable, if no hitType defined, it will be "page" by default
     * @public
     * @example
     *  <pre><code class="javascript">tag.setParam('test1', 'val1', {hitType: ['test', 'click'], permanent: true, encode: true});
     *  </code></pre>
     * @see {@link BufferManager}
     */
    self.setParam = self.buffer.set;

    /**
     * Use to get the collection of hit parameters stored (or a value if a parameter name is given).
     * @name getParams
     * @memberof ATInternet.Tracker.Tag#
     * @function
     * @param param {string} Parameter name (optional)
     * @returns {string|object}
     * @public
     * @see {@link BufferManager}
     */
    self.getParams = function (param) {
        return self.buffer.get(param, false);
    };

    /**
     * Get variables from the buffer using the filter given, with possibility of returning options or not.
     * @name getParam
     * @memberof ATInternet.Tracker.Tag#
     * @function
     * @param filterList {Array} List of key/value(s) in an array. (ex : [[key1,value1],[key2,[value2A,value2B]]]) Filter on variable's options
     * @param withOptions {boolean} If true only returns value else returns object with value and options
     * @returns {string|object}
     * @public
     * @example
     * <pre><code class="javascript">// This filter will get variables with hitType 'page' OR 'all', AND with permanent true
     * var filter = [
     *  ['hitType',['page','all']],
     *  ['permanent',true]
     * ]
     *
     * var dataObj = tag.getParam(filter, true); // true to get options
     * dataObj = {
     *  'variableExample' : {
     *      _value:'value',
     *      _options: {
     *          hitType:['page'],
     *          permanent:true
     *      }
     *   }
     * }
     *
     * var dataObj = tag.getParam(filter); // no option in results
     * dataObj = {
     *  'variableExample':'value'
     * }
     * </code></pre>
     * @see {@link BufferManager}
     */
    self.getParam = self.buffer.get;

    /**
     * Use to delete a stored parameter.
     * @name delParam
     * @memberof ATInternet.Tracker.Tag#
     * @function
     * @public
     * @see {@link BufferManager}
     */
    self.delParam = self.buffer.del;

    /**
     * Module for hit builder.
     * @name builder
     * @memberof ATInternet.Tracker.Tag#
     * @type {BuildManager}
     * @public
     * @see {@link BuildManager}
     */
    self.builder = new BuildManager(self);

    /**
     * Send single hit from complete url. An event will be sent thanks to {@link TriggersManager} :
     * <br />- <b>"Tracker:Hit:Sent:Ok"</b> with the hit as data if succeed,
     * <br />- <b>"Tracker:Hit:Sent:Error"</b> with error as data otherwise.
     * @name sendUrl
     * @memberof ATInternet.Tracker.Tag#
     * @function
     * @param hit {string} Url to send
     * @public
     * @see {@link BuildManager}
     */
    self.sendUrl = self.builder.sendUrl;

    /**
     * Module for callback management.
     * @name callbacks
     * @memberof ATInternet.Tracker.Tag#
     * @type {CallbacksManager}
     * @public
     * @see {@link CallbacksManager}
     */
    self.callbacks = new CallbacksManager(self);

    /**
     * Module for properties management.
     * @name properties
     * @memberof ATInternet.Tracker.Tag#
     * @type {PropertiesManager}
     * @public
     * @see {@link PropertiesManager}
     */
    self.properties = new PropertiesManager(self);

    /**
     * Set a property (overrides if present)
     * @name setProp
     * @memberof ATInternet.Tracker.Tag#
     * @function
     * @param key {string} Key name of the property
     * @param value {string|number|Array} value of the property
     * @param permanent {boolean} Permanence of property
     * @public
     */
    self.setProp = self.properties.setProp;

    /**
     * Set multiple properties
     * @name setProps
     * @memberof ATInternet.Tracker.Tag#
     * @function
     * @param props {object} Object to be added
     * @param permanent {boolean} Permanence of object properties
     * @public
     */
    self.setProps = self.properties.setProps;

    /**
     * Delete/remove a property
     * @name delProp
     * @memberof ATInternet.Tracker.Tag#
     * @function
     * @param key {string} Key name of the property
     * @public
     */
    self.delProp = self.properties.delProp;

    /**
     * Delete/remove all properties
     * @name delProps
     * @memberof ATInternet.Tracker.Tag#
     * @function
     * @public
     */
    self.delProps = self.properties.delProps;

    /**
     * Get a property
     * @name getProp
     * @memberof ATInternet.Tracker.Tag#
     * @function
     * @param key {string} Key name of the property
     * @public
     */
    self.getProp = self.properties.getProp;

    /**
     * Get all properties
     * @name getProp
     * @memberof  ATInternet.Tracker.Tag#
     * @function
     * @return {object}
     * @public
     */
    self.getProps = self.properties.getProps;

    /**
     * Send the hit and call the callback if present
     * The hit will integrate parameters stored
     * @name sendHit
     * @memberof ATInternet.Tracker.Tag#
     * @function
     * @param customParams {object} Object which contains some hit parameters that you would like to send specifically (they are given priority over the current buffer)
     * @param filters {Array} List of buffer filters
     * @param callback {function} Callback to execute
     * @param requestMethod {string} Overloading the global method of sending hits (GET|POST)
     * @param elementType {string} Element type (mailto, form, redirection)
     * @public
     * @see {@link BuildManager}
     */
    self.sendHit = function (customParams, filters, callback, requestMethod, elementType) {
        var properties = self.getProps();
        var value;
        for (var key in properties) {
            if (properties.hasOwnProperty(key)) {
                value = properties[key].value;
                if (properties[key].persistent) {
                    self.setParam(key.toLowerCase(), value, {permanent: true, hitType: ['all'], encode: true});
                } else {
                    if (ATInternet.Utils.isObject(customParams)) {
                        customParams[key.toLowerCase()] = {
                            _value: value,
                            _options: {
                                hitType: ['all'],
                                encode: true
                            }
                        };
                    } else {
                        self.setParam(key.toLowerCase(), value, {hitType: ['all'], encode: true});
                    }
                    self.delProp(key, true);
                }
            }
        }
        self.builder.send(customParams, filters, callback, requestMethod, elementType);
    };

    // Reset privacy context parameters
    ATInternet.Utils.privacy.resetParameters();

    // Ajout du timestamp dans le hit pour le différencier d'un autre
    self.setParam('ts', function () {
        return new Date().getTime()
    }, {permanent: true, hitType: ['all']});

    // Ajout de l'idclient dans le cas où le stockage est désactivé
    if (self.getConfig('disableCookie') || self.getConfig('disableStorage')) {
        self.setParam('idclient', ATInternet.Utils.privacy.CONSENTNO, {permanent: true, hitType: ['all']});
    }

    // Ajout du paramètre medium (ex: fia pour Facebook) si déclaré dans le marqueur
    if (self.getConfig('medium')) {
        self.setParam('medium', self.getConfig('medium'), {permanent: true, hitType: ['all']});
    }

    // Ajout du paramètre page_url aux hits et aux events qui contient l'url courante
    if (self.getConfig('urlPropertyAuto') && typeof window !== 'undefined' && typeof window.location !== 'undefined') {
        var currentUrl = (self.getConfig('urlPropertyQueryString')? window.location.href : window.location.protocol + '//' + window.location.host + window.location.pathname).replace(/[<>]/g, "").substring(0, 1600).replace(/&/g, '$');
        var pageContext = self.getContext('page') || {};
        pageContext.url = window.encodeURIComponent(currentUrl);
        // value différente entre legacy et col=2 ? (à cause des "&" qui sont décodés/etc par le récupligne ?) ou on dégage ce caractère partout comme pour le referrer ?
        self.setContext('page', pageContext); // page context is retrieved automatically for events (carefull, all properties are not retrieved !)
        self.setParam('page_url', currentUrl, {permanent: true, hitType: ['page','click','publisher','selfPromotion','onSiteAdsClick','onSiteAdsImpression','InternalSearch','mvtesting','richmedia']});
    }

    /* Init */
    self.plugins.init();
    self.callbacks.init();

    self.emit('Tracker:Ready', {
        lvl: 'INFO',
        msg: 'Tracker initialized',
        details: {
            tracker: self,
            args: {
                config: config,
                context: context,
                callback: trackerCallback
            }
        }
    });
    trackerCallback && trackerCallback(self);

    // Ajout de l'instance dans la collection
    ATInternet.Tracker.instances.push(self);
};

ATInternet.Tracker.Tag = Tag;

/**
 * Reference all instances of Tag.
 * @name instances
 * @memberof ATInternet.Tracker
 * @type {Array}
 * @public
 */
ATInternet.Tracker.instances = [];

/**
 * Reference all plugins loaded.
 * @name pluginProtos
 * @memberof ATInternet.Tracker
 * @type {object}
 * @public
 */
ATInternet.Tracker.pluginProtos = {};

/**
 * This method loads a plugin in all instances of Tag.
 * @name addPlugin
 * @memberof ATInternet.Tracker
 * @function
 * @param name {string} Plugin's name to add
 * @param obj {object} Plugin to add
 * @public
 */
ATInternet.Tracker.addPlugin = function (name, obj) {
    'use strict';
    obj = obj || ATInternet.Tracker.Plugins[name];
    //Ajout de son proto dans la collection (si pas déjà présent).
    if (!ATInternet.Tracker.pluginProtos[name]) {
        ATInternet.Tracker.pluginProtos[name] = obj;
        //création d'une instance du plugin dans toutes les instances du tracker
        for (var i = 0; i < ATInternet.Tracker.instances.length; i++) {
            ATInternet.Tracker.instances[i].plugins.load(name, obj);
        }
    }
};

/**
 * This method deletes a plugin loaded in all instances of Tag.
 * @name delPlugin
 * @memberof ATInternet.Tracker
 * @function
 * @param name {string} plugin's name to delete
 * @public
 */
ATInternet.Tracker.delPlugin = function (name) {
    'use strict';
    //Ajout de son proto dans la collection (si pas déjà présent)
    if (ATInternet.Tracker.pluginProtos[name]) {
        ATInternet.Tracker.pluginProtos[name] = undefined;
        //Suppression de l'instance du plugin dans toutes les instances du tracker
        for (var i = 0; i < ATInternet.Tracker.instances.length; i++) {
            ATInternet.Tracker.instances[i].plugins.unload(name);
        }
    }
};

/**
 * Reference all callbacks loaded.
 * @name callbackProtos
 * @memberof ATInternet.Tracker
 * @type {object}
 * @public
 */
ATInternet.Tracker.callbackProtos = {};