--- a/busui/owa/modules/base/js/owa.tracker.js +++ b/busui/owa/modules/base/js/owa.tracker.js @@ -1,1 +1,1797 @@ - +// +// Open Web Analytics - An Open Source Web Analytics Framework +// +// Copyright 2006 Peter Adams. All rights reserved. +// +// Licensed under GPL v2.0 http://www.gnu.org/copyleft/gpl.html +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// $Id$ +// + +/** + * OWA Generic Event Object + * + * @author Peter Adams <peter@openwebanalytics.com> + * @copyright Copyright © 2006 Peter Adams <peter@openwebanalytics.com> + * @license http://www.gnu.org/copyleft/gpl.html GPL v2.0 + * @category owa + * @package owa + * @version $Revision$ + * @since owa 1.2.1 + */ +OWA.event = function() { + + this.properties = new Object(); + this.set('timestamp', OWA.util.getCurrentUnixTimestamp() ); +} + +OWA.event.prototype = { + + id : '', + + siteId : '', + + properties : {}, + + get : function(name) { + + if ( this.properties.hasOwnProperty(name) ) { + + return this.properties[name]; + } + }, + + set : function(name, value) { + + this.properties[name] = value; + }, + + setEventType : function(event_type) { + + this.set("event_type", event_type); + }, + + getProperties : function() { + + return this.properties; + }, + + merge : function(properties) { + + for(param in properties) { + + if (properties.hasOwnProperty(param)) { + + this.set(param, properties[param]); + } + } + } +} + +OWA.commandQueue = function() { + + OWA.debug('Command Queue object created'); +} + +OWA.commandQueue.prototype = { + asyncCmds: '', + push : function (cmd) { + + //alert(func[0]); + var args = Array.prototype.slice.call(cmd, 1); + //alert(args); + + var obj_name = ''; + var method = ''; + var check = OWA.util.strpos( cmd[0], '.' ); + + if ( ! check ) { + obj_name = 'OWATracker'; + method = cmd[0]; + } else { + var parts = cmd[0].split( '.' ); + obj_name = parts[0]; + method = parts[1]; + } + + OWA.debug('cmd queue object name %s', obj_name); + OWA.debug('cmd queue object method name %s', method); + + // is OWATracker created? + if ( typeof window[obj_name] == "undefined" ) { + OWA.debug('making global object named: %s', obj_name); + window[obj_name] = new OWA.tracker( { globalObjectName: obj_name } ); + } + + window[obj_name][method].apply(window[obj_name], args); + }, + + loadCmds: function(cmds) { + + this.asyncCmds = cmds; + }, + + process: function() { + + for (var i=0; i < this.asyncCmds.length;i++) { + this.push(this.asyncCmds[i]); + } + } +}; + +/** + * Javascript Tracker Object + * + * @author Peter Adams <peter@openwebanalytics.com> + * @copyright Copyright © 2006 Peter Adams <peter@openwebanalytics.com> + * @license http://www.gnu.org/copyleft/gpl.html GPL v2.0 + * @category owa + * @package owa + * @version $Revision$ + * @since owa 1.2.1 + */ +OWA.tracker = function( options ) { + + //this.setDebug(true); + // set start time + this.startTime = this.getTimestamp(); + + // Configuration options + this.options = { + logClicks: true, + logPage: true, + logMovement: false, + encodeProperties: false, + movementInterval: 100, + logDomStreamPercentage: 100, + domstreamLoggingInterval: 3000, + domstreamEventThreshold: 10, + maxPriorCampaigns: 5, + campaignAttributionWindow: 60, + trafficAttributionMode: 'direct', + sessionLength: 1800, + thirdParty: false, + cookie_domain: false, + campaignKeys: [ + { public: 'owa_medium', private: 'md', full: 'medium' }, + { public: 'owa_campaign', private: 'cn', full: 'campaign' }, + { public: 'owa_source', private: 'sr', full: 'source' }, + { public: 'owa_search_terms', private: 'tr', full: 'search_terms' }, + { public: 'owa_ad', private: 'ad', full: 'ad' }, + { public: 'owa_ad_type', private: 'at', full: 'ad_type' } ], + logger_endpoint: '', + api_endpoint: '' + + }; + + // Endpoint URL of log service. needed for backwards compatability with old tags + var endpoint = window.owa_baseUrl || OWA.config.baseUrl ; + if (endpoint) { + this.setEndpoint(endpoint); + } else { + OWA.debug('no global endpoint url found.'); + } + + this.endpoint = OWA.config.baseUrl; + // Active status of tracker + this.active = true; + + if ( options ) { + + for (opt in options) { + + this.options[opt] = options[opt]; + } + } + + // private vars + this.ecommerce_transaction = '', + this.isClickTrackingEnabled = false; + + // check to se if an overlay session is active + this.checkForOverlaySession(); + + // set default page properties + this.page = new OWA.event(); + this.page.set('page_url', document.URL); + this.setPageTitle(document.title); + this.page.set("referer", document.referrer); + this.page.set('timestamp', this.startTime); + + // merge page properties from global owa_params object + if (typeof owa_params != 'undefined') { + // merge page params from the global object if it exists + if (owa_params.length > 0) { + this.page.merge(owa_params); + } + } +} + +OWA.tracker.prototype = { + + id : '', + // site id + siteId : '', + // ??? + init: 0, + // flag to tell if client state has been set + stateInit: false, + // properties that should be added to all events + globalEventProperties: {}, + // state sores that can be shared across sites + sharableStateStores: ['v', 's', 'c'], + // Time When tracker is loaded + startTime: null, + // time when tracker is unloaded + endTime: null, + // campaign state holder + campaignState : [], + // flag for new campaign status + isNewCampaign: false, + // flag for new session status + isNewSessionFlag: false, + // flag for whether or not traffic has been attributed + isTrafficAttributed: false, + cookie_names: ['owa_s', 'owa_v', 'owa_c'], + linkedStateSet: false, + hashCookiesToDomain: true, + /** + * GET params parsed from URL + */ + urlParams: {}, + /** + * DOM stream Event Binding Methods + */ + streamBindings : ['bindMovementEvents', 'bindScrollEvents','bindKeypressEvents', 'bindClickEvents'], + /** + * Page view event + */ + page : '', + /** + * Latest click event + */ + click : '', + /** + * Domstream event + */ + domstream : '', + /** + * Latest Movement Event + */ + movement : '', + /** + * Latest Keystroke Event + */ + keystroke : '', + /** + * Latest Hover Event + */ + hover : '', + + last_event : '', + last_movement : '', + /** + * DOM Stream Event Queue + */ + event_queue : [], + player: '', + overlay: '', + + setDebug : function(bool) { + + OWA.setSetting('debug', bool); + }, + + checkForLinkedState : function() { + + var ls = this.getUrlParam('owa_state'); + + if ( ! ls ) { + ls = this.getAnchorParam('owa_state'); + } + + if ( ls ) { + OWA.debug('Shared OWA state detected...'); + + ls = OWA.util.base64_decode(OWA.util.urldecode(ls)); + //ls = OWA.util.trim(ls, '\u0000'); + //ls = OWA.util.trim(ls, '\u0000'); + OWA.debug('linked state: %s', ls); + + var state = ls.split('.'); + //var state = OWA.util.explode('.', ls); + OWA.debug('linked state: %s', JSON.stringify(state)); + if ( state ) { + + for (var i=0; state.length > i; i++) { + + var pair = state[i].split('='); + OWA.debug('pair: %s', pair); + // add cookie domain hash for current cookie domain + var value = OWA.util.urldecode(pair[1]); + OWA.debug('pair: %s', value); + //OWA.debug('about to decode shared link state value: %s', value); + decodedvalue = OWA.util.decodeCookieValue(value); + //OWA.debug('decoded shared link state value: %s', JSON.stringify(decodedvalue)); + var format = OWA.util.getCookieValueFormat(value); + //OWA.debug('format of decoded shared state value: %s', format); + decodedvalue.cdh = OWA.util.getCookieDomainHash( this.getCookieDomain() ); + + OWA.replaceState( pair[0], decodedvalue, true, format ); + } + } + } + + this.linkedStateSet = true; + }, + + /** + * Shares User State cross domains using GET string + * + * gets cookies and concatenates them together using: + * name1=encoded_value1.name2=encoded_value2 + * then base64 encodes the entire string and appends it + * to an href + * + * @param url string + */ + shareStateByLink : function(url) { + + OWA.debug( 'href of link: '+ url ); + if ( url ) { + + var state = this.createSharedStateValue(); + + //check to see if we can just stick this on the anchor + var anchor = this.getUrlAnchorValue(); + if ( ! anchor ) { + + OWA.debug('shared state: %s', state); + document.location.href = url + '#owa_state.' + state ; + + // if not then we need ot insert it into GET params + } else { + + } + } + }, + + createSharedStateValue : function() { + + var state = ''; + + for (var i=0; this.sharableStateStores.length > i;i++) { + var value = OWA.getState( this.sharableStateStores[i] ); + value = OWA.util.encodeJsonForCookie(value, OWA.getStateStoreFormat(this.sharableStateStores[i])); + + if (value) { + state += OWA.util.sprintf( '%s=%s', this.sharableStateStores[i], OWA.util.urlEncode(value) ); + if ( this.sharableStateStores.length != ( i + 1) ) { + state += '.'; + } + } + } + + // base64 for transport + if ( state ) { + OWA.debug('linked state to send: %s', state); + + state = OWA.util.base64_encode(state); + state = OWA.util.urlEncode(state); + return state; + } + }, + + shareShareByPost : function (form) { + + var state = this.createSharedStateValue(); + form.action += '#owa_state.' + state; + form.submit(); + }, + + getCookieDomain : function() { + + return this.getOption('cookie_domain') || OWA.getSetting('cookie_domain') || document.domain; + + }, + + setCookieDomain : function(domain) { + + var not_passed = false; + + if ( ! domain ) { + domain = document.domain; + not_passed = true; + //this.setOption('cookie_domain_mode', 'auto'); + //OWA.setSetting('cookie_domain_mode', 'auto'); + } + + // remove the leading period + var period = domain.substr(0,1); + if (period === '.') { + domain = domain.substr(1); + } + + var contains_www = false; + var www = domain.substr(0,4); + // check for www and eliminate it if no domain was passed. + if (www === 'www.') { + if ( not_passed ) { + domain = domain.substr(4); + } + + contains_www = true; + } + + var match = false; + if (document.domain === domain) { + match = true; + } + + /* + if (match === true) { + // check to see if the domain is www + if ( contains_www === true ) { + // eliminate any top level domain cookies + OWA.debug('document domain matches cookie domain and includes www. cleaning up cookies.'); + //erase the no www domain cookie (ie. .openwebanalytics.com) + var top_domain = document.domain.substr(4); + OWA.util.eraseMultipleCookies(this.cookie_names, top_domain); + } + + } else { + // erase the document.domain version of all cookies (ie. www.openwebanalytics.com) + OWA.debug('document domain does not match cookie domain. cleaning up by erasing cookies under document.domain .'); + OWA.util.eraseMultipleCookies(this.cookie_names, document.domain); + + + //if ( contains_www === true) { + // OWA.util.eraseMultipleCookies(this.cookie_names, document.domain.substr(4)); + // OWA.util.eraseMultipleCookies(this.cookie_names, document.domain.substr(4)); + //} + + } + */ + + // add the leading period back + domain = '.' + domain; + this.setOption('cookie_domain', domain); + this.setOption('cookie_domain_set', true); + OWA.setSetting('cookie_domain', domain); + OWA.debug('Cookie domain is: %s', domain); + }, + + getCookieDomainHash: function(domain) { + + return OWA.util.crc32(domain); + }, + + setCookieDomainHashing: function(value) { + this.hashCookiesToDomain = value; + OWA.setSetting('hashCookiesToDomain', value); + }, + + checkForOverlaySession: function() { + + // check to see if overlay sesson should be created + var a = this.getAnchorParam('owa_overlay'); + + if ( a ) { + a = OWA.util.base64_decode(OWA.util.urldecode(a)); + //a = OWA.util.trim(a, '\u0000'); + a = OWA.util.urldecode( a ); + OWA.debug('overlay anchor value: ' + a); + //var domain = this.getCookieDomain(); + + // set the overlay cookie + OWA.util.setCookie('owa_overlay',a, '','/', document.domain ); + ////alert(OWA.util.readCookie('owa_overlay') ); + // pause tracker so we dont log anything during an overlay session + this.pause(); + // start overlay session + OWA.startOverlaySession( OWA.util.decodeCookieValue( a ) ); + } + }, + + getUrlAnchorValue : function() { + + var anchor = self.document.location.hash.substring(1); + OWA.debug('anchor value: ' + anchor); + return anchor; + }, + + getAnchorParam : function(name) { + + var anchor = this.getUrlAnchorValue(); + + if ( anchor ) { + OWA.debug('anchor is: %s', anchor); + var pairs = anchor.split(','); + OWA.debug('anchor pairs: %s', JSON.stringify(pairs)); + if ( pairs.length > 0 ) { + + var values = {}; + for( var i=0; pairs.length > i;i++ ) { + + var pieces = pairs[i].split('.'); + OWA.debug('anchor pieces: %s', JSON.stringify(pieces)); + values[pieces[0]] = pieces[1]; + } + + OWA.debug('anchor values: %s', JSON.stringify(values)); + + if ( values.hasOwnProperty( name ) ) { + return values[name]; + } + } + + } + }, + + getUrlParam : function(name) { + + this.urlParams = this.urlParams || OWA.util.parseUrlParams(); + + if ( this.urlParams.hasOwnProperty( name ) ) { + return this.urlParams[name]; + } else { + return false; + } + }, + + dynamicFunc : function (func){ + //alert(func[0]); + var args = Array.prototype.slice.call(func, 1); + //alert(args); + this[func[0]].apply(this, args); + }, + + /** + * Convienence method for seting page title + */ + setPageTitle: function(title) { + + this.page.set("page_title", title); + }, + + /** + * Convienence method for seting page type + */ + setPageType : function(type) { + + this.page.set("page_type", type); + }, + + /** + * Sets the siteId to be appended to all logging events + */ + setSiteId : function(site_id) { + this.siteId = site_id; + }, + + /** + * Convienence method for getting siteId of the logger + */ + getSiteId : function() { + return this.siteId; + }, + + setEndpoint : function (endpoint) { + + endpoint = ('https:' == document.location.protocol ? window.owa_baseSecUrl || endpoint.replace(/http:/, 'https:') : endpoint ); + this.setOption('baseUrl', endpoint); + OWA.config.baseUrl = endpoint; + }, + + setLoggerEndpoint : function(url) { + + this.setOption( 'logger_endpoint', this.forceUrlProtocol( url ) ); + }, + + getLoggerEndpoint : function() { + + var url = this.getOption( 'logger_endpoint') || this.getEndpoint() || OWA.getSetting('baseUrl') ; + + return url + 'log.php'; + }, + + setApiEndpoint : function(url) { + + this.setOption( 'api_endpoint', this.forceUrlProtocol( url ) ); + OWA.setApiEndpoint(url); + }, + + getApiEndpoint : function() { + + return this.getOption('api_endpoint') || this.getEndpoint() + 'api.php'; + }, + + forceUrlProtocol : function (url) { + + url = ('https:' == document.location.protocol ? url.replace(/http:/, 'https:') : url ); + return url; + }, + + + getEndpoint : function() { + return this.getOption('baseUrl'); + }, + + /** + * Logs a page view event + */ + trackPageView : function(url) { + + if (url) { + this.page.set('page_url', url); + } + + this.page.setEventType("base.page_request"); + + return this.trackEvent(this.page); + }, + + trackAction : function(action_group, action_name, action_label, numeric_value) { + + var event = new OWA.event; + + event.setEventType('track.action'); + event.set('site_id', this.getSiteId()); + event.set('page_url', this.page.get('page_url')); + event.set('action_group', action_group); + event.set('action_name', action_name); + event.set('action_label', action_label); + event.set('numeric_value', numeric_value); + this.trackEvent(event); + OWA.debug("Action logged"); + }, + + trackClicks : function(handler) { + // flag to tell handler to log clicks as they happen + this.setOption('logClicksAsTheyHappen', true); + this.bindClickEvents(); + + }, + + bindClickEvents : function() { + + if ( ! this.isClickTrackingEnabled ) { + var that = this; + // Registers the handler for the before navigate event so that the dom stream can be logged + if (window.addEventListener) { + window.addEventListener('click', function (e) {that.clickEventHandler(e);}, false); + } else if(window.attachEvent) { + window.attachEvent('click', function (e) {that.clickEventHandler(e);}); + } + + this.isClickTrackingEnabled = true; + } + + }, + + trackDomStream : function() { + + if (this.active) { + + // check random number against logging percentage + var rand = Math.floor(Math.random() * 100 + 1 ); + + if (rand <= this.getOption('logDomStreamPercentage')) { + + // needed by click handler + this.setOption('trackDomStream', true); + // loop through stream event bindings + var len = this.streamBindings.length; + for ( var i = 0; i < len; i++ ) { + //for (method in this.streamBindings) { + + this.callMethod(this.streamBindings[i]); + } + + this.startDomstreamTimer(); + } else { + OWA.debug("not tracking domstream for this user."); + } + } + }, + + logDomStream : function() { + + this.domstream = this.domstream || new OWA.event; + + if ( this.event_queue.length > this.options.domstreamEventThreshold ) { + + // make an domstream_id if one does not exist. needed for upstream processing + if ( ! this.domstream.get('domstream_guid') ) { + var salt = 'domstream' + this.page.get('page_url') + this.getSiteId(); + this.domstream.set( 'domstream_guid', OWA.util.generateRandomGuid( salt ) ); + } + + this.domstream.setEventType( 'dom.stream' ); + this.domstream.set( 'site_id', this.getSiteId()); + this.domstream.set( 'page_url', this.page.get('page_url') ); + //this.domstream.set( 'timestamp', this.startTime); + this.domstream.set( 'timestamp', OWA.util.getCurrentUnixTimestamp() ); + this.domstream.set( 'duration', this.getElapsedTime()); + this.domstream.set( 'stream_events', JSON.stringify(this.event_queue)); + this.domstream.set( 'stream_length', this.event_queue.length ); + this.trackEvent( this.domstream ); + this.event_queue = []; + + } else { + OWA.debug("Domstream had too few events to log."); + } + }, + + startDomstreamTimer : function() { + + var interval = this.getOption('domstreamLoggingInterval') + var that = this; + var domstreamTimer = setInterval( + function(){ that.logDomStream() }, + interval + ); + }, + + /** + * Deprecated + */ + log : function() { + this.page.setEventType("base.page_request"); + return this.logEvent(this.page); + }, + + /** + * Logs event asyncronously using AJAX GET + */ + logEventAjax : function (event, method) { + if (this.active) { + + if (event instanceof OWA.event) { + var properties = event.getProperties(); + } else { + var properties = event; + } + + method = method || 'GET'; + + if (method === 'GET') { + return this.ajaxGet(properties); + } else { + this.ajaxPost(properties); + return; + } + + } + + + }, + + isObjectType : function(obj, type) { + return !!(obj && type && type.prototype && obj.constructor == type.prototype.constructor); + }, + + + /** + * Gets XMLHttpRequest Object + */ + getAjaxObj : function() { + + if (window.XMLHttpRequest){ + // If IE7, Mozilla, Safari, etc: Use native object + var ajax = new XMLHttpRequest() + } else { + + if (window.ActiveXObject){ + // ...otherwise, use the ActiveX control for IE5.x and IE6 + var ajax = new ActiveXObject("Microsoft.XMLHTTP"); + } + + } + return ajax; + }, + + ajaxGet : function(properties) { + + var url = this._assembleRequestUrl(properties); + var ajax = this.getAjaxObj(); + ajax.open("GET", url, false); + ajax.send(null); + }, + + /** + * AJAX POST Request + */ + ajaxPost : function(properties) { + + var ajax = this.getAjaxObj(); + var params = this.prepareRequestParams(properties); + + ajax.open("POST", this.getLoggerEndpoint(), false); + //Send the proper header information along with the request + ajax.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + ajax.setRequestHeader("Content-length", params.length); + ajax.setRequestHeader("Connection", "close"); + + ajax.onreadystatechange = function() {//Call a function when the state changes. + if(ajax.readyState == 4 && ajax.status == 200) { + //console.log("ajax response: %s", ajax.responseText); + } + } + + ajax.send(params); + + }, + + ajaxJsonp : function (url) { + + var script = document.createElement("script"); + script.setAttribute("src",url); + script.setAttribute("type","text/javascript"); + document.body.appendChild(script); + }, + + prepareRequestParams : function(properties) { + + var get = ''; + + // append site_id to properties + properties.site_id = this.getSiteId(); + //assemble query string + for ( param in properties ) { + // print out the params + var value = ''; + var kvp = ''; + + if ( properties.hasOwnProperty(param) ) { + + if ( OWA.util.is_array( properties[param] ) ) { + + for ( var i = 0, n = properties[param].length; i < n; i++ ) { + + if ( OWA.util.is_object( properties[param][i] ) ) { + for ( o_param in properties[param][i] ) { + kvp = OWA.util.sprintf('owa_%s[%s][%s]=%s&', param, i, o_param, OWA.util.urlEncode( properties[param][i][o_param] ) ); + get += kvp; + } + } else { + // what the heck is it then. assum string + kvp = OWA.util.sprintf('owa_%s[%s]=%s&', param, i, OWA.util.urlEncode( properties[param][i] ) ); + get += kvp; + } + } + // assume it's a string + } else { + kvp = OWA.util.sprintf('owa_%s=%s&', param, OWA.util.urlEncode( properties[param] ) ); + + } + + + //needed? + } else { + + kvp = OWA.util.sprintf('owa_%s=%s&', '', OWA.util.urlEncode( properties[param] ) ); + } + + get += kvp; + } + //OWA.debug('GET string: %s', get); + return get; + }, + + /** + * Sends an OWA event to the server for processing using GET + * inserts 1x1 pixel IMG tag into DOM + */ + trackEvent : function(event, block) { + //OWA.debug('pre global event: %s', JSON.stringify(event)); + + if ( this.getOption('cookie_domain_set') != true ) { + // set default cookie domain + this.setCookieDomain(); + } + + if ( this.linkedStateSet != true ) { + //check for linked state send from another domain + this.checkForLinkedState(); + } + + if ( this.active ) { + if ( ! block ) { + block_flag = false; + } else { + block_flag = true; + } + + // check for third party mode. + if ( this.getOption( 'thirdParty' ) ) { + // tell upstream client to manage state + this.globalEventProperties.thirdParty = true; + // add in campaign related properties for upstream evaluation + this.setCampaignRelatedProperties(event); + } else { + // else we are in first party mode, so manage state on the client. + this.manageState(event); + } + + this.addGlobalPropertiesToEvent( event ); + //OWA.debug('post global event: %s', JSON.stringify(event)); + return this.logEvent( event.getProperties(), block_flag ); + } + }, + + addGlobalPropertiesToEvent : function ( event ) { + OWA.debug( 'Adding global properties to event: %s', JSON.stringify(this.globalEventProperties) ); + for ( prop in this.globalEventProperties ) { + event.set( prop, this.globalEventProperties[prop] ); + } + }, + + + /** + * Logs event by inserting 1x1 pixel IMG tag into DOM + */ + logEvent : function (properties, block) { + + if (this.active) { + + var url = this._assembleRequestUrl(properties); + OWA.debug('url : %s', url); + image = new Image(1, 1); + //expireDateTime = now.getTime() + delay; + image.onLoad = function () { }; + image.src = url; + if (block) { + //OWA.debug(' blocking...'); + } + OWA.debug('Inserted web bug for %s', properties['event_type']); + } + }, + + /** + * Private method for helping assemble request params + */ + _assembleRequestUrl : function(properties) { + + // append site_id to properties + properties.site_id = this.getSiteId(); + var get = this.prepareRequestParams(properties); + + var log_url = this.getLoggerEndpoint(); + + if (log_url.indexOf('?') === -1) { + log_url += '?'; + } else { + log_url += '&'; + } + + // add some radomness for cache busting + return log_url + get; + }, + + getViewportDimensions : function() { + + var viewport = new Object(); + viewport.width = window.innerWidth ? window.innerWidth : document.body.offsetWidth; + viewport.height = window.innerHeight ? window.innerHeight : document.body.offsetHeight; + return viewport; + }, + + /** + * Sets the X coordinate of where in the browser the user clicked + *