Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/matomo-org/matomo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/js
diff options
context:
space:
mode:
authorrobocoder <anthon.pang@gmail.com>2012-08-01 04:39:48 +0400
committerrobocoder <anthon.pang@gmail.com>2012-08-01 04:39:48 +0400
commit8ea28de6e3cae17c99f8aba3d058ba782ec28240 (patch)
tree208f02e65b48f7ee1b63ed6ea8d756ea39fdc24b /js
parentaa3e70c3310905353cda8454080a214d820c4c48 (diff)
convert tabs to spaces
git-svn-id: http://dev.piwik.org/svn/trunk@6620 59fd770c-687e-43c8-a1e3-f5a4ff64c105
Diffstat (limited to 'js')
-rw-r--r--js/piwik.js4722
1 files changed, 2361 insertions, 2361 deletions
diff --git a/js/piwik.js b/js/piwik.js
index 1151a480ca..983069528d 100644
--- a/js/piwik.js
+++ b/js/piwik.js
@@ -42,7 +42,7 @@
// methods in a closure to avoid creating global variables.
if (!this.JSON2) {
- this.JSON2 = {};
+ this.JSON2 = {};
}
(function () {
@@ -58,12 +58,12 @@ if (!this.JSON2) {
if (objectType === '[object Date]') {
return isFinite(value.valueOf()) ?
- value.getUTCFullYear() + '-' +
- f(value.getUTCMonth() + 1) + '-' +
- f(value.getUTCDate()) + 'T' +
- f(value.getUTCHours()) + ':' +
- f(value.getUTCMinutes()) + ':' +
- f(value.getUTCSeconds()) + 'Z' : null;
+ value.getUTCFullYear() + '-' +
+ f(value.getUTCMonth() + 1) + '-' +
+ f(value.getUTCDate()) + 'T' +
+ f(value.getUTCHours()) + ':' +
+ f(value.getUTCMinutes()) + ':' +
+ f(value.getUTCSeconds()) + 'Z' : null;
}
if (objectType === '[object String]' ||
@@ -81,7 +81,7 @@ if (!this.JSON2) {
}
var cx = new RegExp('[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]', 'g'),
- // hack: workaround Snort false positive (sid 8443)
+ // hack: workaround Snort false positive (sid 8443)
pattern = '\\\\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]',
escapable = new RegExp('[' + pattern, 'g'),
gap,
@@ -108,7 +108,7 @@ if (!this.JSON2) {
return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
var c = meta[a];
return typeof c === 'string' ? c :
- '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+ '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
}) + '"' : '"' + string + '"';
}
@@ -354,7 +354,7 @@ if (!this.JSON2) {
// each name/value pair to a reviver function for possible transformation.
return typeof reviver === 'function' ?
- walk({'': j}, '') : j;
+ walk({'': j}, '') : j;
}
// If the text is not JSON parseable, then a SyntaxError is thrown.
@@ -373,2417 +373,2417 @@ if (!this.JSON2) {
/*global ActiveXObject */
/*global _paq:true */
/*members encodeURIComponent, decodeURIComponent, getElementsByTagName,
- shift, unshift,
- addEventListener, attachEvent, removeEventListener, detachEvent, disableCookies,
- cookie, domain, readyState, documentElement, doScroll, title, text,
- location, top, document, referrer, parent, links, href, protocol, GearsFactory,
- event, which, button, srcElement, type, target,
- parentNode, tagName, hostname, className,
- userAgent, cookieEnabled, platform, mimeTypes, enabledPlugin, javaEnabled,
- XMLHttpRequest, ActiveXObject, open, setRequestHeader, onreadystatechange, send, readyState, status,
- getTime, getTimeAlias, setTime, toGMTString, getHours, getMinutes, getSeconds,
- toLowerCase, charAt, indexOf, lastIndexOf, split, slice, toUpperCase,
- onload, src,
- round, random,
- exec,
- res, width, height,
- pdf, qt, realp, wma, dir, fla, java, gears, ag,
- hook, getHook, getVisitorId, getVisitorInfo, setTrackerUrl, setSiteId,
- getAttributionInfo, getAttributionCampaignName, getAttributionCampaignKeyword,
- getAttributionReferrerTimestamp, getAttributionReferrerUrl,
- setCustomData, getCustomData,
- setCustomVariable, getCustomVariable, deleteCustomVariable,
- setDownloadExtensions, addDownloadExtensions,
- setDomains, setIgnoreClasses, setRequestMethod,
- setReferrerUrl, setCustomUrl, setDocumentTitle,
- setDownloadClasses, setLinkClasses,
- setCampaignNameKey, setCampaignKeywordKey,
- discardHashTag,
- setCookieNamePrefix, setCookieDomain, setCookiePath, setVisitorIdCookie,
- setVisitorCookieTimeout, setSessionCookieTimeout, setReferralCookieTimeout,
- setConversionAttributionFirstReferrer,
- doNotTrack, setDoNotTrack, msDoNotTrack,
- addListener, enableLinkTracking, setLinkTrackingTimer,
- setHeartBeatTimer, killFrame, redirectFile, setCountPreRendered,
- trackGoal, trackLink, trackPageView, setEcommerceView, addEcommerceItem, trackEcommerceOrder, trackEcommerceCartUpdate,
- addPlugin, getTracker, getAsyncTracker
+ shift, unshift,
+ addEventListener, attachEvent, removeEventListener, detachEvent, disableCookies,
+ cookie, domain, readyState, documentElement, doScroll, title, text,
+ location, top, document, referrer, parent, links, href, protocol, GearsFactory,
+ event, which, button, srcElement, type, target,
+ parentNode, tagName, hostname, className,
+ userAgent, cookieEnabled, platform, mimeTypes, enabledPlugin, javaEnabled,
+ XMLHttpRequest, ActiveXObject, open, setRequestHeader, onreadystatechange, send, readyState, status,
+ getTime, getTimeAlias, setTime, toGMTString, getHours, getMinutes, getSeconds,
+ toLowerCase, charAt, indexOf, lastIndexOf, split, slice, toUpperCase,
+ onload, src,
+ round, random,
+ exec,
+ res, width, height,
+ pdf, qt, realp, wma, dir, fla, java, gears, ag,
+ hook, getHook, getVisitorId, getVisitorInfo, setTrackerUrl, setSiteId,
+ getAttributionInfo, getAttributionCampaignName, getAttributionCampaignKeyword,
+ getAttributionReferrerTimestamp, getAttributionReferrerUrl,
+ setCustomData, getCustomData,
+ setCustomVariable, getCustomVariable, deleteCustomVariable,
+ setDownloadExtensions, addDownloadExtensions,
+ setDomains, setIgnoreClasses, setRequestMethod,
+ setReferrerUrl, setCustomUrl, setDocumentTitle,
+ setDownloadClasses, setLinkClasses,
+ setCampaignNameKey, setCampaignKeywordKey,
+ discardHashTag,
+ setCookieNamePrefix, setCookieDomain, setCookiePath, setVisitorIdCookie,
+ setVisitorCookieTimeout, setSessionCookieTimeout, setReferralCookieTimeout,
+ setConversionAttributionFirstReferrer,
+ doNotTrack, setDoNotTrack, msDoNotTrack,
+ addListener, enableLinkTracking, setLinkTrackingTimer,
+ setHeartBeatTimer, killFrame, redirectFile, setCountPreRendered,
+ trackGoal, trackLink, trackPageView, setEcommerceView, addEcommerceItem, trackEcommerceOrder, trackEcommerceCartUpdate,
+ addPlugin, getTracker, getAsyncTracker
*/
var
- // asynchronous tracker (or proxy)
- _paq = _paq || [],
-
- // Piwik singleton and namespace
- Piwik = Piwik || (function () {
- "use strict";
-
- /************************************************************
- * Private data
- ************************************************************/
-
- var expireDateTime,
-
- /* plugins */
- plugins = {},
-
- /* alias frequently used globals for added minification */
- documentAlias = document,
- navigatorAlias = navigator,
- screenAlias = screen,
- windowAlias = window,
-
- /* DOM Ready */
- hasLoaded = false,
- registeredOnLoadHandlers = [],
-
- /* encode */
- encodeWrapper = windowAlias.encodeURIComponent,
-
- /* decode */
- decodeWrapper = windowAlias.decodeURIComponent,
-
- /* urldecode */
- urldecode = unescape,
-
- /* asynchronous tracker */
- asyncTracker,
-
- /* iterator */
- i;
-
- /************************************************************
- * Private methods
- ************************************************************/
-
- /*
- * Is property defined?
- */
- function isDefined(property) {
- // workaround https://github.com/douglascrockford/JSLint/commit/24f63ada2f9d7ad65afc90e6d949f631935c2480
- return 'undefined' !== typeof property;
- }
-
- /*
- * Is property a function?
- */
- function isFunction(property) {
- return typeof property === 'function';
- }
-
- /*
- * Is property an object?
- *
- * @return bool Returns true if property is null, an Object, or subclass of Object (i.e., an instanceof String, Date, etc.)
- */
- function isObject(property) {
- return typeof property === 'object';
- }
-
- /*
- * Is property a string?
- */
- function isString(property) {
- return typeof property === 'string' || property instanceof String;
- }
-
- /*
- * apply wrapper
- *
- * @param array parameterArray An array comprising either:
- * [ 'methodName', optional_parameters ]
- * or:
- * [ functionObject, optional_parameters ]
- */
- function apply() {
- var i, f, parameterArray;
-
- for (i = 0; i < arguments.length; i += 1) {
- parameterArray = arguments[i];
- f = parameterArray.shift();
-
- if (isString(f)) {
- asyncTracker[f].apply(asyncTracker, parameterArray);
- } else {
- f.apply(asyncTracker, parameterArray);
- }
- }
- }
-
- /*
- * Cross-browser helper function to add event handler
- */
- function addEventListener(element, eventType, eventHandler, useCapture) {
- if (element.addEventListener) {
- element.addEventListener(eventType, eventHandler, useCapture);
- return true;
- }
- if (element.attachEvent) {
- return element.attachEvent('on' + eventType, eventHandler);
- }
- element['on' + eventType] = eventHandler;
- }
-
- /*
- * Call plugin hook methods
- */
- function executePluginMethod(methodName, callback) {
- var result = '',
- i,
- pluginMethod;
-
- for (i in plugins) {
- if (Object.prototype.hasOwnProperty.call(plugins, i)) {
- pluginMethod = plugins[i][methodName];
- if (isFunction(pluginMethod)) {
- result += pluginMethod(callback);
- }
- }
- }
-
- return result;
- }
-
- /*
- * Handle beforeunload event
- *
- * Subject to Safari's "Runaway JavaScript Timer" and
- * Chrome V8 extension that terminates JS that exhibits
- * "slow unload", i.e., calling getTime() > 1000 times
- */
- function beforeUnloadHandler() {
- var now;
-
- executePluginMethod('unload');
-
- /*
- * Delay/pause (blocks UI)
- */
- if (expireDateTime) {
- // the things we do for backwards compatibility...
- // in ECMA-262 5th ed., we could simply use:
- // while (Date.now() < expireDateTime) { }
- do {
- now = new Date();
- } while (now.getTimeAlias() < expireDateTime);
- }
- }
-
- /*
- * Handler for onload event
- */
- function loadHandler() {
- var i;
-
- if (!hasLoaded) {
- hasLoaded = true;
- executePluginMethod('load');
- for (i = 0; i < registeredOnLoadHandlers.length; i++) {
- registeredOnLoadHandlers[i]();
- }
- }
- return true;
- }
-
- /*
- * Add onload or DOM ready handler
- */
- function addReadyListener() {
- var _timer;
-
- if (documentAlias.addEventListener) {
- addEventListener(documentAlias, 'DOMContentLoaded', function ready() {
- documentAlias.removeEventListener('DOMContentLoaded', ready, false);
- loadHandler();
- });
- } else if (documentAlias.attachEvent) {
- documentAlias.attachEvent('onreadystatechange', function ready() {
- if (documentAlias.readyState === 'complete') {
- documentAlias.detachEvent('onreadystatechange', ready);
- loadHandler();
- }
- });
-
- if (documentAlias.documentElement.doScroll && windowAlias === windowAlias.top) {
- (function ready() {
- if (!hasLoaded) {
- try {
- documentAlias.documentElement.doScroll('left');
- } catch (error) {
- setTimeout(ready, 0);
- return;
- }
- loadHandler();
- }
- }());
- }
- }
-
- // sniff for older WebKit versions
- if ((new RegExp('WebKit')).test(navigatorAlias.userAgent)) {
- _timer = setInterval(function () {
- if (hasLoaded || /loaded|complete/.test(documentAlias.readyState)) {
- clearInterval(_timer);
- loadHandler();
- }
- }, 10);
- }
-
- // fallback
- addEventListener(windowAlias, 'load', loadHandler, false);
- }
-
- /*
- * Get page referrer
- */
- function getReferrer() {
- var referrer = '';
-
- try {
- referrer = windowAlias.top.document.referrer;
- } catch (e) {
- if (windowAlias.parent) {
- try {
- referrer = windowAlias.parent.document.referrer;
- } catch (e2) {
- referrer = '';
- }
- }
- }
- if (referrer === '') {
- referrer = documentAlias.referrer;
- }
-
- return referrer;
- }
-
- /*
- * Extract scheme/protocol from URL
- */
- function getProtocolScheme(url) {
- var e = new RegExp('^([a-z]+):'),
- matches = e.exec(url);
-
- return matches ? matches[1] : null;
- }
-
- /*
- * Extract hostname from URL
- */
- function getHostName(url) {
- // scheme : // [username [: password] @] hostame [: port] [/ [path] [? query] [# fragment]]
- var e = new RegExp('^(?:(?:https?|ftp):)/*(?:[^@]+@)?([^:/#]+)'),
- matches = e.exec(url);
-
- return matches ? matches[1] : url;
- }
-
- /*
- * Extract parameter from URL
- */
- function getParameter(url, name) {
- // scheme : // [username [: password] @] hostame [: port] [/ [path] [? query] [# fragment]]
- var e = new RegExp('^(?:https?|ftp)(?::/*(?:[^?]+)[?])([^#]+)'),
- matches = e.exec(url),
- f = new RegExp('(?:^|&)' + name + '=([^&]*)'),
- result = matches ? f.exec(matches[1]) : 0;
-
- return result ? decodeWrapper(result[1]) : '';
- }
-
- /*
- * UTF-8 encoding
- */
- function utf8_encode(argString) {
- return urldecode(encodeWrapper(argString));
- }
-
- /************************************************************
- * sha1
- * - based on sha1 from http://phpjs.org/functions/sha1:512 (MIT / GPL v2)
- ************************************************************/
- function sha1(str) {
- // + original by: Webtoolkit.info (http://www.webtoolkit.info/)
- // + namespaced by: Michael White (http://getsprink.com)
- // + input by: Brett Zamir (http://brett-zamir.me)
- // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
- // + jslinted by: Anthon Pang (http://piwik.org)
-
- var
- rotate_left = function (n, s) {
- return (n << s) | (n >>> (32 - s));
- },
-
- cvt_hex = function (val) {
- var str = '',
- i,
- v;
-
- for (i = 7; i >= 0; i--) {
- v = (val >>> (i * 4)) & 0x0f;
- str += v.toString(16);
- }
- return str;
- },
-
- blockstart,
- i,
- j,
- W = [],
- H0 = 0x67452301,
- H1 = 0xEFCDAB89,
- H2 = 0x98BADCFE,
- H3 = 0x10325476,
- H4 = 0xC3D2E1F0,
- A,
- B,
- C,
- D,
- E,
- temp,
- str_len,
- word_array = [];
-
- str = utf8_encode(str);
- str_len = str.length;
-
- for (i = 0; i < str_len - 3; i += 4) {
- j = str.charCodeAt(i) << 24 | str.charCodeAt(i + 1) << 16 |
- str.charCodeAt(i + 2) << 8 | str.charCodeAt(i + 3);
- word_array.push(j);
- }
-
- switch (str_len & 3) {
- case 0:
- i = 0x080000000;
- break;
- case 1:
- i = str.charCodeAt(str_len - 1) << 24 | 0x0800000;
- break;
- case 2:
- i = str.charCodeAt(str_len - 2) << 24 | str.charCodeAt(str_len - 1) << 16 | 0x08000;
- break;
- case 3:
- i = str.charCodeAt(str_len - 3) << 24 | str.charCodeAt(str_len - 2) << 16 | str.charCodeAt(str_len - 1) << 8 | 0x80;
- break;
- }
-
- word_array.push(i);
-
- while ((word_array.length & 15) !== 14) {
- word_array.push(0);
- }
-
- word_array.push(str_len >>> 29);
- word_array.push((str_len << 3) & 0x0ffffffff);
-
- for (blockstart = 0; blockstart < word_array.length; blockstart += 16) {
- for (i = 0; i < 16; i++) {
- W[i] = word_array[blockstart + i];
- }
-
- for (i = 16; i <= 79; i++) {
- W[i] = rotate_left(W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16], 1);
- }
-
- A = H0;
- B = H1;
- C = H2;
- D = H3;
- E = H4;
-
- for (i = 0; i <= 19; i++) {
- temp = (rotate_left(A, 5) + ((B & C) | (~B & D)) + E + W[i] + 0x5A827999) & 0x0ffffffff;
- E = D;
- D = C;
- C = rotate_left(B, 30);
- B = A;
- A = temp;
- }
-
- for (i = 20; i <= 39; i++) {
- temp = (rotate_left(A, 5) + (B ^ C ^ D) + E + W[i] + 0x6ED9EBA1) & 0x0ffffffff;
- E = D;
- D = C;
- C = rotate_left(B, 30);
- B = A;
- A = temp;
- }
-
- for (i = 40; i <= 59; i++) {
- temp = (rotate_left(A, 5) + ((B & C) | (B & D) | (C & D)) + E + W[i] + 0x8F1BBCDC) & 0x0ffffffff;
- E = D;
- D = C;
- C = rotate_left(B, 30);
- B = A;
- A = temp;
- }
-
- for (i = 60; i <= 79; i++) {
- temp = (rotate_left(A, 5) + (B ^ C ^ D) + E + W[i] + 0xCA62C1D6) & 0x0ffffffff;
- E = D;
- D = C;
- C = rotate_left(B, 30);
- B = A;
- A = temp;
- }
-
- H0 = (H0 + A) & 0x0ffffffff;
- H1 = (H1 + B) & 0x0ffffffff;
- H2 = (H2 + C) & 0x0ffffffff;
- H3 = (H3 + D) & 0x0ffffffff;
- H4 = (H4 + E) & 0x0ffffffff;
- }
-
- temp = cvt_hex(H0) + cvt_hex(H1) + cvt_hex(H2) + cvt_hex(H3) + cvt_hex(H4);
- return temp.toLowerCase();
- }
- /************************************************************
- * end sha1
- ************************************************************/
-
- /*
- * Fix-up URL when page rendered from search engine cache or translated page
- */
- function urlFixup(hostName, href, referrer) {
- if (hostName === 'translate.googleusercontent.com') { // Google
- if (referrer === '') {
- referrer = href;
- }
- href = getParameter(href, 'u');
- hostName = getHostName(href);
- } else if (hostName === 'cc.bingj.com' || // Bing
- hostName === 'webcache.googleusercontent.com' || // Google
- hostName.slice(0, 5) === '74.6.') { // Yahoo (via Inktomi 74.6.0.0/16)
- href = documentAlias.links[0].href;
- hostName = getHostName(href);
- }
- return [hostName, href, referrer];
- }
-
- /*
- * Fix-up domain
- */
- function domainFixup(domain) {
- var dl = domain.length;
-
- // remove trailing '.'
- if (domain.charAt(--dl) === '.') {
- domain = domain.slice(0, dl);
- }
- // remove leading '*'
- if (domain.slice(0, 2) === '*.') {
- domain = domain.slice(1);
- }
- return domain;
- }
-
- /*
- * Title fixup
- */
- function titleFixup(title) {
- if (!isString(title)) {
- title = title.text || '';
-
- var tmp = documentAlias.getElementsByTagName('title');
- if (tmp && isDefined(tmp[0])) {
- title = tmp[0].text;
- }
- }
- return title;
- }
-
- /*
- * Piwik Tracker class
- *
- * trackerUrl and trackerSiteId are optional arguments to the constructor
- *
- * See: Tracker.setTrackerUrl() and Tracker.setSiteId()
- */
- function Tracker(trackerUrl, siteId) {
-
- /************************************************************
- * Private members
- ************************************************************/
-
- var
+ // asynchronous tracker (or proxy)
+ _paq = _paq || [],
+
+ // Piwik singleton and namespace
+ Piwik = Piwik || (function () {
+ "use strict";
+
+ /************************************************************
+ * Private data
+ ************************************************************/
+
+ var expireDateTime,
+
+ /* plugins */
+ plugins = {},
+
+ /* alias frequently used globals for added minification */
+ documentAlias = document,
+ navigatorAlias = navigator,
+ screenAlias = screen,
+ windowAlias = window,
+
+ /* DOM Ready */
+ hasLoaded = false,
+ registeredOnLoadHandlers = [],
+
+ /* encode */
+ encodeWrapper = windowAlias.encodeURIComponent,
+
+ /* decode */
+ decodeWrapper = windowAlias.decodeURIComponent,
+
+ /* urldecode */
+ urldecode = unescape,
+
+ /* asynchronous tracker */
+ asyncTracker,
+
+ /* iterator */
+ i;
+
+ /************************************************************
+ * Private methods
+ ************************************************************/
+
+ /*
+ * Is property defined?
+ */
+ function isDefined(property) {
+ // workaround https://github.com/douglascrockford/JSLint/commit/24f63ada2f9d7ad65afc90e6d949f631935c2480
+ return 'undefined' !== typeof property;
+ }
+
+ /*
+ * Is property a function?
+ */
+ function isFunction(property) {
+ return typeof property === 'function';
+ }
+
+ /*
+ * Is property an object?
+ *
+ * @return bool Returns true if property is null, an Object, or subclass of Object (i.e., an instanceof String, Date, etc.)
+ */
+ function isObject(property) {
+ return typeof property === 'object';
+ }
+
+ /*
+ * Is property a string?
+ */
+ function isString(property) {
+ return typeof property === 'string' || property instanceof String;
+ }
+
+ /*
+ * apply wrapper
+ *
+ * @param array parameterArray An array comprising either:
+ * [ 'methodName', optional_parameters ]
+ * or:
+ * [ functionObject, optional_parameters ]
+ */
+ function apply() {
+ var i, f, parameterArray;
+
+ for (i = 0; i < arguments.length; i += 1) {
+ parameterArray = arguments[i];
+ f = parameterArray.shift();
+
+ if (isString(f)) {
+ asyncTracker[f].apply(asyncTracker, parameterArray);
+ } else {
+ f.apply(asyncTracker, parameterArray);
+ }
+ }
+ }
+
+ /*
+ * Cross-browser helper function to add event handler
+ */
+ function addEventListener(element, eventType, eventHandler, useCapture) {
+ if (element.addEventListener) {
+ element.addEventListener(eventType, eventHandler, useCapture);
+ return true;
+ }
+ if (element.attachEvent) {
+ return element.attachEvent('on' + eventType, eventHandler);
+ }
+ element['on' + eventType] = eventHandler;
+ }
+
+ /*
+ * Call plugin hook methods
+ */
+ function executePluginMethod(methodName, callback) {
+ var result = '',
+ i,
+ pluginMethod;
+
+ for (i in plugins) {
+ if (Object.prototype.hasOwnProperty.call(plugins, i)) {
+ pluginMethod = plugins[i][methodName];
+ if (isFunction(pluginMethod)) {
+ result += pluginMethod(callback);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ /*
+ * Handle beforeunload event
+ *
+ * Subject to Safari's "Runaway JavaScript Timer" and
+ * Chrome V8 extension that terminates JS that exhibits
+ * "slow unload", i.e., calling getTime() > 1000 times
+ */
+ function beforeUnloadHandler() {
+ var now;
+
+ executePluginMethod('unload');
+
+ /*
+ * Delay/pause (blocks UI)
+ */
+ if (expireDateTime) {
+ // the things we do for backwards compatibility...
+ // in ECMA-262 5th ed., we could simply use:
+ // while (Date.now() < expireDateTime) { }
+ do {
+ now = new Date();
+ } while (now.getTimeAlias() < expireDateTime);
+ }
+ }
+
+ /*
+ * Handler for onload event
+ */
+ function loadHandler() {
+ var i;
+
+ if (!hasLoaded) {
+ hasLoaded = true;
+ executePluginMethod('load');
+ for (i = 0; i < registeredOnLoadHandlers.length; i++) {
+ registeredOnLoadHandlers[i]();
+ }
+ }
+ return true;
+ }
+
+ /*
+ * Add onload or DOM ready handler
+ */
+ function addReadyListener() {
+ var _timer;
+
+ if (documentAlias.addEventListener) {
+ addEventListener(documentAlias, 'DOMContentLoaded', function ready() {
+ documentAlias.removeEventListener('DOMContentLoaded', ready, false);
+ loadHandler();
+ });
+ } else if (documentAlias.attachEvent) {
+ documentAlias.attachEvent('onreadystatechange', function ready() {
+ if (documentAlias.readyState === 'complete') {
+ documentAlias.detachEvent('onreadystatechange', ready);
+ loadHandler();
+ }
+ });
+
+ if (documentAlias.documentElement.doScroll && windowAlias === windowAlias.top) {
+ (function ready() {
+ if (!hasLoaded) {
+ try {
+ documentAlias.documentElement.doScroll('left');
+ } catch (error) {
+ setTimeout(ready, 0);
+ return;
+ }
+ loadHandler();
+ }
+ }());
+ }
+ }
+
+ // sniff for older WebKit versions
+ if ((new RegExp('WebKit')).test(navigatorAlias.userAgent)) {
+ _timer = setInterval(function () {
+ if (hasLoaded || /loaded|complete/.test(documentAlias.readyState)) {
+ clearInterval(_timer);
+ loadHandler();
+ }
+ }, 10);
+ }
+
+ // fallback
+ addEventListener(windowAlias, 'load', loadHandler, false);
+ }
+
+ /*
+ * Get page referrer
+ */
+ function getReferrer() {
+ var referrer = '';
+
+ try {
+ referrer = windowAlias.top.document.referrer;
+ } catch (e) {
+ if (windowAlias.parent) {
+ try {
+ referrer = windowAlias.parent.document.referrer;
+ } catch (e2) {
+ referrer = '';
+ }
+ }
+ }
+ if (referrer === '') {
+ referrer = documentAlias.referrer;
+ }
+
+ return referrer;
+ }
+
+ /*
+ * Extract scheme/protocol from URL
+ */
+ function getProtocolScheme(url) {
+ var e = new RegExp('^([a-z]+):'),
+ matches = e.exec(url);
+
+ return matches ? matches[1] : null;
+ }
+
+ /*
+ * Extract hostname from URL
+ */
+ function getHostName(url) {
+ // scheme : // [username [: password] @] hostame [: port] [/ [path] [? query] [# fragment]]
+ var e = new RegExp('^(?:(?:https?|ftp):)/*(?:[^@]+@)?([^:/#]+)'),
+ matches = e.exec(url);
+
+ return matches ? matches[1] : url;
+ }
+
+ /*
+ * Extract parameter from URL
+ */
+ function getParameter(url, name) {
+ // scheme : // [username [: password] @] hostame [: port] [/ [path] [? query] [# fragment]]
+ var e = new RegExp('^(?:https?|ftp)(?::/*(?:[^?]+)[?])([^#]+)'),
+ matches = e.exec(url),
+ f = new RegExp('(?:^|&)' + name + '=([^&]*)'),
+ result = matches ? f.exec(matches[1]) : 0;
+
+ return result ? decodeWrapper(result[1]) : '';
+ }
+
+ /*
+ * UTF-8 encoding
+ */
+ function utf8_encode(argString) {
+ return urldecode(encodeWrapper(argString));
+ }
+
+ /************************************************************
+ * sha1
+ * - based on sha1 from http://phpjs.org/functions/sha1:512 (MIT / GPL v2)
+ ************************************************************/
+ function sha1(str) {
+ // + original by: Webtoolkit.info (http://www.webtoolkit.info/)
+ // + namespaced by: Michael White (http://getsprink.com)
+ // + input by: Brett Zamir (http://brett-zamir.me)
+ // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
+ // + jslinted by: Anthon Pang (http://piwik.org)
+
+ var
+ rotate_left = function (n, s) {
+ return (n << s) | (n >>> (32 - s));
+ },
+
+ cvt_hex = function (val) {
+ var str = '',
+ i,
+ v;
+
+ for (i = 7; i >= 0; i--) {
+ v = (val >>> (i * 4)) & 0x0f;
+ str += v.toString(16);
+ }
+ return str;
+ },
+
+ blockstart,
+ i,
+ j,
+ W = [],
+ H0 = 0x67452301,
+ H1 = 0xEFCDAB89,
+ H2 = 0x98BADCFE,
+ H3 = 0x10325476,
+ H4 = 0xC3D2E1F0,
+ A,
+ B,
+ C,
+ D,
+ E,
+ temp,
+ str_len,
+ word_array = [];
+
+ str = utf8_encode(str);
+ str_len = str.length;
+
+ for (i = 0; i < str_len - 3; i += 4) {
+ j = str.charCodeAt(i) << 24 | str.charCodeAt(i + 1) << 16 |
+ str.charCodeAt(i + 2) << 8 | str.charCodeAt(i + 3);
+ word_array.push(j);
+ }
+
+ switch (str_len & 3) {
+ case 0:
+ i = 0x080000000;
+ break;
+ case 1:
+ i = str.charCodeAt(str_len - 1) << 24 | 0x0800000;
+ break;
+ case 2:
+ i = str.charCodeAt(str_len - 2) << 24 | str.charCodeAt(str_len - 1) << 16 | 0x08000;
+ break;
+ case 3:
+ i = str.charCodeAt(str_len - 3) << 24 | str.charCodeAt(str_len - 2) << 16 | str.charCodeAt(str_len - 1) << 8 | 0x80;
+ break;
+ }
+
+ word_array.push(i);
+
+ while ((word_array.length & 15) !== 14) {
+ word_array.push(0);
+ }
+
+ word_array.push(str_len >>> 29);
+ word_array.push((str_len << 3) & 0x0ffffffff);
+
+ for (blockstart = 0; blockstart < word_array.length; blockstart += 16) {
+ for (i = 0; i < 16; i++) {
+ W[i] = word_array[blockstart + i];
+ }
+
+ for (i = 16; i <= 79; i++) {
+ W[i] = rotate_left(W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16], 1);
+ }
+
+ A = H0;
+ B = H1;
+ C = H2;
+ D = H3;
+ E = H4;
+
+ for (i = 0; i <= 19; i++) {
+ temp = (rotate_left(A, 5) + ((B & C) | (~B & D)) + E + W[i] + 0x5A827999) & 0x0ffffffff;
+ E = D;
+ D = C;
+ C = rotate_left(B, 30);
+ B = A;
+ A = temp;
+ }
+
+ for (i = 20; i <= 39; i++) {
+ temp = (rotate_left(A, 5) + (B ^ C ^ D) + E + W[i] + 0x6ED9EBA1) & 0x0ffffffff;
+ E = D;
+ D = C;
+ C = rotate_left(B, 30);
+ B = A;
+ A = temp;
+ }
+
+ for (i = 40; i <= 59; i++) {
+ temp = (rotate_left(A, 5) + ((B & C) | (B & D) | (C & D)) + E + W[i] + 0x8F1BBCDC) & 0x0ffffffff;
+ E = D;
+ D = C;
+ C = rotate_left(B, 30);
+ B = A;
+ A = temp;
+ }
+
+ for (i = 60; i <= 79; i++) {
+ temp = (rotate_left(A, 5) + (B ^ C ^ D) + E + W[i] + 0xCA62C1D6) & 0x0ffffffff;
+ E = D;
+ D = C;
+ C = rotate_left(B, 30);
+ B = A;
+ A = temp;
+ }
+
+ H0 = (H0 + A) & 0x0ffffffff;
+ H1 = (H1 + B) & 0x0ffffffff;
+ H2 = (H2 + C) & 0x0ffffffff;
+ H3 = (H3 + D) & 0x0ffffffff;
+ H4 = (H4 + E) & 0x0ffffffff;
+ }
+
+ temp = cvt_hex(H0) + cvt_hex(H1) + cvt_hex(H2) + cvt_hex(H3) + cvt_hex(H4);
+ return temp.toLowerCase();
+ }
+ /************************************************************
+ * end sha1
+ ************************************************************/
+
+ /*
+ * Fix-up URL when page rendered from search engine cache or translated page
+ */
+ function urlFixup(hostName, href, referrer) {
+ if (hostName === 'translate.googleusercontent.com') { // Google
+ if (referrer === '') {
+ referrer = href;
+ }
+ href = getParameter(href, 'u');
+ hostName = getHostName(href);
+ } else if (hostName === 'cc.bingj.com' || // Bing
+ hostName === 'webcache.googleusercontent.com' || // Google
+ hostName.slice(0, 5) === '74.6.') { // Yahoo (via Inktomi 74.6.0.0/16)
+ href = documentAlias.links[0].href;
+ hostName = getHostName(href);
+ }
+ return [hostName, href, referrer];
+ }
+
+ /*
+ * Fix-up domain
+ */
+ function domainFixup(domain) {
+ var dl = domain.length;
+
+ // remove trailing '.'
+ if (domain.charAt(--dl) === '.') {
+ domain = domain.slice(0, dl);
+ }
+ // remove leading '*'
+ if (domain.slice(0, 2) === '*.') {
+ domain = domain.slice(1);
+ }
+ return domain;
+ }
+
+ /*
+ * Title fixup
+ */
+ function titleFixup(title) {
+ if (!isString(title)) {
+ title = title.text || '';
+
+ var tmp = documentAlias.getElementsByTagName('title');
+ if (tmp && isDefined(tmp[0])) {
+ title = tmp[0].text;
+ }
+ }
+ return title;
+ }
+
+ /*
+ * Piwik Tracker class
+ *
+ * trackerUrl and trackerSiteId are optional arguments to the constructor
+ *
+ * See: Tracker.setTrackerUrl() and Tracker.setSiteId()
+ */
+ function Tracker(trackerUrl, siteId) {
+
+ /************************************************************
+ * Private members
+ ************************************************************/
+
+ var
/*<DEBUG>*/
- /*
- * registered test hooks
- */
- registeredHooks = {},
+ /*
+ * registered test hooks
+ */
+ registeredHooks = {},
/*</DEBUG>*/
- // Current URL and Referrer URL
- locationArray = urlFixup(documentAlias.domain, windowAlias.location.href, getReferrer()),
- domainAlias = domainFixup(locationArray[0]),
- locationHrefAlias = locationArray[1],
- configReferrerUrl = locationArray[2],
+ // Current URL and Referrer URL
+ locationArray = urlFixup(documentAlias.domain, windowAlias.location.href, getReferrer()),
+ domainAlias = domainFixup(locationArray[0]),
+ locationHrefAlias = locationArray[1],
+ configReferrerUrl = locationArray[2],
+
+ // Request method (GET or POST)
+ configRequestMethod = 'GET',
+
+ // Tracker URL
+ configTrackerUrl = trackerUrl || '',
+
+ // Site ID
+ configTrackerSiteId = siteId || '',
+
+ // Document URL
+ configCustomUrl,
+
+ // Document title
+ configTitle = documentAlias.title,
+
+ // Extensions to be treated as download links
+ configDownloadExtensions = '7z|aac|ar[cj]|as[fx]|avi|bin|csv|deb|dmg|doc|exe|flv|gif|gz|gzip|hqx|jar|jpe?g|js|mp(2|3|4|e?g)|mov(ie)?|ms[ip]|od[bfgpst]|og[gv]|pdf|phps|png|ppt|qtm?|ra[mr]?|rpm|sea|sit|tar|t?bz2?|tgz|torrent|txt|wav|wm[av]|wpd||xls|xml|z|zip',
+
+ // Hosts or alias(es) to not treat as outlinks
+ configHostsAlias = [domainAlias],
+
+ // HTML anchor element classes to not track
+ configIgnoreClasses = [],
+
+ // HTML anchor element classes to treat as downloads
+ configDownloadClasses = [],
+
+ // HTML anchor element classes to treat at outlinks
+ configLinkClasses = [],
+
+ // Maximum delay to wait for web bug image to be fetched (in milliseconds)
+ configTrackerPause = 500,
+
+ // Minimum visit time after initial page view (in milliseconds)
+ configMinimumVisitTime,
+
+ // Recurring heart beat after initial ping (in milliseconds)
+ configHeartBeatTimer,
+
+ // Disallow hash tags in URL
+ configDiscardHashTag,
+
+ // Custom data
+ configCustomData,
+
+ // Campaign names
+ configCampaignNameParameters = [ 'pk_campaign', 'piwik_campaign', 'utm_campaign', 'utm_source', 'utm_medium' ],
+
+ // Campaign keywords
+ configCampaignKeywordParameters = [ 'pk_kwd', 'piwik_kwd', 'utm_term' ],
+
+ // First-party cookie name prefix
+ configCookieNamePrefix = '_pk_',
+
+ // First-party cookie domain
+ // User agent defaults to origin hostname
+ configCookieDomain,
+
+ // First-party cookie path
+ // Default is user agent defined.
+ configCookiePath,
+
+ // Cookies are disabled
+ configCookiesDisabled = false,
+
+ // Do Not Track
+ configDoNotTrack,
+
+ // Count sites which are pre-rendered
+ configCountPreRendered,
+
+ // Do we attribute the conversion to the first referrer or the most recent referrer?
+ configConversionAttributionFirstReferrer,
+
+ // Life of the visitor cookie (in milliseconds)
+ configVisitorCookieTimeout = 63072000000, // 2 years
+
+ // Life of the session cookie (in milliseconds)
+ configSessionCookieTimeout = 1800000, // 30 minutes
+
+ // Life of the referral cookie (in milliseconds)
+ configReferralCookieTimeout = 15768000000, // 6 months
+
+ // Should cookies have the secure flag set
+ cookieSecure = documentAlias.location.protocol === 'https',
+
+ // Custom Variables read from cookie, scope "visit"
+ customVariables = false,
+
+ // Custom Variables, scope "page"
+ customVariablesPage = {},
+
+ // Custom Variables names and values are each truncated before being sent in the request or recorded in the cookie
+ customVariableMaximumLength = 200,
+
+ // Ecommerce items
+ ecommerceItems = {},
+
+ // Browser features via client-side data collection
+ browserFeatures = {},
+
+ // Guard against installing the link tracker more than once per Tracker instance
+ linkTrackingInstalled = false,
+
+ // Guard against installing the activity tracker more than once per Tracker instance
+ activityTrackingInstalled = false,
+
+ // Last activity timestamp
+ lastActivityTime,
+
+ // Internal state of the pseudo click handler
+ lastButton,
+ lastTarget,
+
+ // Hash function
+ hash = sha1,
+
+ // Domain hash value
+ domainHash,
+
+ // Visitor UUID
+ visitorUUID;
+
+
+ /*
+ * Set cookie value
+ */
+ function setCookie(cookieName, value, msToExpire, path, domain, secure) {
+ if (configCookiesDisabled) {
+ return;
+ }
+ var expiryDate;
+
+ // relative time to expire in milliseconds
+ if (msToExpire) {
+ expiryDate = new Date();
+ expiryDate.setTime(expiryDate.getTime() + msToExpire);
+ }
+
+ documentAlias.cookie = cookieName + '=' + encodeWrapper(value) +
+ (msToExpire ? ';expires=' + expiryDate.toGMTString() : '') +
+ ';path=' + (path || '/') +
+ (domain ? ';domain=' + domain : '') +
+ (secure ? ';secure' : '');
+ }
+
+ /*
+ * Get cookie value
+ */
+ function getCookie(cookieName) {
+ if (configCookiesDisabled) {
+ return 0;
+ }
+ var cookiePattern = new RegExp('(^|;)[ ]*' + cookieName + '=([^;]*)'),
+ cookieMatch = cookiePattern.exec(documentAlias.cookie);
+
+ return cookieMatch ? decodeWrapper(cookieMatch[2]) : 0;
+ }
+
+ /*
+ * Removes hash tag from the URL
+ *
+ * URLs are purified before being recorded in the cookie,
+ * or before being sent as GET parameters
+ */
+ function purify(url) {
+ var targetPattern;
+
+ if (configDiscardHashTag) {
+ targetPattern = new RegExp('#.*');
+ return url.replace(targetPattern, '');
+ }
+ return url;
+ }
+
+ /*
+ * Resolve relative reference
+ *
+ * Note: not as described in rfc3986 section 5.2
+ */
+ function resolveRelativeReference(baseUrl, url) {
+ var protocol = getProtocolScheme(url),
+ i;
+
+ if (protocol) {
+ return url;
+ }
+
+ if (url.slice(0, 1) === '/') {
+ return getProtocolScheme(baseUrl) + '://' + getHostName(baseUrl) + url;
+ }
+
+ baseUrl = purify(baseUrl);
+ if ((i = baseUrl.indexOf('?')) >= 0) {
+ baseUrl = baseUrl.slice(0, i);
+ }
+ if ((i = baseUrl.lastIndexOf('/')) !== baseUrl.length - 1) {
+ baseUrl = baseUrl.slice(0, i + 1);
+ }
+
+ return baseUrl + url;
+ }
+
+ /*
+ * Is the host local? (i.e., not an outlink)
+ */
+ function isSiteHostName(hostName) {
+ var i,
+ alias,
+ offset;
+
+ for (i = 0; i < configHostsAlias.length; i++) {
+ alias = domainFixup(configHostsAlias[i].toLowerCase());
+
+ if (hostName === alias) {
+ return true;
+ }
+
+ if (alias.slice(0, 1) === '.') {
+ if (hostName === alias.slice(1)) {
+ return true;
+ }
+
+ offset = hostName.length - alias.length;
+ if ((offset > 0) && (hostName.slice(offset) === alias)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /*
+ * Send image request to Piwik server using GET.
+ * The infamous web bug (or beacon) is a transparent, single pixel (1x1) image
+ */
+ function getImage(request) {
+ var image = new Image(1, 1);
- // Request method (GET or POST)
- configRequestMethod = 'GET',
+ image.onload = function () { };
+ image.src = configTrackerUrl + (configTrackerUrl.indexOf('?') < 0 ? '?' : '&') + request;
+ }
+
+ /*
+ * POST request to Piwik server using XMLHttpRequest.
+ */
+ function sendXmlHttpRequest(request) {
+ try {
+ // we use the progid Microsoft.XMLHTTP because
+ // IE5.5 included MSXML 2.5; the progid MSXML2.XMLHTTP
+ // is pinned to MSXML2.XMLHTTP.3.0
+ var xhr = windowAlias.XMLHttpRequest ? new windowAlias.XMLHttpRequest() :
+ windowAlias.ActiveXObject ? new ActiveXObject('Microsoft.XMLHTTP') :
+ null;
+
+ xhr.open('POST', configTrackerUrl, true);
+
+ // fallback on error
+ xhr.onreadystatechange = function () {
+ if (this.readyState === 4 && this.status !== 200) {
+ getImage(request);
+ }
+ };
+
+ // see XMLHttpRequest Level 2 spec, section 4.7.2 for invalid headers
+ // @link http://dvcs.w3.org/hg/xhr/raw-file/tip/Overview.html
+ xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
+
+ xhr.send(request);
+ } catch (e) {
+ // fallback
+ getImage(request);
+ }
+ }
+
+ /*
+ * Send request
+ */
+ function sendRequest(request, delay) {
+ var now = new Date();
+
+ if (!configDoNotTrack) {
+ if (configRequestMethod === 'POST') {
+ sendXmlHttpRequest(request);
+ } else {
+ getImage(request);
+ }
+
+ expireDateTime = now.getTime() + delay;
+ }
+ }
+
+ /*
+ * Get cookie name with prefix and domain hash
+ */
+ function getCookieName(baseName) {
+ // NOTE: If the cookie name is changed, we must also update the PiwikTracker.php which
+ // will attempt to discover first party cookies. eg. See the PHP Client method getVisitorId()
+ return configCookieNamePrefix + baseName + '.' + configTrackerSiteId + '.' + domainHash;
+ }
+
+ /*
+ * Does browser have cookies enabled (for this site)?
+ */
+ function hasCookies() {
+ if (configCookiesDisabled) {
+ return '0';
+ }
+ if (!isDefined(navigatorAlias.cookieEnabled)) {
+ var testCookieName = getCookieName('testcookie');
+ setCookie(testCookieName, '1');
+ return getCookie(testCookieName) === '1' ? '1' : '0';
+ }
+
+ return navigatorAlias.cookieEnabled ? '1' : '0';
+ }
+
+ /*
+ * Update domain hash
+ */
+ function updateDomainHash() {
+ domainHash = hash((configCookieDomain || domainAlias) + (configCookiePath || '/')).slice(0, 4); // 4 hexits = 16 bits
+ }
+
+ /*
+ * Inits the custom variables object
+ */
+ function getCustomVariablesFromCookie() {
+ var cookieName = getCookieName('cvar'),
+ cookie = getCookie(cookieName);
+
+ if (cookie.length) {
+ cookie = JSON2.parse(cookie);
+ if (isObject(cookie)) {
+ return cookie;
+ }
+ }
+ return {};
+ }
+
+ /*
+ * Lazy loads the custom variables from the cookie, only once during this page view
+ */
+ function loadCustomVariables() {
+ if (customVariables === false) {
+ customVariables = getCustomVariablesFromCookie();
+ }
+ }
+
+ /*
+ * Process all "activity" events.
+ * For performance, this function must have low overhead.
+ */
+ function activityHandler() {
+ var now = new Date();
+
+ lastActivityTime = now.getTime();
+ }
+
+ /*
+ * Sets the Visitor ID cookie: either the first time loadVisitorIdCookie is called
+ * or when there is a new visit or a new page view
+ */
+ function setVisitorIdCookie(uuid, createTs, visitCount, nowTs, lastVisitTs, lastEcommerceOrderTs) {
+ setCookie(getCookieName('id'), uuid + '.' + createTs + '.' + visitCount + '.' + nowTs + '.' + lastVisitTs + '.' + lastEcommerceOrderTs, configVisitorCookieTimeout, configCookiePath, configCookieDomain, cookieSecure);
+ }
- // Tracker URL
- configTrackerUrl = trackerUrl || '',
+ /*
+ * Load visitor ID cookie
+ */
+ function loadVisitorIdCookie() {
+ var now = new Date(),
+ nowTs = Math.round(now.getTime() / 1000),
+ id = getCookie(getCookieName('id')),
+ tmpContainer;
+
+ if (id) {
+ tmpContainer = id.split('.');
+
+ // returning visitor flag
+ tmpContainer.unshift('0');
+ } else {
+ // uuid - generate a pseudo-unique ID to fingerprint this user;
+ // note: this isn't a RFC4122-compliant UUID
+ if (!visitorUUID) {
+ visitorUUID = hash(
+ (navigatorAlias.userAgent || '') +
+ (navigatorAlias.platform || '') +
+ JSON2.stringify(browserFeatures) + nowTs
+ ).slice(0, 16); // 16 hexits = 64 bits
+ }
- // Site ID
- configTrackerSiteId = siteId || '',
+ tmpContainer = [
+ // new visitor
+ '1',
- // Document URL
- configCustomUrl,
+ // uuid
+ visitorUUID,
- // Document title
- configTitle = documentAlias.title,
+ // creation timestamp - seconds since Unix epoch
+ nowTs,
- // Extensions to be treated as download links
- configDownloadExtensions = '7z|aac|ar[cj]|as[fx]|avi|bin|csv|deb|dmg|doc|exe|flv|gif|gz|gzip|hqx|jar|jpe?g|js|mp(2|3|4|e?g)|mov(ie)?|ms[ip]|od[bfgpst]|og[gv]|pdf|phps|png|ppt|qtm?|ra[mr]?|rpm|sea|sit|tar|t?bz2?|tgz|torrent|txt|wav|wm[av]|wpd||xls|xml|z|zip',
+ // visitCount - 0 = no previous visit
+ 0,
- // Hosts or alias(es) to not treat as outlinks
- configHostsAlias = [domainAlias],
+ // current visit timestamp
+ nowTs,
- // HTML anchor element classes to not track
- configIgnoreClasses = [],
+ // last visit timestamp - blank = no previous visit
+ '',
- // HTML anchor element classes to treat as downloads
- configDownloadClasses = [],
+ // last ecommerce order timestamp
+ ''
+ ];
+ }
+ return tmpContainer;
+ }
- // HTML anchor element classes to treat at outlinks
- configLinkClasses = [],
+ /*
+ * Loads the referrer attribution information
+ *
+ * @returns array
+ * 0: campaign name
+ * 1: campaign keyword
+ * 2: timestamp
+ * 3: raw URL
+ */
+ function loadReferrerAttributionCookie() {
+ // NOTE: if the format of the cookie changes,
+ // we must also update JS tests, PHP tracker, Integration tests,
+ // and notify other tracking clients (eg. Java) of the changes
+ var cookie = getCookie(getCookieName('ref'));
+
+ if (cookie.length) {
+ try {
+ cookie = JSON2.parse(cookie);
+ if (isObject(cookie)) {
+ return cookie;
+ }
+ } catch (err) {
+ // Pre 1.3, this cookie was not JSON encoded
+ }
+ }
+ return [
+ '',
+ '',
+ 0,
+ ''
+ ];
+ }
- // Maximum delay to wait for web bug image to be fetched (in milliseconds)
- configTrackerPause = 500,
+ /*
+ * Returns the URL to call piwik.php,
+ * with the standard parameters (plugins, resolution, url, referrer, etc.).
+ * Sends the pageview and browser settings with every request in case of race conditions.
+ */
+ function getRequest(request, customData, pluginMethod, currentEcommerceOrderTs) {
+ var i,
+ now = new Date(),
+ nowTs = Math.round(now.getTime() / 1000),
+ newVisitor,
+ uuid,
+ visitCount,
+ createTs,
+ currentVisitTs,
+ lastVisitTs,
+ lastEcommerceOrderTs,
+ referralTs,
+ referralUrl,
+ referralUrlMaxLength = 1024,
+ currentReferrerHostName,
+ originalReferrerHostName,
+ customVariablesCopy = customVariables,
+ idname = getCookieName('id'),
+ sesname = getCookieName('ses'),
+ refname = getCookieName('ref'),
+ cvarname = getCookieName('cvar'),
+ id = loadVisitorIdCookie(),
+ ses = getCookie(sesname),
+ attributionCookie = loadReferrerAttributionCookie(),
+ currentUrl = configCustomUrl || locationHrefAlias,
+ campaignNameDetected,
+ campaignKeywordDetected;
+
+ if (configCookiesDisabled) {
+ // Temporarily allow cookies just to delete the existing ones
+ configCookiesDisabled = false;
+ setCookie(idname, '', -86400, configCookiePath, configCookieDomain);
+ setCookie(sesname, '', -86400, configCookiePath, configCookieDomain);
+ setCookie(cvarname, '', -86400, configCookiePath, configCookieDomain);
+ setCookie(refname, '', -86400, configCookiePath, configCookieDomain);
+ configCookiesDisabled = true;
+ }
- // Minimum visit time after initial page view (in milliseconds)
- configMinimumVisitTime,
+ if (configDoNotTrack) {
+ return '';
+ }
- // Recurring heart beat after initial ping (in milliseconds)
- configHeartBeatTimer,
+ newVisitor = id[0];
+ uuid = id[1];
+ createTs = id[2];
+ visitCount = id[3];
+ currentVisitTs = id[4];
+ lastVisitTs = id[5];
+ // case migrating from pre-1.5 cookies
+ if (!isDefined(id[6])) {
+ id[6] = "";
+ }
+ lastEcommerceOrderTs = id[6];
- // Disallow hash tags in URL
- configDiscardHashTag,
+ if (!isDefined(currentEcommerceOrderTs)) {
+ currentEcommerceOrderTs = "";
+ }
- // Custom data
- configCustomData,
+ campaignNameDetected = attributionCookie[0];
+ campaignKeywordDetected = attributionCookie[1];
+ referralTs = attributionCookie[2];
+ referralUrl = attributionCookie[3];
+
+ if (!ses) {
+ // new session (aka new visit)
+ visitCount++;
+
+ lastVisitTs = currentVisitTs;
+
+ // Detect the campaign information from the current URL
+ // Only if campaign wasn't previously set
+ // Or if it was set but we must attribute to the most recent one
+ // Note: we are working on the currentUrl before purify() since we can parse the campaign parameters in the hash tag
+ if (!configConversionAttributionFirstReferrer
+ || !campaignNameDetected.length) {
+ for (i in configCampaignNameParameters) {
+ if (Object.prototype.hasOwnProperty.call(configCampaignNameParameters, i)) {
+ campaignNameDetected = getParameter(currentUrl, configCampaignNameParameters[i]);
+ if (campaignNameDetected.length) {
+ break;
+ }
+ }
+ }
+ for (i in configCampaignKeywordParameters) {
+ if (Object.prototype.hasOwnProperty.call(configCampaignKeywordParameters, i)) {
+ campaignKeywordDetected = getParameter(currentUrl, configCampaignKeywordParameters[i]);
+ if (campaignKeywordDetected.length) {
+ break;
+ }
+ }
+ }
+ }
- // Campaign names
- configCampaignNameParameters = [ 'pk_campaign', 'piwik_campaign', 'utm_campaign', 'utm_source', 'utm_medium' ],
+ // Store the referrer URL and time in the cookie;
+ // referral URL depends on the first or last referrer attribution
+ currentReferrerHostName = getHostName(configReferrerUrl);
+ originalReferrerHostName = referralUrl.length ? getHostName(referralUrl) : '';
+ if (currentReferrerHostName.length && // there is a referrer
+ !isSiteHostName(currentReferrerHostName) && // domain is not the current domain
+ (!configConversionAttributionFirstReferrer || // attribute to last known referrer
+ !originalReferrerHostName.length || // previously empty
+ isSiteHostName(originalReferrerHostName))) { // previously set but in current domain
+ referralUrl = configReferrerUrl;
+ }
- // Campaign keywords
- configCampaignKeywordParameters = [ 'pk_kwd', 'piwik_kwd', 'utm_term' ],
+ // Set the referral cookie if we have either a Referrer URL, or detected a Campaign (or both)
+ if (referralUrl.length
+ || campaignNameDetected.length) {
+ referralTs = nowTs;
+ attributionCookie = [
+ campaignNameDetected,
+ campaignKeywordDetected,
+ referralTs,
+ purify(referralUrl.slice(0, referralUrlMaxLength))
+ ];
+
+ setCookie(refname, JSON2.stringify(attributionCookie), configReferralCookieTimeout, configCookiePath, configCookieDomain, cookieSecure);
+ }
+ }
+ // build out the rest of the request
+ request += '&idsite=' + configTrackerSiteId +
+ '&rec=1' +
+ '&r=' + String(Math.random()).slice(2, 8) + // keep the string to a minimum
+ '&h=' + now.getHours() + '&m=' + now.getMinutes() + '&s=' + now.getSeconds() +
+ '&url=' + encodeWrapper(purify(currentUrl)) +
+ (configReferrerUrl.length ? '&urlref=' + encodeWrapper(purify(configReferrerUrl)) : '') +
+ '&_id=' + uuid + '&_idts=' + createTs + '&_idvc=' + visitCount +
+ '&_idn=' + newVisitor + // currently unused
+ (campaignNameDetected.length ? '&_rcn=' + encodeWrapper(campaignNameDetected) : '') +
+ (campaignKeywordDetected.length ? '&_rck=' + encodeWrapper(campaignKeywordDetected) : '') +
+ '&_refts=' + referralTs +
+ '&_viewts=' + lastVisitTs +
+ (String(lastEcommerceOrderTs).length ? '&_ects=' + lastEcommerceOrderTs : '') +
+ (String(referralUrl).length ? '&_ref=' + encodeWrapper(purify(referralUrl.slice(0, referralUrlMaxLength))) : '');
+
+ // Custom Variables, scope "page"
+ var customVariablesPageStringified = JSON2.stringify(customVariablesPage);
+ if (customVariablesPageStringified.length > 2) {
+ request += '&cvar=' + encodeWrapper(customVariablesPageStringified);
+ }
- // First-party cookie name prefix
- configCookieNamePrefix = '_pk_',
+ // browser features
+ for (i in browserFeatures) {
+ if (Object.prototype.hasOwnProperty.call(browserFeatures, i)) {
+ request += '&' + i + '=' + browserFeatures[i];
+ }
+ }
- // First-party cookie domain
- // User agent defaults to origin hostname
- configCookieDomain,
+ // custom data
+ if (customData) {
+ request += '&data=' + encodeWrapper(JSON2.stringify(customData));
+ } else if (configCustomData) {
+ request += '&data=' + encodeWrapper(JSON2.stringify(configCustomData));
+ }
- // First-party cookie path
- // Default is user agent defined.
- configCookiePath,
+ // Custom Variables, scope "visit"
+ if (customVariables) {
+ var customVariablesStringified = JSON2.stringify(customVariables);
+ // Don't sent empty custom variables {}
+ if (customVariablesStringified.length > 2) {
+ request += '&_cvar=' + encodeWrapper(customVariablesStringified);
+ }
- // Cookies are disabled
- configCookiesDisabled = false,
+ // Don't save deleted custom variables in the cookie
+ for (i in customVariablesCopy) {
+ if (Object.prototype.hasOwnProperty.call(customVariablesCopy, i)) {
+ if (customVariables[i][0] === '' || customVariables[i][1] === '') {
+ delete customVariables[i];
+ }
+ }
+ }
- // Do Not Track
- configDoNotTrack,
+ setCookie(cvarname, JSON2.stringify(customVariables), configSessionCookieTimeout, configCookiePath, configCookieDomain, cookieSecure);
+ }
- // Count sites which are pre-rendered
- configCountPreRendered,
+ // update cookies
+ setVisitorIdCookie(uuid, createTs, visitCount, nowTs, lastVisitTs, isDefined(currentEcommerceOrderTs) && String(currentEcommerceOrderTs).length ? currentEcommerceOrderTs : lastEcommerceOrderTs);
+ setCookie(sesname, '*', configSessionCookieTimeout, configCookiePath, configCookieDomain, cookieSecure);
- // Do we attribute the conversion to the first referrer or the most recent referrer?
- configConversionAttributionFirstReferrer,
+ // tracker plugin hook
+ request += executePluginMethod(pluginMethod);
- // Life of the visitor cookie (in milliseconds)
- configVisitorCookieTimeout = 63072000000, // 2 years
+ return request;
+ }
- // Life of the session cookie (in milliseconds)
- configSessionCookieTimeout = 1800000, // 30 minutes
+ function logEcommerce(orderId, grandTotal, subTotal, tax, shipping, discount) {
+ var request = 'idgoal=0',
+ lastEcommerceOrderTs,
+ now = new Date(),
+ items = [],
+ sku;
+
+ if (String(orderId).length) {
+ request += '&ec_id=' + encodeWrapper(orderId);
+ // Record date of order in the visitor cookie
+ lastEcommerceOrderTs = Math.round(now.getTime() / 1000);
+ }
- // Life of the referral cookie (in milliseconds)
- configReferralCookieTimeout = 15768000000, // 6 months
+ request += '&revenue=' + grandTotal;
+ if (String(subTotal).length) {
+ request += '&ec_st=' + subTotal;
+ }
+ if (String(tax).length) {
+ request += '&ec_tx=' + tax;
+ }
+ if (String(shipping).length) {
+ request += '&ec_sh=' + shipping;
+ }
+ if (String(discount).length) {
+ request += '&ec_dt=' + discount;
+ }
+ if (ecommerceItems) {
+ // Removing the SKU index in the array before JSON encoding
+ for (sku in ecommerceItems) {
+ if (Object.prototype.hasOwnProperty.call(ecommerceItems, sku)) {
+ // Ensure name and category default to healthy value
+ if (!isDefined(ecommerceItems[sku][1])) {
+ ecommerceItems[sku][1] = "";
+ }
+ if (!isDefined(ecommerceItems[sku][2])) {
+ ecommerceItems[sku][2] = "";
+ }
+ // Set price to zero
+ if (!isDefined(ecommerceItems[sku][3])
+ || String(ecommerceItems[sku][3]).length === 0) {
+ ecommerceItems[sku][3] = 0;
+ }
+ // Set quantity to 1
+ if (!isDefined(ecommerceItems[sku][4])
+ || String(ecommerceItems[sku][4]).length === 0) {
+ ecommerceItems[sku][4] = 1;
+ }
+ items.push(ecommerceItems[sku]);
+ }
+ }
+ request += '&ec_items=' + encodeWrapper(JSON2.stringify(items));
+ }
+ request = getRequest(request, configCustomData, 'ecommerce', lastEcommerceOrderTs);
+ sendRequest(request, configTrackerPause);
+ }
- // Should cookies have the secure flag set
- cookieSecure = documentAlias.location.protocol === 'https',
+ function logEcommerceOrder(orderId, grandTotal, subTotal, tax, shipping, discount) {
+ if (String(orderId).length
+ && isDefined(grandTotal)) {
+ logEcommerce(orderId, grandTotal, subTotal, tax, shipping, discount);
+ }
+ }
- // Custom Variables read from cookie, scope "visit"
- customVariables = false,
+ function logEcommerceCartUpdate(grandTotal) {
+ if (isDefined(grandTotal)) {
+ logEcommerce("", grandTotal, "", "", "", "");
+ }
+ }
- // Custom Variables, scope "page"
- customVariablesPage = {},
+ /*
+ * Log the page view / visit
+ */
+ function logPageView(customTitle, customData) {
+ var now = new Date(),
+ request = getRequest('action_name=' + encodeWrapper(titleFixup(customTitle || configTitle)), customData, 'log');
+
+ sendRequest(request, configTrackerPause);
+
+ // send ping
+ if (configMinimumVisitTime && configHeartBeatTimer && !activityTrackingInstalled) {
+ activityTrackingInstalled = true;
+
+ // add event handlers; cross-browser compatibility here varies significantly
+ // @see http://quirksmode.org/dom/events
+ addEventListener(documentAlias, 'click', activityHandler);
+ addEventListener(documentAlias, 'mouseup', activityHandler);
+ addEventListener(documentAlias, 'mousedown', activityHandler);
+ addEventListener(documentAlias, 'mousemove', activityHandler);
+ addEventListener(documentAlias, 'mousewheel', activityHandler);
+ addEventListener(windowAlias, 'DOMMouseScroll', activityHandler);
+ addEventListener(windowAlias, 'scroll', activityHandler);
+ addEventListener(documentAlias, 'keypress', activityHandler);
+ addEventListener(documentAlias, 'keydown', activityHandler);
+ addEventListener(documentAlias, 'keyup', activityHandler);
+ addEventListener(windowAlias, 'resize', activityHandler);
+ addEventListener(windowAlias, 'focus', activityHandler);
+ addEventListener(windowAlias, 'blur', activityHandler);
+
+ // periodic check for activity
+ lastActivityTime = now.getTime();
+ setTimeout(function heartBeat() {
+ var now = new Date(),
+ request;
+
+ // there was activity during the heart beat period;
+ // on average, this is going to overstate the visitDuration by configHeartBeatTimer/2
+ if ((lastActivityTime + configHeartBeatTimer) > now.getTime()) {
+ // send ping if minimum visit time has elapsed
+ if (configMinimumVisitTime < now.getTime()) {
+ request = getRequest('ping=1', customData, 'ping');
+
+ sendRequest(request, configTrackerPause);
+ }
- // Custom Variables names and values are each truncated before being sent in the request or recorded in the cookie
- customVariableMaximumLength = 200,
+ // resume heart beat
+ setTimeout(heartBeat, configHeartBeatTimer);
+ }
+ // else heart beat cancelled due to inactivity
+ }, configHeartBeatTimer);
+ }
+ }
- // Ecommerce items
- ecommerceItems = {},
+ /*
+ * Log the goal with the server
+ */
+ function logGoal(idGoal, customRevenue, customData) {
+ var request = getRequest('idgoal=' + idGoal + (customRevenue ? '&revenue=' + customRevenue : ''), customData, 'goal');
- // Browser features via client-side data collection
- browserFeatures = {},
+ sendRequest(request, configTrackerPause);
+ }
- // Guard against installing the link tracker more than once per Tracker instance
- linkTrackingInstalled = false,
+ /*
+ * Log the link or click with the server
+ */
+ function logLink(url, linkType, customData) {
+ var request = getRequest(linkType + '=' + encodeWrapper(purify(url)), customData, 'link');
- // Guard against installing the activity tracker more than once per Tracker instance
- activityTrackingInstalled = false,
+ sendRequest(request, configTrackerPause);
+ }
- // Last activity timestamp
- lastActivityTime,
+ /*
+ * Browser prefix
+ */
+ function prefixPropertyName(prefix, propertyName) {
+ if (prefix !== '') {
+ return prefix + propertyName.charAt(0).toUpperCase() + propertyName.slice(1);
+ }
- // Internal state of the pseudo click handler
- lastButton,
- lastTarget,
+ return propertyName;
+ }
- // Hash function
- hash = sha1,
+ /*
+ * Check for pre-rendered web pages, and log the page view/link/goal
+ * according to the configuration and/or visibility
+ *
+ * @see http://dvcs.w3.org/hg/webperf/raw-file/tip/specs/PageVisibility/Overview.html
+ */
+ function trackCallback(callback) {
+ var isPreRendered,
+ i,
+ // Chrome 13, IE10, FF10
+ prefixes = ['', 'webkit', 'ms', 'moz'],
+ prefix;
+
+ if (!configCountPreRendered) {
+ for (i = 0; i < prefixes.length; i++) {
+ prefix = prefixes[i];
+
+ // does this browser support the page visibility API?
+ if (Object.prototype.hasOwnProperty.call(documentAlias, prefixPropertyName(prefix, 'hidden'))) {
+ // if pre-rendered, then defer callback until page visibility changes
+ if (documentAlias[prefixPropertyName(prefix, 'visibilityState')] === 'prerender') {
+ isPreRendered = true;
+ }
+ break;
+ }
+ }
+ }
- // Domain hash value
- domainHash,
+ if (isPreRendered) {
+ // note: the event name doesn't follow the same naming convention as vendor properties
+ addEventListener(documentAlias, prefix + 'visibilitychange', function ready() {
+ documentAlias.removeEventListener(prefix + 'visibilitychange', ready, false);
+ callback();
+ });
+ return;
+ }
- // Visitor UUID
- visitorUUID;
+ // configCountPreRendered === true || isPreRendered === false
+ callback();
+ }
+ /*
+ * Construct regular expression of classes
+ */
+ function getClassesRegExp(configClasses, defaultClass) {
+ var i,
+ classesRegExp = '(^| )(piwik[_-]' + defaultClass;
- /*
- * Set cookie value
- */
- function setCookie(cookieName, value, msToExpire, path, domain, secure) {
- if (configCookiesDisabled) {
- return;
- }
- var expiryDate;
-
- // relative time to expire in milliseconds
- if (msToExpire) {
- expiryDate = new Date();
- expiryDate.setTime(expiryDate.getTime() + msToExpire);
- }
-
- documentAlias.cookie = cookieName + '=' + encodeWrapper(value) +
- (msToExpire ? ';expires=' + expiryDate.toGMTString() : '') +
- ';path=' + (path || '/') +
- (domain ? ';domain=' + domain : '') +
- (secure ? ';secure' : '');
- }
-
- /*
- * Get cookie value
- */
- function getCookie(cookieName) {
- if (configCookiesDisabled) {
- return 0;
- }
- var cookiePattern = new RegExp('(^|;)[ ]*' + cookieName + '=([^;]*)'),
- cookieMatch = cookiePattern.exec(documentAlias.cookie);
-
- return cookieMatch ? decodeWrapper(cookieMatch[2]) : 0;
- }
-
- /*
- * Removes hash tag from the URL
- *
- * URLs are purified before being recorded in the cookie,
- * or before being sent as GET parameters
- */
- function purify(url) {
- var targetPattern;
-
- if (configDiscardHashTag) {
- targetPattern = new RegExp('#.*');
- return url.replace(targetPattern, '');
- }
- return url;
- }
-
- /*
- * Resolve relative reference
- *
- * Note: not as described in rfc3986 section 5.2
- */
- function resolveRelativeReference(baseUrl, url) {
- var protocol = getProtocolScheme(url),
- i;
-
- if (protocol) {
- return url;
- }
-
- if (url.slice(0, 1) === '/') {
- return getProtocolScheme(baseUrl) + '://' + getHostName(baseUrl) + url;
- }
-
- baseUrl = purify(baseUrl);
- if ((i = baseUrl.indexOf('?')) >= 0) {
- baseUrl = baseUrl.slice(0, i);
- }
- if ((i = baseUrl.lastIndexOf('/')) !== baseUrl.length - 1) {
- baseUrl = baseUrl.slice(0, i + 1);
- }
-
- return baseUrl + url;
- }
-
- /*
- * Is the host local? (i.e., not an outlink)
- */
- function isSiteHostName(hostName) {
- var i,
- alias,
- offset;
-
- for (i = 0; i < configHostsAlias.length; i++) {
- alias = domainFixup(configHostsAlias[i].toLowerCase());
-
- if (hostName === alias) {
- return true;
- }
-
- if (alias.slice(0, 1) === '.') {
- if (hostName === alias.slice(1)) {
- return true;
- }
-
- offset = hostName.length - alias.length;
- if ((offset > 0) && (hostName.slice(offset) === alias)) {
- return true;
- }
- }
- }
- return false;
- }
-
- /*
- * Send image request to Piwik server using GET.
- * The infamous web bug (or beacon) is a transparent, single pixel (1x1) image
- */
- function getImage(request) {
- var image = new Image(1, 1);
-
- image.onload = function () { };
- image.src = configTrackerUrl + (configTrackerUrl.indexOf('?') < 0 ? '?' : '&') + request;
- }
-
- /*
- * POST request to Piwik server using XMLHttpRequest.
- */
- function sendXmlHttpRequest(request) {
- try {
- // we use the progid Microsoft.XMLHTTP because
- // IE5.5 included MSXML 2.5; the progid MSXML2.XMLHTTP
- // is pinned to MSXML2.XMLHTTP.3.0
- var xhr = windowAlias.XMLHttpRequest ? new windowAlias.XMLHttpRequest() :
- windowAlias.ActiveXObject ? new ActiveXObject('Microsoft.XMLHTTP') :
- null;
-
- xhr.open('POST', configTrackerUrl, true);
-
- // fallback on error
- xhr.onreadystatechange = function () {
- if (this.readyState === 4 && this.status !== 200) {
- getImage(request);
- }
- };
-
- // see XMLHttpRequest Level 2 spec, section 4.7.2 for invalid headers
- // @link http://dvcs.w3.org/hg/xhr/raw-file/tip/Overview.html
- xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
-
- xhr.send(request);
- } catch (e) {
- // fallback
- getImage(request);
- }
- }
-
- /*
- * Send request
- */
- function sendRequest(request, delay) {
- var now = new Date();
-
- if (!configDoNotTrack) {
- if (configRequestMethod === 'POST') {
- sendXmlHttpRequest(request);
- } else {
- getImage(request);
- }
-
- expireDateTime = now.getTime() + delay;
- }
- }
-
- /*
- * Get cookie name with prefix and domain hash
- */
- function getCookieName(baseName) {
- // NOTE: If the cookie name is changed, we must also update the PiwikTracker.php which
- // will attempt to discover first party cookies. eg. See the PHP Client method getVisitorId()
- return configCookieNamePrefix + baseName + '.' + configTrackerSiteId + '.' + domainHash;
- }
-
- /*
- * Does browser have cookies enabled (for this site)?
- */
- function hasCookies() {
- if (configCookiesDisabled) {
- return '0';
- }
- if (!isDefined(navigatorAlias.cookieEnabled)) {
- var testCookieName = getCookieName('testcookie');
- setCookie(testCookieName, '1');
- return getCookie(testCookieName) === '1' ? '1' : '0';
- }
-
- return navigatorAlias.cookieEnabled ? '1' : '0';
- }
-
- /*
- * Update domain hash
- */
- function updateDomainHash() {
- domainHash = hash((configCookieDomain || domainAlias) + (configCookiePath || '/')).slice(0, 4); // 4 hexits = 16 bits
- }
-
- /*
- * Inits the custom variables object
- */
- function getCustomVariablesFromCookie() {
- var cookieName = getCookieName('cvar'),
- cookie = getCookie(cookieName);
-
- if (cookie.length) {
- cookie = JSON2.parse(cookie);
- if (isObject(cookie)) {
- return cookie;
- }
- }
- return {};
- }
-
- /*
- * Lazy loads the custom variables from the cookie, only once during this page view
- */
- function loadCustomVariables() {
- if (customVariables === false) {
- customVariables = getCustomVariablesFromCookie();
- }
- }
-
- /*
- * Process all "activity" events.
- * For performance, this function must have low overhead.
- */
- function activityHandler() {
- var now = new Date();
-
- lastActivityTime = now.getTime();
- }
-
- /*
- * Sets the Visitor ID cookie: either the first time loadVisitorIdCookie is called
- * or when there is a new visit or a new page view
- */
- function setVisitorIdCookie(uuid, createTs, visitCount, nowTs, lastVisitTs, lastEcommerceOrderTs) {
- setCookie(getCookieName('id'), uuid + '.' + createTs + '.' + visitCount + '.' + nowTs + '.' + lastVisitTs + '.' + lastEcommerceOrderTs, configVisitorCookieTimeout, configCookiePath, configCookieDomain, cookieSecure);
- }
-
- /*
- * Load visitor ID cookie
- */
- function loadVisitorIdCookie() {
- var now = new Date(),
- nowTs = Math.round(now.getTime() / 1000),
- id = getCookie(getCookieName('id')),
- tmpContainer;
-
- if (id) {
- tmpContainer = id.split('.');
-
- // returning visitor flag
- tmpContainer.unshift('0');
- } else {
- // uuid - generate a pseudo-unique ID to fingerprint this user;
- // note: this isn't a RFC4122-compliant UUID
- if (!visitorUUID) {
- visitorUUID = hash(
- (navigatorAlias.userAgent || '') +
- (navigatorAlias.platform || '') +
- JSON2.stringify(browserFeatures) + nowTs
- ).slice(0, 16); // 16 hexits = 64 bits
- }
-
- tmpContainer = [
- // new visitor
- '1',
-
- // uuid
- visitorUUID,
-
- // creation timestamp - seconds since Unix epoch
- nowTs,
-
- // visitCount - 0 = no previous visit
- 0,
-
- // current visit timestamp
- nowTs,
-
- // last visit timestamp - blank = no previous visit
- '',
-
- // last ecommerce order timestamp
- ''
- ];
- }
- return tmpContainer;
- }
-
- /*
- * Loads the referrer attribution information
- *
- * @returns array
- * 0: campaign name
- * 1: campaign keyword
- * 2: timestamp
- * 3: raw URL
- */
- function loadReferrerAttributionCookie() {
- // NOTE: if the format of the cookie changes,
- // we must also update JS tests, PHP tracker, Integration tests,
- // and notify other tracking clients (eg. Java) of the changes
- var cookie = getCookie(getCookieName('ref'));
-
- if (cookie.length) {
- try {
- cookie = JSON2.parse(cookie);
- if (isObject(cookie)) {
- return cookie;
- }
- } catch (err) {
- // Pre 1.3, this cookie was not JSON encoded
- }
- }
- return [
- '',
- '',
- 0,
- ''
- ];
- }
-
- /*
- * Returns the URL to call piwik.php,
- * with the standard parameters (plugins, resolution, url, referrer, etc.).
- * Sends the pageview and browser settings with every request in case of race conditions.
- */
- function getRequest(request, customData, pluginMethod, currentEcommerceOrderTs) {
- var i,
- now = new Date(),
- nowTs = Math.round(now.getTime() / 1000),
- newVisitor,
- uuid,
- visitCount,
- createTs,
- currentVisitTs,
- lastVisitTs,
- lastEcommerceOrderTs,
- referralTs,
- referralUrl,
- referralUrlMaxLength = 1024,
- currentReferrerHostName,
- originalReferrerHostName,
- customVariablesCopy = customVariables,
- idname = getCookieName('id'),
- sesname = getCookieName('ses'),
- refname = getCookieName('ref'),
- cvarname = getCookieName('cvar'),
- id = loadVisitorIdCookie(),
- ses = getCookie(sesname),
- attributionCookie = loadReferrerAttributionCookie(),
- currentUrl = configCustomUrl || locationHrefAlias,
- campaignNameDetected,
- campaignKeywordDetected;
-
- if (configCookiesDisabled) {
- // Temporarily allow cookies just to delete the existing ones
- configCookiesDisabled = false;
- setCookie(idname, '', -86400, configCookiePath, configCookieDomain);
- setCookie(sesname, '', -86400, configCookiePath, configCookieDomain);
- setCookie(cvarname, '', -86400, configCookiePath, configCookieDomain);
- setCookie(refname, '', -86400, configCookiePath, configCookieDomain);
- configCookiesDisabled = true;
- }
-
- if (configDoNotTrack) {
- return '';
- }
-
- newVisitor = id[0];
- uuid = id[1];
- createTs = id[2];
- visitCount = id[3];
- currentVisitTs = id[4];
- lastVisitTs = id[5];
- // case migrating from pre-1.5 cookies
- if (!isDefined(id[6])) {
- id[6] = "";
- }
- lastEcommerceOrderTs = id[6];
-
- if (!isDefined(currentEcommerceOrderTs)) {
- currentEcommerceOrderTs = "";
- }
-
- campaignNameDetected = attributionCookie[0];
- campaignKeywordDetected = attributionCookie[1];
- referralTs = attributionCookie[2];
- referralUrl = attributionCookie[3];
-
- if (!ses) {
- // new session (aka new visit)
- visitCount++;
-
- lastVisitTs = currentVisitTs;
-
- // Detect the campaign information from the current URL
- // Only if campaign wasn't previously set
- // Or if it was set but we must attribute to the most recent one
- // Note: we are working on the currentUrl before purify() since we can parse the campaign parameters in the hash tag
- if (!configConversionAttributionFirstReferrer
- || !campaignNameDetected.length) {
- for (i in configCampaignNameParameters) {
- if (Object.prototype.hasOwnProperty.call(configCampaignNameParameters, i)) {
- campaignNameDetected = getParameter(currentUrl, configCampaignNameParameters[i]);
- if (campaignNameDetected.length) {
- break;
- }
- }
- }
- for (i in configCampaignKeywordParameters) {
- if (Object.prototype.hasOwnProperty.call(configCampaignKeywordParameters, i)) {
- campaignKeywordDetected = getParameter(currentUrl, configCampaignKeywordParameters[i]);
- if (campaignKeywordDetected.length) {
- break;
- }
- }
- }
- }
-
- // Store the referrer URL and time in the cookie;
- // referral URL depends on the first or last referrer attribution
- currentReferrerHostName = getHostName(configReferrerUrl);
- originalReferrerHostName = referralUrl.length ? getHostName(referralUrl) : '';
- if (currentReferrerHostName.length && // there is a referrer
- !isSiteHostName(currentReferrerHostName) && // domain is not the current domain
- (!configConversionAttributionFirstReferrer || // attribute to last known referrer
- !originalReferrerHostName.length || // previously empty
- isSiteHostName(originalReferrerHostName))) { // previously set but in current domain
- referralUrl = configReferrerUrl;
- }
-
- // Set the referral cookie if we have either a Referrer URL, or detected a Campaign (or both)
- if (referralUrl.length
- || campaignNameDetected.length) {
- referralTs = nowTs;
- attributionCookie = [
- campaignNameDetected,
- campaignKeywordDetected,
- referralTs,
- purify(referralUrl.slice(0, referralUrlMaxLength))
- ];
-
- setCookie(refname, JSON2.stringify(attributionCookie), configReferralCookieTimeout, configCookiePath, configCookieDomain, cookieSecure);
- }
- }
- // build out the rest of the request
- request += '&idsite=' + configTrackerSiteId +
- '&rec=1' +
- '&r=' + String(Math.random()).slice(2, 8) + // keep the string to a minimum
- '&h=' + now.getHours() + '&m=' + now.getMinutes() + '&s=' + now.getSeconds() +
- '&url=' + encodeWrapper(purify(currentUrl)) +
- (configReferrerUrl.length ? '&urlref=' + encodeWrapper(purify(configReferrerUrl)) : '') +
- '&_id=' + uuid + '&_idts=' + createTs + '&_idvc=' + visitCount +
- '&_idn=' + newVisitor + // currently unused
- (campaignNameDetected.length ? '&_rcn=' + encodeWrapper(campaignNameDetected) : '') +
- (campaignKeywordDetected.length ? '&_rck=' + encodeWrapper(campaignKeywordDetected) : '') +
- '&_refts=' + referralTs +
- '&_viewts=' + lastVisitTs +
- (String(lastEcommerceOrderTs).length ? '&_ects=' + lastEcommerceOrderTs : '') +
- (String(referralUrl).length ? '&_ref=' + encodeWrapper(purify(referralUrl.slice(0, referralUrlMaxLength))) : '');
-
- // Custom Variables, scope "page"
- var customVariablesPageStringified = JSON2.stringify(customVariablesPage);
- if (customVariablesPageStringified.length > 2) {
- request += '&cvar=' + encodeWrapper(customVariablesPageStringified);
- }
-
- // browser features
- for (i in browserFeatures) {
- if (Object.prototype.hasOwnProperty.call(browserFeatures, i)) {
- request += '&' + i + '=' + browserFeatures[i];
- }
- }
-
- // custom data
- if (customData) {
- request += '&data=' + encodeWrapper(JSON2.stringify(customData));
- } else if (configCustomData) {
- request += '&data=' + encodeWrapper(JSON2.stringify(configCustomData));
- }
-
- // Custom Variables, scope "visit"
- if (customVariables) {
- var customVariablesStringified = JSON2.stringify(customVariables);
- // Don't sent empty custom variables {}
- if (customVariablesStringified.length > 2) {
- request += '&_cvar=' + encodeWrapper(customVariablesStringified);
- }
-
- // Don't save deleted custom variables in the cookie
- for (i in customVariablesCopy) {
- if (Object.prototype.hasOwnProperty.call(customVariablesCopy, i)) {
- if (customVariables[i][0] === '' || customVariables[i][1] === '') {
- delete customVariables[i];
- }
- }
- }
-
- setCookie(cvarname, JSON2.stringify(customVariables), configSessionCookieTimeout, configCookiePath, configCookieDomain, cookieSecure);
- }
-
- // update cookies
- setVisitorIdCookie(uuid, createTs, visitCount, nowTs, lastVisitTs, isDefined(currentEcommerceOrderTs) && String(currentEcommerceOrderTs).length ? currentEcommerceOrderTs : lastEcommerceOrderTs);
- setCookie(sesname, '*', configSessionCookieTimeout, configCookiePath, configCookieDomain, cookieSecure);
-
- // tracker plugin hook
- request += executePluginMethod(pluginMethod);
-
- return request;
- }
-
- function logEcommerce(orderId, grandTotal, subTotal, tax, shipping, discount) {
- var request = 'idgoal=0',
- lastEcommerceOrderTs,
- now = new Date(),
- items = [],
- sku;
-
- if (String(orderId).length) {
- request += '&ec_id=' + encodeWrapper(orderId);
- // Record date of order in the visitor cookie
- lastEcommerceOrderTs = Math.round(now.getTime() / 1000);
- }
-
- request += '&revenue=' + grandTotal;
- if (String(subTotal).length) {
- request += '&ec_st=' + subTotal;
- }
- if (String(tax).length) {
- request += '&ec_tx=' + tax;
- }
- if (String(shipping).length) {
- request += '&ec_sh=' + shipping;
- }
- if (String(discount).length) {
- request += '&ec_dt=' + discount;
- }
- if (ecommerceItems) {
- // Removing the SKU index in the array before JSON encoding
- for (sku in ecommerceItems) {
- if (Object.prototype.hasOwnProperty.call(ecommerceItems, sku)) {
- // Ensure name and category default to healthy value
- if (!isDefined(ecommerceItems[sku][1])) {
- ecommerceItems[sku][1] = "";
- }
- if (!isDefined(ecommerceItems[sku][2])) {
- ecommerceItems[sku][2] = "";
- }
- // Set price to zero
- if (!isDefined(ecommerceItems[sku][3])
- || String(ecommerceItems[sku][3]).length === 0) {
- ecommerceItems[sku][3] = 0;
- }
- // Set quantity to 1
- if (!isDefined(ecommerceItems[sku][4])
- || String(ecommerceItems[sku][4]).length === 0) {
- ecommerceItems[sku][4] = 1;
- }
- items.push(ecommerceItems[sku]);
- }
- }
- request += '&ec_items=' + encodeWrapper(JSON2.stringify(items));
- }
- request = getRequest(request, configCustomData, 'ecommerce', lastEcommerceOrderTs);
- sendRequest(request, configTrackerPause);
- }
-
- function logEcommerceOrder(orderId, grandTotal, subTotal, tax, shipping, discount) {
- if (String(orderId).length
- && isDefined(grandTotal)) {
- logEcommerce(orderId, grandTotal, subTotal, tax, shipping, discount);
- }
- }
-
- function logEcommerceCartUpdate(grandTotal) {
- if (isDefined(grandTotal)) {
- logEcommerce("", grandTotal, "", "", "", "");
- }
- }
-
- /*
- * Log the page view / visit
- */
- function logPageView(customTitle, customData) {
- var now = new Date(),
- request = getRequest('action_name=' + encodeWrapper(titleFixup(customTitle || configTitle)), customData, 'log');
-
- sendRequest(request, configTrackerPause);
-
- // send ping
- if (configMinimumVisitTime && configHeartBeatTimer && !activityTrackingInstalled) {
- activityTrackingInstalled = true;
-
- // add event handlers; cross-browser compatibility here varies significantly
- // @see http://quirksmode.org/dom/events
- addEventListener(documentAlias, 'click', activityHandler);
- addEventListener(documentAlias, 'mouseup', activityHandler);
- addEventListener(documentAlias, 'mousedown', activityHandler);
- addEventListener(documentAlias, 'mousemove', activityHandler);
- addEventListener(documentAlias, 'mousewheel', activityHandler);
- addEventListener(windowAlias, 'DOMMouseScroll', activityHandler);
- addEventListener(windowAlias, 'scroll', activityHandler);
- addEventListener(documentAlias, 'keypress', activityHandler);
- addEventListener(documentAlias, 'keydown', activityHandler);
- addEventListener(documentAlias, 'keyup', activityHandler);
- addEventListener(windowAlias, 'resize', activityHandler);
- addEventListener(windowAlias, 'focus', activityHandler);
- addEventListener(windowAlias, 'blur', activityHandler);
-
- // periodic check for activity
- lastActivityTime = now.getTime();
- setTimeout(function heartBeat() {
- var now = new Date(),
- request;
-
- // there was activity during the heart beat period;
- // on average, this is going to overstate the visitDuration by configHeartBeatTimer/2
- if ((lastActivityTime + configHeartBeatTimer) > now.getTime()) {
- // send ping if minimum visit time has elapsed
- if (configMinimumVisitTime < now.getTime()) {
- request = getRequest('ping=1', customData, 'ping');
-
- sendRequest(request, configTrackerPause);
- }
-
- // resume heart beat
- setTimeout(heartBeat, configHeartBeatTimer);
- }
- // else heart beat cancelled due to inactivity
- }, configHeartBeatTimer);
- }
- }
-
- /*
- * Log the goal with the server
- */
- function logGoal(idGoal, customRevenue, customData) {
- var request = getRequest('idgoal=' + idGoal + (customRevenue ? '&revenue=' + customRevenue : ''), customData, 'goal');
-
- sendRequest(request, configTrackerPause);
- }
-
- /*
- * Log the link or click with the server
- */
- function logLink(url, linkType, customData) {
- var request = getRequest(linkType + '=' + encodeWrapper(purify(url)), customData, 'link');
-
- sendRequest(request, configTrackerPause);
- }
-
- /*
- * Browser prefix
- */
- function prefixPropertyName(prefix, propertyName) {
- if (prefix !== '') {
- return prefix + propertyName.charAt(0).toUpperCase() + propertyName.slice(1);
- }
-
- return propertyName;
- }
-
- /*
- * Check for pre-rendered web pages, and log the page view/link/goal
- * according to the configuration and/or visibility
- *
- * @see http://dvcs.w3.org/hg/webperf/raw-file/tip/specs/PageVisibility/Overview.html
- */
- function trackCallback(callback) {
- var isPreRendered,
- i,
- // Chrome 13, IE10, FF10
- prefixes = ['', 'webkit', 'ms', 'moz'],
- prefix;
-
- if (!configCountPreRendered) {
- for (i = 0; i < prefixes.length; i++) {
- prefix = prefixes[i];
-
- // does this browser support the page visibility API?
- if (Object.prototype.hasOwnProperty.call(documentAlias, prefixPropertyName(prefix, 'hidden'))) {
- // if pre-rendered, then defer callback until page visibility changes
- if (documentAlias[prefixPropertyName(prefix, 'visibilityState')] === 'prerender') {
- isPreRendered = true;
- }
- break;
- }
- }
- }
-
- if (isPreRendered) {
- // note: the event name doesn't follow the same naming convention as vendor properties
- addEventListener(documentAlias, prefix + 'visibilitychange', function ready() {
- documentAlias.removeEventListener(prefix + 'visibilitychange', ready, false);
- callback();
- });
- return;
- }
-
- // configCountPreRendered === true || isPreRendered === false
- callback();
- }
-
- /*
- * Construct regular expression of classes
- */
- function getClassesRegExp(configClasses, defaultClass) {
- var i,
- classesRegExp = '(^| )(piwik[_-]' + defaultClass;
-
- if (configClasses) {
- for (i = 0; i < configClasses.length; i++) {
- classesRegExp += '|' + configClasses[i];
- }
- }
- classesRegExp += ')( |$)';
-
- return new RegExp(classesRegExp);
- }
-
- /*
- * Link or Download?
- */
- function getLinkType(className, href, isInLink) {
- // outlinks
- if (!isInLink) {
- return 'link';
- }
-
- // does class indicate whether it is an (explicit/forced) outlink or a download?
- var downloadPattern = getClassesRegExp(configDownloadClasses, 'download'),
- linkPattern = getClassesRegExp(configLinkClasses, 'link'),
-
- // does file extension indicate that it is a download?
- downloadExtensionsPattern = new RegExp('\\.(' + configDownloadExtensions + ')([?&#]|$)', 'i');
-
- // optimization of the if..elseif..else construct below
- return linkPattern.test(className) ? 'link' : (downloadPattern.test(className) || downloadExtensionsPattern.test(href) ? 'download' : 0);
+ if (configClasses) {
+ for (i = 0; i < configClasses.length; i++) {
+ classesRegExp += '|' + configClasses[i];
+ }
+ }
+ classesRegExp += ')( |$)';
+
+ return new RegExp(classesRegExp);
+ }
+
+ /*
+ * Link or Download?
+ */
+ function getLinkType(className, href, isInLink) {
+ // outlinks
+ if (!isInLink) {
+ return 'link';
+ }
+
+ // does class indicate whether it is an (explicit/forced) outlink or a download?
+ var downloadPattern = getClassesRegExp(configDownloadClasses, 'download'),
+ linkPattern = getClassesRegExp(configLinkClasses, 'link'),
+
+ // does file extension indicate that it is a download?
+ downloadExtensionsPattern = new RegExp('\\.(' + configDownloadExtensions + ')([?&#]|$)', 'i');
+
+ // optimization of the if..elseif..else construct below
+ return linkPattern.test(className) ? 'link' : (downloadPattern.test(className) || downloadExtensionsPattern.test(href) ? 'download' : 0);
/*
- var linkType;
-
- if (linkPattern.test(className)) {
- // class attribute contains 'piwik_link' (or user's override)
- linkType = 'link';
- } else if (downloadPattern.test(className)) {
- // class attribute contains 'piwik_download' (or user's override)
- linkType = 'download';
- } else if (downloadExtensionsPattern.test(sourceHref)) {
- // file extension matches a defined download extension
- linkType = 'download';
- } else {
- // otherwise none of the above
- linkType = 0;
- }
-
- return linkType;
+ var linkType;
+
+ if (linkPattern.test(className)) {
+ // class attribute contains 'piwik_link' (or user's override)
+ linkType = 'link';
+ } else if (downloadPattern.test(className)) {
+ // class attribute contains 'piwik_download' (or user's override)
+ linkType = 'download';
+ } else if (downloadExtensionsPattern.test(sourceHref)) {
+ // file extension matches a defined download extension
+ linkType = 'download';
+ } else {
+ // otherwise none of the above
+ linkType = 0;
+ }
+
+ return linkType;
*/
- }
-
- /*
- * Process clicks
- */
- function processClick(sourceElement) {
- var parentElement,
- tag,
- linkType;
-
- while ((parentElement = sourceElement.parentNode) !== null &&
- isDefined(parentElement) && // buggy IE5.5
- ((tag = sourceElement.tagName.toUpperCase()) !== 'A' && tag !== 'AREA')) {
- sourceElement = parentElement;
- }
-
- if (isDefined(sourceElement.href)) {
- // browsers, such as Safari, don't downcase hostname and href
- var originalSourceHostName = sourceElement.hostname || getHostName(sourceElement.href),
- sourceHostName = originalSourceHostName.toLowerCase(),
- sourceHref = sourceElement.href.replace(originalSourceHostName, sourceHostName),
- scriptProtocol = new RegExp('^(javascript|vbscript|jscript|mocha|livescript|ecmascript|mailto):', 'i');
-
- // ignore script pseudo-protocol links
- if (!scriptProtocol.test(sourceHref)) {
- // track outlinks and all downloads
- linkType = getLinkType(sourceElement.className, sourceHref, isSiteHostName(sourceHostName));
- if (linkType) {
- // urldecode %xx
- sourceHref = urldecode(sourceHref);
- logLink(sourceHref, linkType);
- }
- }
- }
- }
-
- /*
- * Handle click event
- */
- function clickHandler(evt) {
- var button,
- target;
-
- evt = evt || windowAlias.event;
- button = evt.which || evt.button;
- target = evt.target || evt.srcElement;
-
- // Using evt.type (added in IE4), we avoid defining separate handlers for mouseup and mousedown.
- if (evt.type === 'click') {
- if (target) {
- processClick(target);
- }
- } else if (evt.type === 'mousedown') {
- if ((button === 1 || button === 2) && target) {
- lastButton = button;
- lastTarget = target;
- } else {
- lastButton = lastTarget = null;
- }
- } else if (evt.type === 'mouseup') {
- if (button === lastButton && target === lastTarget) {
- processClick(target);
- }
- lastButton = lastTarget = null;
- }
- }
-
- /*
- * Add click listener to a DOM element
- */
- function addClickListener(element, enable) {
- if (enable) {
- // for simplicity and performance, we ignore drag events
- addEventListener(element, 'mouseup', clickHandler, false);
- addEventListener(element, 'mousedown', clickHandler, false);
- } else {
- addEventListener(element, 'click', clickHandler, false);
- }
- }
-
- /*
- * Add click handlers to anchor and AREA elements, except those to be ignored
- */
- function addClickListeners(enable) {
- if (!linkTrackingInstalled) {
- linkTrackingInstalled = true;
-
- // iterate through anchor elements with href and AREA elements
-
- var i,
- ignorePattern = getClassesRegExp(configIgnoreClasses, 'ignore'),
- linkElements = documentAlias.links;
-
- if (linkElements) {
- for (i = 0; i < linkElements.length; i++) {
- if (!ignorePattern.test(linkElements[i].className)) {
- addClickListener(linkElements[i], enable);
- }
- }
- }
- }
- }
-
- /*
- * Browser features (plugins, resolution, cookies)
- */
- function detectBrowserFeatures() {
- var i,
- mimeType,
- pluginMap = {
- // document types
- pdf: 'application/pdf',
-
- // media players
- qt: 'video/quicktime',
- realp: 'audio/x-pn-realaudio-plugin',
- wma: 'application/x-mplayer2',
-
- // interactive multimedia
- dir: 'application/x-director',
- fla: 'application/x-shockwave-flash',
-
- // RIA
- java: 'application/x-java-vm',
- gears: 'application/x-googlegears',
- ag: 'application/x-silverlight'
- };
-
- if (!((new RegExp('MSIE')).test(navigatorAlias.userAgent))) {
- // general plugin detection
- if (navigatorAlias.mimeTypes && navigatorAlias.mimeTypes.length) {
- for (i in pluginMap) {
- if (Object.prototype.hasOwnProperty.call(pluginMap, i)) {
- mimeType = navigatorAlias.mimeTypes[pluginMap[i]];
- browserFeatures[i] = (mimeType && mimeType.enabledPlugin) ? '1' : '0';
- }
- }
- }
-
- // Safari and Opera
- // IE6/IE7 navigator.javaEnabled can't be aliased, so test directly
- if (typeof navigator.javaEnabled !== 'unknown' &&
- isDefined(navigatorAlias.javaEnabled) &&
- navigatorAlias.javaEnabled()) {
- browserFeatures.java = '1';
- }
-
- // Firefox
- if (isFunction(windowAlias.GearsFactory)) {
- browserFeatures.gears = '1';
- }
-
- // other browser features
- browserFeatures.cookie = hasCookies();
- }
-
- // screen resolution
- browserFeatures.res = screenAlias.width + 'x' + screenAlias.height;
- }
+ }
+
+ /*
+ * Process clicks
+ */
+ function processClick(sourceElement) {
+ var parentElement,
+ tag,
+ linkType;
+
+ while ((parentElement = sourceElement.parentNode) !== null &&
+ isDefined(parentElement) && // buggy IE5.5
+ ((tag = sourceElement.tagName.toUpperCase()) !== 'A' && tag !== 'AREA')) {
+ sourceElement = parentElement;
+ }
+
+ if (isDefined(sourceElement.href)) {
+ // browsers, such as Safari, don't downcase hostname and href
+ var originalSourceHostName = sourceElement.hostname || getHostName(sourceElement.href),
+ sourceHostName = originalSourceHostName.toLowerCase(),
+ sourceHref = sourceElement.href.replace(originalSourceHostName, sourceHostName),
+ scriptProtocol = new RegExp('^(javascript|vbscript|jscript|mocha|livescript|ecmascript|mailto):', 'i');
+
+ // ignore script pseudo-protocol links
+ if (!scriptProtocol.test(sourceHref)) {
+ // track outlinks and all downloads
+ linkType = getLinkType(sourceElement.className, sourceHref, isSiteHostName(sourceHostName));
+ if (linkType) {
+ // urldecode %xx
+ sourceHref = urldecode(sourceHref);
+ logLink(sourceHref, linkType);
+ }
+ }
+ }
+ }
+
+ /*
+ * Handle click event
+ */
+ function clickHandler(evt) {
+ var button,
+ target;
+
+ evt = evt || windowAlias.event;
+ button = evt.which || evt.button;
+ target = evt.target || evt.srcElement;
+
+ // Using evt.type (added in IE4), we avoid defining separate handlers for mouseup and mousedown.
+ if (evt.type === 'click') {
+ if (target) {
+ processClick(target);
+ }
+ } else if (evt.type === 'mousedown') {
+ if ((button === 1 || button === 2) && target) {
+ lastButton = button;
+ lastTarget = target;
+ } else {
+ lastButton = lastTarget = null;
+ }
+ } else if (evt.type === 'mouseup') {
+ if (button === lastButton && target === lastTarget) {
+ processClick(target);
+ }
+ lastButton = lastTarget = null;
+ }
+ }
+
+ /*
+ * Add click listener to a DOM element
+ */
+ function addClickListener(element, enable) {
+ if (enable) {
+ // for simplicity and performance, we ignore drag events
+ addEventListener(element, 'mouseup', clickHandler, false);
+ addEventListener(element, 'mousedown', clickHandler, false);
+ } else {
+ addEventListener(element, 'click', clickHandler, false);
+ }
+ }
+
+ /*
+ * Add click handlers to anchor and AREA elements, except those to be ignored
+ */
+ function addClickListeners(enable) {
+ if (!linkTrackingInstalled) {
+ linkTrackingInstalled = true;
+
+ // iterate through anchor elements with href and AREA elements
+
+ var i,
+ ignorePattern = getClassesRegExp(configIgnoreClasses, 'ignore'),
+ linkElements = documentAlias.links;
+
+ if (linkElements) {
+ for (i = 0; i < linkElements.length; i++) {
+ if (!ignorePattern.test(linkElements[i].className)) {
+ addClickListener(linkElements[i], enable);
+ }
+ }
+ }
+ }
+ }
+
+ /*
+ * Browser features (plugins, resolution, cookies)
+ */
+ function detectBrowserFeatures() {
+ var i,
+ mimeType,
+ pluginMap = {
+ // document types
+ pdf: 'application/pdf',
+
+ // media players
+ qt: 'video/quicktime',
+ realp: 'audio/x-pn-realaudio-plugin',
+ wma: 'application/x-mplayer2',
+
+ // interactive multimedia
+ dir: 'application/x-director',
+ fla: 'application/x-shockwave-flash',
+
+ // RIA
+ java: 'application/x-java-vm',
+ gears: 'application/x-googlegears',
+ ag: 'application/x-silverlight'
+ };
+
+ if (!((new RegExp('MSIE')).test(navigatorAlias.userAgent))) {
+ // general plugin detection
+ if (navigatorAlias.mimeTypes && navigatorAlias.mimeTypes.length) {
+ for (i in pluginMap) {
+ if (Object.prototype.hasOwnProperty.call(pluginMap, i)) {
+ mimeType = navigatorAlias.mimeTypes[pluginMap[i]];
+ browserFeatures[i] = (mimeType && mimeType.enabledPlugin) ? '1' : '0';
+ }
+ }
+ }
+
+ // Safari and Opera
+ // IE6/IE7 navigator.javaEnabled can't be aliased, so test directly
+ if (typeof navigator.javaEnabled !== 'unknown' &&
+ isDefined(navigatorAlias.javaEnabled) &&
+ navigatorAlias.javaEnabled()) {
+ browserFeatures.java = '1';
+ }
+
+ // Firefox
+ if (isFunction(windowAlias.GearsFactory)) {
+ browserFeatures.gears = '1';
+ }
+
+ // other browser features
+ browserFeatures.cookie = hasCookies();
+ }
+
+ // screen resolution
+ browserFeatures.res = screenAlias.width + 'x' + screenAlias.height;
+ }
/*<DEBUG>*/
- /*
- * Register a test hook. Using eval() permits access to otherwise
- * privileged members.
- */
- function registerHook(hookName, userHook) {
- var hookObj = null;
-
- if (isString(hookName) && !isDefined(registeredHooks[hookName]) && userHook) {
- if (isObject(userHook)) {
- hookObj = userHook;
- } else if (isString(userHook)) {
- try {
- eval('hookObj =' + userHook);
- } catch (e) { }
- }
-
- registeredHooks[hookName] = hookObj;
- }
- return hookObj;
- }
+ /*
+ * Register a test hook. Using eval() permits access to otherwise
+ * privileged members.
+ */
+ function registerHook(hookName, userHook) {
+ var hookObj = null;
+
+ if (isString(hookName) && !isDefined(registeredHooks[hookName]) && userHook) {
+ if (isObject(userHook)) {
+ hookObj = userHook;
+ } else if (isString(userHook)) {
+ try {
+ eval('hookObj =' + userHook);
+ } catch (e) { }
+ }
+
+ registeredHooks[hookName] = hookObj;
+ }
+ return hookObj;
+ }
/*</DEBUG>*/
- /************************************************************
- * Constructor
- ************************************************************/
+ /************************************************************
+ * Constructor
+ ************************************************************/
- /*
- * initialize tracker
- */
- detectBrowserFeatures();
- updateDomainHash();
+ /*
+ * initialize tracker
+ */
+ detectBrowserFeatures();
+ updateDomainHash();
/*<DEBUG>*/
- /*
- * initialize test plugin
- */
- executePluginMethod('run', registerHook);
+ /*
+ * initialize test plugin
+ */
+ executePluginMethod('run', registerHook);
/*</DEBUG>*/
- /************************************************************
- * Public data and methods
- ************************************************************/
+ /************************************************************
+ * Public data and methods
+ ************************************************************/
- return {
+ return {
/*<DEBUG>*/
- /*
- * Test hook accessors
- */
- hook: registeredHooks,
- getHook: function (hookName) {
- return registeredHooks[hookName];
- },
+ /*
+ * Test hook accessors
+ */
+ hook: registeredHooks,
+ getHook: function (hookName) {
+ return registeredHooks[hookName];
+ },
/*</DEBUG>*/
- /**
- * Get visitor ID (from first party cookie)
- *
- * @return string Visitor ID in hexits (or null, if not yet known)
- */
- getVisitorId: function () {
- return (loadVisitorIdCookie())[1];
- },
-
- /**
- * Get the visitor information (from first party cookie)
- *
- * @return array
- */
- getVisitorInfo: function () {
- return loadVisitorIdCookie();
- },
-
- /**
- * Get the Attribution information, which is an array that contains
- * the Referrer used to reach the site as well as the campaign name and keyword
- * It is useful only when used in conjunction with Tracker API function setAttributionInfo()
- * To access specific data point, you should use the other functions getAttributionReferrer* and getAttributionCampaign*
- *
- * @return array Attribution array, Example use:
- * 1) Call JSON2.stringify(piwikTracker.getAttributionInfo())
- * 2) Pass this json encoded string to the Tracking API (php or java client): setAttributionInfo()
- */
- getAttributionInfo: function () {
- return loadReferrerAttributionCookie();
- },
-
- /**
- * Get the Campaign name that was parsed from the landing page URL when the visitor
- * landed on the site originally
- *
- * @return string
- */
- getAttributionCampaignName: function () {
- return loadReferrerAttributionCookie()[0];
- },
-
- /**
- * Get the Campaign keyword that was parsed from the landing page URL when the visitor
- * landed on the site originally
- *
- * @return string
- */
- getAttributionCampaignKeyword: function () {
- return loadReferrerAttributionCookie()[1];
- },
-
- /**
- * Get the time at which the referrer (used for Goal Attribution) was detected
- *
- * @return int Timestamp or 0 if no referrer currently set
- */
- getAttributionReferrerTimestamp: function () {
- return loadReferrerAttributionCookie()[2];
- },
-
- /**
- * Get the full referrer URL that will be used for Goal Attribution
- *
- * @return string Raw URL, or empty string '' if no referrer currently set
- */
- getAttributionReferrerUrl: function () {
- return loadReferrerAttributionCookie()[3];
- },
-
- /**
- * Specify the Piwik server URL
- *
- * @param string trackerUrl
- */
- setTrackerUrl: function (trackerUrl) {
- configTrackerUrl = trackerUrl;
- },
-
- /**
- * Specify the site ID
- *
- * @param int|string siteId
- */
- setSiteId: function (siteId) {
- configTrackerSiteId = siteId;
- },
-
- /**
- * Pass custom data to the server
- *
- * Examples:
- * tracker.setCustomData(object);
- * tracker.setCustomData(key, value);
- *
- * @param mixed key_or_obj
- * @param mixed opt_value
- */
- setCustomData: function (key_or_obj, opt_value) {
- if (isObject(key_or_obj)) {
- configCustomData = key_or_obj;
- } else {
- if (!configCustomData) {
- configCustomData = [];
- }
- configCustomData[key_or_obj] = opt_value;
- }
- },
-
- /**
- * Get custom data
- *
- * @return mixed
- */
- getCustomData: function () {
- return configCustomData;
- },
-
- /**
- * Set custom variable within this visit
- *
- * @param int index
- * @param string name
- * @param string value
- * @param string scope Scope of Custom Variable:
- * - "visit" will store the name/value in the visit and will persist it in the cookie for the duration of the visit,
- * - "page" will store the name/value in the page view.
- */
- setCustomVariable: function (index, name, value, scope) {
- var toRecord;
- if (!isDefined(scope)) {
- scope = 'visit';
- }
- if (index > 0) {
- name = isDefined(name) && !isString(name) ? String(name) : name;
- value = isDefined(value) && !isString(value) ? String(value) : value;
- toRecord = [name.slice(0, customVariableMaximumLength), value.slice(0, customVariableMaximumLength)];
- if (scope === 'visit' || scope === 2) { /* GA compatibility/misuse */
- loadCustomVariables();
- customVariables[index] = toRecord;
- } else if (scope === 'page' || scope === 3) { /* GA compatibility/misuse */
- customVariablesPage[index] = toRecord;
- }
- }
- },
-
- /**
- * Get custom variable
- *
- * @param int index
- * @param string scope Scope of Custom Variable: "visit" or "page"
- */
- getCustomVariable: function (index, scope) {
- var cvar;
- if (!isDefined(scope)) {
- scope = "visit";
- }
- if (scope === "page" || scope === 3) {
- cvar = customVariablesPage[index];
- } else if (scope === "visit" || scope === 2) {
- loadCustomVariables();
- cvar = customVariables[index];
- }
- if (!isDefined(cvar)
- || (cvar && cvar[0] === '')) {
- return false;
- }
- return cvar;
- },
-
- /**
- * Delete custom variable
- *
- * @param int index
- */
- deleteCustomVariable: function (index, scope) {
- // Only delete if it was there already
- if (this.getCustomVariable(index, scope)) {
- this.setCustomVariable(index, '', '', scope);
- }
- },
-
- /**
- * Set delay for link tracking (in milliseconds)
- *
- * @param int delay
- */
- setLinkTrackingTimer: function (delay) {
- configTrackerPause = delay;
- },
-
- /**
- * Set list of file extensions to be recognized as downloads
- *
- * @param string extensions
- */
- setDownloadExtensions: function (extensions) {
- configDownloadExtensions = extensions;
- },
-
- /**
- * Specify additional file extensions to be recognized as downloads
- *
- * @param string extensions
- */
- addDownloadExtensions: function (extensions) {
- configDownloadExtensions += '|' + extensions;
- },
-
- /**
- * Set array of domains to be treated as local
- *
- * @param string|array hostsAlias
- */
- setDomains: function (hostsAlias) {
- configHostsAlias = isString(hostsAlias) ? [hostsAlias] : hostsAlias;
- configHostsAlias.push(domainAlias);
- },
-
- /**
- * Set array of classes to be ignored if present in link
- *
- * @param string|array ignoreClasses
- */
- setIgnoreClasses: function (ignoreClasses) {
- configIgnoreClasses = isString(ignoreClasses) ? [ignoreClasses] : ignoreClasses;
- },
-
- /**
- * Set request method
- *
- * @param string method GET or POST; default is GET
- */
- setRequestMethod: function (method) {
- configRequestMethod = method || 'GET';
- },
-
- /**
- * Override referrer
- *
- * @param string url
- */
- setReferrerUrl: function (url) {
- configReferrerUrl = url;
- },
-
- /**
- * Override url
- *
- * @param string url
- */
- setCustomUrl: function (url) {
- configCustomUrl = resolveRelativeReference(locationHrefAlias, url);
- },
-
- /**
- * Override document.title
- *
- * @param string title
- */
- setDocumentTitle: function (title) {
- configTitle = title;
- },
-
- /**
- * Set array of classes to be treated as downloads
- *
- * @param string|array downloadClasses
- */
- setDownloadClasses: function (downloadClasses) {
- configDownloadClasses = isString(downloadClasses) ? [downloadClasses] : downloadClasses;
- },
-
- /**
- * Set array of classes to be treated as outlinks
- *
- * @param string|array linkClasses
- */
- setLinkClasses: function (linkClasses) {
- configLinkClasses = isString(linkClasses) ? [linkClasses] : linkClasses;
- },
-
- /**
- * Set array of campaign name parameters
- *
- * @see http://piwik.org/faq/how-to/#faq_120
- * @param string|array campaignNames
- */
- setCampaignNameKey: function (campaignNames) {
- configCampaignNameParameters = isString(campaignNames) ? [campaignNames] : campaignNames;
- },
-
- /**
- * Set array of campaign keyword parameters
- *
- * @see http://piwik.org/faq/how-to/#faq_120
- * @param string|array campaignKeywords
- */
- setCampaignKeywordKey: function (campaignKeywords) {
- configCampaignKeywordParameters = isString(campaignKeywords) ? [campaignKeywords] : campaignKeywords;
- },
-
- /**
- * Strip hash tag (or anchor) from URL
- *
- * @param bool enableFilter
- */
- discardHashTag: function (enableFilter) {
- configDiscardHashTag = enableFilter;
- },
-
- /**
- * Set first-party cookie name prefix
- *
- * @param string cookieNamePrefix
- */
- setCookieNamePrefix: function (cookieNamePrefix) {
- configCookieNamePrefix = cookieNamePrefix;
- // Re-init the Custom Variables cookie
- customVariables = getCustomVariablesFromCookie();
- },
-
- /**
- * Set first-party cookie domain
- *
- * @param string domain
- */
- setCookieDomain: function (domain) {
- configCookieDomain = domainFixup(domain);
- updateDomainHash();
- },
-
- /**
- * Set first-party cookie path
- *
- * @param string domain
- */
- setCookiePath: function (path) {
- configCookiePath = path;
- updateDomainHash();
- },
-
- /**
- * Set visitor cookie timeout (in seconds)
- *
- * @param int timeout
- */
- setVisitorCookieTimeout: function (timeout) {
- configVisitorCookieTimeout = timeout * 1000;
- },
-
- /**
- * Set session cookie timeout (in seconds)
- *
- * @param int timeout
- */
- setSessionCookieTimeout: function (timeout) {
- configSessionCookieTimeout = timeout * 1000;
- },
-
- /**
- * Set referral cookie timeout (in seconds)
- *
- * @param int timeout
- */
- setReferralCookieTimeout: function (timeout) {
- configReferralCookieTimeout = timeout * 1000;
- },
-
- /**
- * Set conversion attribution to first referrer and campaign
- *
- * @param bool if true, use first referrer (and first campaign)
- * if false, use the last referrer (or campaign)
- */
- setConversionAttributionFirstReferrer: function (enable) {
- configConversionAttributionFirstReferrer = enable;
- },
-
- /**
- * Disables all cookies from being set
- * Existing cookies will be deleted on the next call to track*
- *
- */
- disableCookies: function () {
- configCookiesDisabled = true;
- browserFeatures.cookie = '0';
- },
-
- /**
- * Handle do-not-track requests
- *
- * @param bool enable If true, don't track if user agent sends 'do-not-track' header
- */
- setDoNotTrack: function (enable) {
- var dnt = navigatorAlias.doNotTrack || navigatorAlias.msDoNotTrack;
- configDoNotTrack = enable && (dnt === 'yes' || dnt === '1');
-
- // do not track also disables cookies and deletes existing cookies
- if (configDoNotTrack) {
- this.disableCookies();
- }
- },
-
- /**
- * Add click listener to a specific link element.
- * When clicked, Piwik will log the click automatically.
- *
- * @param DOMElement element
- * @param bool enable If true, use pseudo click-handler (mousedown+mouseup)
- */
- addListener: function (element, enable) {
- addClickListener(element, enable);
- },
-
- /**
- * Install link tracker
- *
- * The default behaviour is to use actual click events. However, some browsers
- * (e.g., Firefox, Opera, and Konqueror) don't generate click events for the middle mouse button.
- *
- * To capture more "clicks", the pseudo click-handler uses mousedown + mouseup events.
- * This is not industry standard and is vulnerable to false positives (e.g., drag events).
- *
- * There is a Safari/Chrome/Webkit bug that prevents tracking requests from being sent
- * by either click handler. The workaround is to set a target attribute (which can't
- * be "_self", "_top", or "_parent").
- *
- * @see https://bugs.webkit.org/show_bug.cgi?id=54783
- *
- * @param bool enable If true, use pseudo click-handler (mousedown+mouseup)
- */
- enableLinkTracking: function (enable) {
- if (hasLoaded) {
- // the load event has already fired, add the click listeners now
- addClickListeners(enable);
- } else {
- // defer until page has loaded
- registeredOnLoadHandlers.push(function () {
- addClickListeners(enable);
- });
- }
- },
-
- /**
- * Set heartbeat (in seconds)
- *
- * @param int minimumVisitLength
- * @param int heartBeatDelay
- */
- setHeartBeatTimer: function (minimumVisitLength, heartBeatDelay) {
- var now = new Date();
-
- configMinimumVisitTime = now.getTime() + minimumVisitLength * 1000;
- configHeartBeatTimer = heartBeatDelay * 1000;
- },
-
- /**
- * Frame buster
- */
- killFrame: function () {
- if (windowAlias.location !== windowAlias.top.location) {
- windowAlias.top.location = windowAlias.location;
- }
- },
-
- /**
- * Redirect if browsing offline (aka file: buster)
- *
- * @param string url Redirect to this URL
- */
- redirectFile: function (url) {
- if (windowAlias.location.protocol === 'file:') {
- windowAlias.location = url;
- }
- },
-
- /**
- * Count sites in pre-rendered state
- *
- * @param bool enable If true, track when in pre-rendered state
- */
- setCountPreRendered: function (enable) {
- configCountPreRendered = enable;
- },
-
- /**
- * Trigger a goal
- *
- * @param int|string idGoal
- * @param int|float customRevenue
- * @param mixed customData
- */
- trackGoal: function (idGoal, customRevenue, customData) {
- trackCallback(function () {
- logGoal(idGoal, customRevenue, customData);
- });
- },
-
- /**
- * Manually log a click from your own code
- *
- * @param string sourceUrl
- * @param string linkType
- * @param mixed customData
- */
- trackLink: function (sourceUrl, linkType, customData) {
- trackCallback(function () {
- logLink(sourceUrl, linkType, customData);
- });
- },
-
- /**
- * Log visit to this page
- *
- * @param string customTitle
- * @param mixed customData
- */
- trackPageView: function (customTitle, customData) {
- trackCallback(function () {
- logPageView(customTitle, customData);
- });
- },
-
-
- /**
- * Used to record that the current page view is an item (product) page view, or a Ecommerce Category page view.
- * This must be called before trackPageView() on the product/category page.
- * It will set 3 custom variables of scope "page" with the SKU, Name and Category for this page view.
- * Note: Custom Variables of scope "page" slots 3, 4 and 5 will be used.
- *
- * On a category page, you can set the parameter category, and set the other parameters to empty string or false
- *
- * Tracking Product/Category page views will allow Piwik to report on Product & Categories
- * conversion rates (Conversion rate = Ecommerce orders containing this product or category / Visits to the product or category)
- *
- * @param string sku Item's SKU code being viewed
- * @param string name Item's Name being viewed
- * @param string category Category page being viewed. On an Item's page, this is the item's category
- * @param float price Item's display price, not use in standard Piwik reports, but output in API product reports.
- */
- setEcommerceView: function (sku, name, category, price) {
- if (!isDefined(category) || !category.length) {
- category = "";
- } else if (category instanceof Array) {
- category = JSON2.stringify(category);
- }
- customVariablesPage[5] = ['_pkc', category];
- if (isDefined(price) && String(price).length) {
- customVariablesPage[2] = ['_pkp', price];
- }
- // On a category page, do not track Product name not defined
- if ((!isDefined(sku) || !sku.length)
- && (!isDefined(name) || !name.length)) {
- return;
- }
-
- if (isDefined(sku) && sku.length) {
- customVariablesPage[3] = ['_pks', sku];
- }
- if (!isDefined(name) || !name.length) {
- name = "";
- }
- customVariablesPage[4] = ['_pkn', name];
- },
-
- /**
- * Adds an item (product) that is in the current Cart or in the Ecommerce order.
- * This function is called for every item (product) in the Cart or the Order.
- * The only required parameter is sku.
- *
- * @param string sku (required) Item's SKU Code. This is the unique identifier for the product.
- * @param string name (optional) Item's name
- * @param string name (optional) Item's category, or array of up to 5 categories
- * @param float price (optional) Item's price. If not specified, will default to 0
- * @param float quantity (optional) Item's quantity. If not specified, will default to 1
- */
- addEcommerceItem: function (sku, name, category, price, quantity) {
- if (sku.length) {
- ecommerceItems[sku] = [ sku, name, category, price, quantity ];
- }
- },
-
- /**
- * Tracks an Ecommerce order.
- * If the Ecommerce order contains items (products), you must call first the addEcommerceItem() for each item in the order.
- * All revenues (grandTotal, subTotal, tax, shipping, discount) will be individually summed and reported in Piwik reports.
- * Parameters orderId and grandTotal are required. For others, you can set to false if you don't need to specify them.
- *
- * @param string|int orderId (required) Unique Order ID.
- * This will be used to count this order only once in the event the order page is reloaded several times.
- * orderId must be unique for each transaction, even on different days, or the transaction will not be recorded by Piwik.
- * @param float grandTotal (required) Grand Total revenue of the transaction (including tax, shipping, etc.)
- * @param float subTotal (optional) Sub total amount, typically the sum of items prices for all items in this order (before Tax and Shipping costs are applied)
- * @param float tax (optional) Tax amount for this order
- * @param float shipping (optional) Shipping amount for this order
- * @param float discount (optional) Discounted amount in this order
- */
- trackEcommerceOrder: function (orderId, grandTotal, subTotal, tax, shipping, discount) {
- logEcommerceOrder(orderId, grandTotal, subTotal, tax, shipping, discount);
- },
-
- /**
- * Tracks a Cart Update (add item, remove item, update item).
- * On every Cart update, you must call addEcommerceItem() for each item (product) in the cart, including the items that haven't been updated since the last cart update.
- * Then you can call this function with the Cart grandTotal (typically the sum of all items' prices)
- *
- * @param float grandTotal (required) Items (products) amount in the Cart
- */
- trackEcommerceCartUpdate: function (grandTotal) {
- logEcommerceCartUpdate(grandTotal);
- }
-
- };
- }
-
- /************************************************************
- * Proxy object
- * - this allows the caller to continue push()'ing to _paq
- * after the Tracker has been initialized and loaded
- ************************************************************/
-
- function TrackerProxy() {
- return {
- push: apply
- };
- }
-
- /************************************************************
- * Constructor
- ************************************************************/
-
- // initialize the Piwik singleton
- addEventListener(windowAlias, 'beforeunload', beforeUnloadHandler, false);
- addReadyListener();
-
- Date.prototype.getTimeAlias = Date.prototype.getTime;
-
- asyncTracker = new Tracker();
-
- for (i = 0; i < _paq.length; i++) {
- apply(_paq[i]);
- }
-
- // replace initialization array with proxy object
- _paq = new TrackerProxy();
-
- /************************************************************
- * Public data and methods
- ************************************************************/
-
- return {
- /**
- * Add plugin
- *
- * @param string pluginName
- * @param Object pluginObj
- */
- addPlugin: function (pluginName, pluginObj) {
- plugins[pluginName] = pluginObj;
- },
-
- /**
- * Get Tracker (factory method)
- *
- * @param string piwikUrl
- * @param int|string siteId
- * @return Tracker
- */
- getTracker: function (piwikUrl, siteId) {
- return new Tracker(piwikUrl, siteId);
- },
-
- /**
- * Get internal asynchronous tracker object
- *
- * @return Tracker
- */
- getAsyncTracker: function () {
- return asyncTracker;
- }
- };
- }()),
-
- /************************************************************
- * Deprecated functionality below
- * - for legacy piwik.js compatibility
- ************************************************************/
-
- /*
- * Piwik globals
- *
- * var piwik_install_tracker, piwik_tracker_pause, piwik_download_extensions, piwik_hosts_alias, piwik_ignore_classes;
- */
-
- piwik_track,
-
- /**
- * Track page visit
- *
- * @param string documentTitle
- * @param int|string siteId
- * @param string piwikUrl
- * @param mixed customData
- */
- piwik_log = function (documentTitle, siteId, piwikUrl, customData) {
- "use strict";
-
- function getOption(optionName) {
- try {
- return eval('piwik_' + optionName);
- } catch (e) { }
-
- return; // undefined
- }
-
- // instantiate the tracker
- var option,
- piwikTracker = Piwik.getTracker(piwikUrl, siteId);
-
- // initialize tracker
- piwikTracker.setDocumentTitle(documentTitle);
- piwikTracker.setCustomData(customData);
-
- // handle Piwik globals
- option = getOption('tracker_pause');
- if (option) {
- piwikTracker.setLinkTrackingTimer(option);
- }
- option = getOption('download_extensions');
- if (option) {
- piwikTracker.setDownloadExtensions(option);
- }
- option = getOption('hosts_alias');
- if (option) {
- piwikTracker.setDomains(option);
- }
- option = getOption('ignore_classes');
- if (option) {
- piwikTracker.setIgnoreClasses(option);
- }
-
- // track this page view
- piwikTracker.trackPageView();
-
- // default is to install the link tracker
- if (getOption('install_tracker')) {
-
- /**
- * Track click manually (function is defined below)
- *
- * @param string sourceUrl
- * @param int|string siteId
- * @param string piwikUrl
- * @param string linkType
- */
- piwik_track = function (sourceUrl, siteId, piwikUrl, linkType) {
- piwikTracker.setSiteId(siteId);
- piwikTracker.setTrackerUrl(piwikUrl);
- piwikTracker.trackLink(sourceUrl, linkType);
- };
-
- // set-up link tracking
- piwikTracker.enableLinkTracking();
- }
- };
+ /**
+ * Get visitor ID (from first party cookie)
+ *
+ * @return string Visitor ID in hexits (or null, if not yet known)
+ */
+ getVisitorId: function () {
+ return (loadVisitorIdCookie())[1];
+ },
+
+ /**
+ * Get the visitor information (from first party cookie)
+ *
+ * @return array
+ */
+ getVisitorInfo: function () {
+ return loadVisitorIdCookie();
+ },
+
+ /**
+ * Get the Attribution information, which is an array that contains
+ * the Referrer used to reach the site as well as the campaign name and keyword
+ * It is useful only when used in conjunction with Tracker API function setAttributionInfo()
+ * To access specific data point, you should use the other functions getAttributionReferrer* and getAttributionCampaign*
+ *
+ * @return array Attribution array, Example use:
+ * 1) Call JSON2.stringify(piwikTracker.getAttributionInfo())
+ * 2) Pass this json encoded string to the Tracking API (php or java client): setAttributionInfo()
+ */
+ getAttributionInfo: function () {
+ return loadReferrerAttributionCookie();
+ },
+
+ /**
+ * Get the Campaign name that was parsed from the landing page URL when the visitor
+ * landed on the site originally
+ *
+ * @return string
+ */
+ getAttributionCampaignName: function () {
+ return loadReferrerAttributionCookie()[0];
+ },
+
+ /**
+ * Get the Campaign keyword that was parsed from the landing page URL when the visitor
+ * landed on the site originally
+ *
+ * @return string
+ */
+ getAttributionCampaignKeyword: function () {
+ return loadReferrerAttributionCookie()[1];
+ },
+
+ /**
+ * Get the time at which the referrer (used for Goal Attribution) was detected
+ *
+ * @return int Timestamp or 0 if no referrer currently set
+ */
+ getAttributionReferrerTimestamp: function () {
+ return loadReferrerAttributionCookie()[2];
+ },
+
+ /**
+ * Get the full referrer URL that will be used for Goal Attribution
+ *
+ * @return string Raw URL, or empty string '' if no referrer currently set
+ */
+ getAttributionReferrerUrl: function () {
+ return loadReferrerAttributionCookie()[3];
+ },
+
+ /**
+ * Specify the Piwik server URL
+ *
+ * @param string trackerUrl
+ */
+ setTrackerUrl: function (trackerUrl) {
+ configTrackerUrl = trackerUrl;
+ },
+
+ /**
+ * Specify the site ID
+ *
+ * @param int|string siteId
+ */
+ setSiteId: function (siteId) {
+ configTrackerSiteId = siteId;
+ },
+
+ /**
+ * Pass custom data to the server
+ *
+ * Examples:
+ * tracker.setCustomData(object);
+ * tracker.setCustomData(key, value);
+ *
+ * @param mixed key_or_obj
+ * @param mixed opt_value
+ */
+ setCustomData: function (key_or_obj, opt_value) {
+ if (isObject(key_or_obj)) {
+ configCustomData = key_or_obj;
+ } else {
+ if (!configCustomData) {
+ configCustomData = [];
+ }
+ configCustomData[key_or_obj] = opt_value;
+ }
+ },
+
+ /**
+ * Get custom data
+ *
+ * @return mixed
+ */
+ getCustomData: function () {
+ return configCustomData;
+ },
+
+ /**
+ * Set custom variable within this visit
+ *
+ * @param int index
+ * @param string name
+ * @param string value
+ * @param string scope Scope of Custom Variable:
+ * - "visit" will store the name/value in the visit and will persist it in the cookie for the duration of the visit,
+ * - "page" will store the name/value in the page view.
+ */
+ setCustomVariable: function (index, name, value, scope) {
+ var toRecord;
+ if (!isDefined(scope)) {
+ scope = 'visit';
+ }
+ if (index > 0) {
+ name = isDefined(name) && !isString(name) ? String(name) : name;
+ value = isDefined(value) && !isString(value) ? String(value) : value;
+ toRecord = [name.slice(0, customVariableMaximumLength), value.slice(0, customVariableMaximumLength)];
+ if (scope === 'visit' || scope === 2) { /* GA compatibility/misuse */
+ loadCustomVariables();
+ customVariables[index] = toRecord;
+ } else if (scope === 'page' || scope === 3) { /* GA compatibility/misuse */
+ customVariablesPage[index] = toRecord;
+ }
+ }
+ },
+
+ /**
+ * Get custom variable
+ *
+ * @param int index
+ * @param string scope Scope of Custom Variable: "visit" or "page"
+ */
+ getCustomVariable: function (index, scope) {
+ var cvar;
+ if (!isDefined(scope)) {
+ scope = "visit";
+ }
+ if (scope === "page" || scope === 3) {
+ cvar = customVariablesPage[index];
+ } else if (scope === "visit" || scope === 2) {
+ loadCustomVariables();
+ cvar = customVariables[index];
+ }
+ if (!isDefined(cvar)
+ || (cvar && cvar[0] === '')) {
+ return false;
+ }
+ return cvar;
+ },
+
+ /**
+ * Delete custom variable
+ *
+ * @param int index
+ */
+ deleteCustomVariable: function (index, scope) {
+ // Only delete if it was there already
+ if (this.getCustomVariable(index, scope)) {
+ this.setCustomVariable(index, '', '', scope);
+ }
+ },
+
+ /**
+ * Set delay for link tracking (in milliseconds)
+ *
+ * @param int delay
+ */
+ setLinkTrackingTimer: function (delay) {
+ configTrackerPause = delay;
+ },
+
+ /**
+ * Set list of file extensions to be recognized as downloads
+ *
+ * @param string extensions
+ */
+ setDownloadExtensions: function (extensions) {
+ configDownloadExtensions = extensions;
+ },
+
+ /**
+ * Specify additional file extensions to be recognized as downloads
+ *
+ * @param string extensions
+ */
+ addDownloadExtensions: function (extensions) {
+ configDownloadExtensions += '|' + extensions;
+ },
+
+ /**
+ * Set array of domains to be treated as local
+ *
+ * @param string|array hostsAlias
+ */
+ setDomains: function (hostsAlias) {
+ configHostsAlias = isString(hostsAlias) ? [hostsAlias] : hostsAlias;
+ configHostsAlias.push(domainAlias);
+ },
+
+ /**
+ * Set array of classes to be ignored if present in link
+ *
+ * @param string|array ignoreClasses
+ */
+ setIgnoreClasses: function (ignoreClasses) {
+ configIgnoreClasses = isString(ignoreClasses) ? [ignoreClasses] : ignoreClasses;
+ },
+
+ /**
+ * Set request method
+ *
+ * @param string method GET or POST; default is GET
+ */
+ setRequestMethod: function (method) {
+ configRequestMethod = method || 'GET';
+ },
+
+ /**
+ * Override referrer
+ *
+ * @param string url
+ */
+ setReferrerUrl: function (url) {
+ configReferrerUrl = url;
+ },
+
+ /**
+ * Override url
+ *
+ * @param string url
+ */
+ setCustomUrl: function (url) {
+ configCustomUrl = resolveRelativeReference(locationHrefAlias, url);
+ },
+
+ /**
+ * Override document.title
+ *
+ * @param string title
+ */
+ setDocumentTitle: function (title) {
+ configTitle = title;
+ },
+
+ /**
+ * Set array of classes to be treated as downloads
+ *
+ * @param string|array downloadClasses
+ */
+ setDownloadClasses: function (downloadClasses) {
+ configDownloadClasses = isString(downloadClasses) ? [downloadClasses] : downloadClasses;
+ },
+
+ /**
+ * Set array of classes to be treated as outlinks
+ *
+ * @param string|array linkClasses
+ */
+ setLinkClasses: function (linkClasses) {
+ configLinkClasses = isString(linkClasses) ? [linkClasses] : linkClasses;
+ },
+
+ /**
+ * Set array of campaign name parameters
+ *
+ * @see http://piwik.org/faq/how-to/#faq_120
+ * @param string|array campaignNames
+ */
+ setCampaignNameKey: function (campaignNames) {
+ configCampaignNameParameters = isString(campaignNames) ? [campaignNames] : campaignNames;
+ },
+
+ /**
+ * Set array of campaign keyword parameters
+ *
+ * @see http://piwik.org/faq/how-to/#faq_120
+ * @param string|array campaignKeywords
+ */
+ setCampaignKeywordKey: function (campaignKeywords) {
+ configCampaignKeywordParameters = isString(campaignKeywords) ? [campaignKeywords] : campaignKeywords;
+ },
+
+ /**
+ * Strip hash tag (or anchor) from URL
+ *
+ * @param bool enableFilter
+ */
+ discardHashTag: function (enableFilter) {
+ configDiscardHashTag = enableFilter;
+ },
+
+ /**
+ * Set first-party cookie name prefix
+ *
+ * @param string cookieNamePrefix
+ */
+ setCookieNamePrefix: function (cookieNamePrefix) {
+ configCookieNamePrefix = cookieNamePrefix;
+ // Re-init the Custom Variables cookie
+ customVariables = getCustomVariablesFromCookie();
+ },
+
+ /**
+ * Set first-party cookie domain
+ *
+ * @param string domain
+ */
+ setCookieDomain: function (domain) {
+ configCookieDomain = domainFixup(domain);
+ updateDomainHash();
+ },
+
+ /**
+ * Set first-party cookie path
+ *
+ * @param string domain
+ */
+ setCookiePath: function (path) {
+ configCookiePath = path;
+ updateDomainHash();
+ },
+
+ /**
+ * Set visitor cookie timeout (in seconds)
+ *
+ * @param int timeout
+ */
+ setVisitorCookieTimeout: function (timeout) {
+ configVisitorCookieTimeout = timeout * 1000;
+ },
+
+ /**
+ * Set session cookie timeout (in seconds)
+ *
+ * @param int timeout
+ */
+ setSessionCookieTimeout: function (timeout) {
+ configSessionCookieTimeout = timeout * 1000;
+ },
+
+ /**
+ * Set referral cookie timeout (in seconds)
+ *
+ * @param int timeout
+ */
+ setReferralCookieTimeout: function (timeout) {
+ configReferralCookieTimeout = timeout * 1000;
+ },
+
+ /**
+ * Set conversion attribution to first referrer and campaign
+ *
+ * @param bool if true, use first referrer (and first campaign)
+ * if false, use the last referrer (or campaign)
+ */
+ setConversionAttributionFirstReferrer: function (enable) {
+ configConversionAttributionFirstReferrer = enable;
+ },
+
+ /**
+ * Disables all cookies from being set
+ *
+ * Existing cookies will be deleted on the next call to track
+ */
+ disableCookies: function () {
+ configCookiesDisabled = true;
+ browserFeatures.cookie = '0';
+ },
+
+ /**
+ * Handle do-not-track requests
+ *
+ * @param bool enable If true, don't track if user agent sends 'do-not-track' header
+ */
+ setDoNotTrack: function (enable) {
+ var dnt = navigatorAlias.doNotTrack || navigatorAlias.msDoNotTrack;
+ configDoNotTrack = enable && (dnt === 'yes' || dnt === '1');
+
+ // do not track also disables cookies and deletes existing cookies
+ if (configDoNotTrack) {
+ this.disableCookies();
+ }
+ },
+
+ /**
+ * Add click listener to a specific link element.
+ * When clicked, Piwik will log the click automatically.
+ *
+ * @param DOMElement element
+ * @param bool enable If true, use pseudo click-handler (mousedown+mouseup)
+ */
+ addListener: function (element, enable) {
+ addClickListener(element, enable);
+ },
+
+ /**
+ * Install link tracker
+ *
+ * The default behaviour is to use actual click events. However, some browsers
+ * (e.g., Firefox, Opera, and Konqueror) don't generate click events for the middle mouse button.
+ *
+ * To capture more "clicks", the pseudo click-handler uses mousedown + mouseup events.
+ * This is not industry standard and is vulnerable to false positives (e.g., drag events).
+ *
+ * There is a Safari/Chrome/Webkit bug that prevents tracking requests from being sent
+ * by either click handler. The workaround is to set a target attribute (which can't
+ * be "_self", "_top", or "_parent").
+ *
+ * @see https://bugs.webkit.org/show_bug.cgi?id=54783
+ *
+ * @param bool enable If true, use pseudo click-handler (mousedown+mouseup)
+ */
+ enableLinkTracking: function (enable) {
+ if (hasLoaded) {
+ // the load event has already fired, add the click listeners now
+ addClickListeners(enable);
+ } else {
+ // defer until page has loaded
+ registeredOnLoadHandlers.push(function () {
+ addClickListeners(enable);
+ });
+ }
+ },
+
+ /**
+ * Set heartbeat (in seconds)
+ *
+ * @param int minimumVisitLength
+ * @param int heartBeatDelay
+ */
+ setHeartBeatTimer: function (minimumVisitLength, heartBeatDelay) {
+ var now = new Date();
+
+ configMinimumVisitTime = now.getTime() + minimumVisitLength * 1000;
+ configHeartBeatTimer = heartBeatDelay * 1000;
+ },
+
+ /**
+ * Frame buster
+ */
+ killFrame: function () {
+ if (windowAlias.location !== windowAlias.top.location) {
+ windowAlias.top.location = windowAlias.location;
+ }
+ },
+
+ /**
+ * Redirect if browsing offline (aka file: buster)
+ *
+ * @param string url Redirect to this URL
+ */
+ redirectFile: function (url) {
+ if (windowAlias.location.protocol === 'file:') {
+ windowAlias.location = url;
+ }
+ },
+
+ /**
+ * Count sites in pre-rendered state
+ *
+ * @param bool enable If true, track when in pre-rendered state
+ */
+ setCountPreRendered: function (enable) {
+ configCountPreRendered = enable;
+ },
+
+ /**
+ * Trigger a goal
+ *
+ * @param int|string idGoal
+ * @param int|float customRevenue
+ * @param mixed customData
+ */
+ trackGoal: function (idGoal, customRevenue, customData) {
+ trackCallback(function () {
+ logGoal(idGoal, customRevenue, customData);
+ });
+ },
+
+ /**
+ * Manually log a click from your own code
+ *
+ * @param string sourceUrl
+ * @param string linkType
+ * @param mixed customData
+ */
+ trackLink: function (sourceUrl, linkType, customData) {
+ trackCallback(function () {
+ logLink(sourceUrl, linkType, customData);
+ });
+ },
+
+ /**
+ * Log visit to this page
+ *
+ * @param string customTitle
+ * @param mixed customData
+ */
+ trackPageView: function (customTitle, customData) {
+ trackCallback(function () {
+ logPageView(customTitle, customData);
+ });
+ },
+
+
+ /**
+ * Used to record that the current page view is an item (product) page view, or a Ecommerce Category page view.
+ * This must be called before trackPageView() on the product/category page.
+ * It will set 3 custom variables of scope "page" with the SKU, Name and Category for this page view.
+ * Note: Custom Variables of scope "page" slots 3, 4 and 5 will be used.
+ *
+ * On a category page, you can set the parameter category, and set the other parameters to empty string or false
+ *
+ * Tracking Product/Category page views will allow Piwik to report on Product & Categories
+ * conversion rates (Conversion rate = Ecommerce orders containing this product or category / Visits to the product or category)
+ *
+ * @param string sku Item's SKU code being viewed
+ * @param string name Item's Name being viewed
+ * @param string category Category page being viewed. On an Item's page, this is the item's category
+ * @param float price Item's display price, not use in standard Piwik reports, but output in API product reports.
+ */
+ setEcommerceView: function (sku, name, category, price) {
+ if (!isDefined(category) || !category.length) {
+ category = "";
+ } else if (category instanceof Array) {
+ category = JSON2.stringify(category);
+ }
+ customVariablesPage[5] = ['_pkc', category];
+ if (isDefined(price) && String(price).length) {
+ customVariablesPage[2] = ['_pkp', price];
+ }
+ // On a category page, do not track Product name not defined
+ if ((!isDefined(sku) || !sku.length)
+ && (!isDefined(name) || !name.length)) {
+ return;
+ }
+
+ if (isDefined(sku) && sku.length) {
+ customVariablesPage[3] = ['_pks', sku];
+ }
+ if (!isDefined(name) || !name.length) {
+ name = "";
+ }
+ customVariablesPage[4] = ['_pkn', name];
+ },
+
+ /**
+ * Adds an item (product) that is in the current Cart or in the Ecommerce order.
+ * This function is called for every item (product) in the Cart or the Order.
+ * The only required parameter is sku.
+ *
+ * @param string sku (required) Item's SKU Code. This is the unique identifier for the product.
+ * @param string name (optional) Item's name
+ * @param string name (optional) Item's category, or array of up to 5 categories
+ * @param float price (optional) Item's price. If not specified, will default to 0
+ * @param float quantity (optional) Item's quantity. If not specified, will default to 1
+ */
+ addEcommerceItem: function (sku, name, category, price, quantity) {
+ if (sku.length) {
+ ecommerceItems[sku] = [ sku, name, category, price, quantity ];
+ }
+ },
+
+ /**
+ * Tracks an Ecommerce order.
+ * If the Ecommerce order contains items (products), you must call first the addEcommerceItem() for each item in the order.
+ * All revenues (grandTotal, subTotal, tax, shipping, discount) will be individually summed and reported in Piwik reports.
+ * Parameters orderId and grandTotal are required. For others, you can set to false if you don't need to specify them.
+ *
+ * @param string|int orderId (required) Unique Order ID.
+ * This will be used to count this order only once in the event the order page is reloaded several times.
+ * orderId must be unique for each transaction, even on different days, or the transaction will not be recorded by Piwik.
+ * @param float grandTotal (required) Grand Total revenue of the transaction (including tax, shipping, etc.)
+ * @param float subTotal (optional) Sub total amount, typically the sum of items prices for all items in this order (before Tax and Shipping costs are applied)
+ * @param float tax (optional) Tax amount for this order
+ * @param float shipping (optional) Shipping amount for this order
+ * @param float discount (optional) Discounted amount in this order
+ */
+ trackEcommerceOrder: function (orderId, grandTotal, subTotal, tax, shipping, discount) {
+ logEcommerceOrder(orderId, grandTotal, subTotal, tax, shipping, discount);
+ },
+
+ /**
+ * Tracks a Cart Update (add item, remove item, update item).
+ * On every Cart update, you must call addEcommerceItem() for each item (product) in the cart, including the items that haven't been updated since the last cart update.
+ * Then you can call this function with the Cart grandTotal (typically the sum of all items' prices)
+ *
+ * @param float grandTotal (required) Items (products) amount in the Cart
+ */
+ trackEcommerceCartUpdate: function (grandTotal) {
+ logEcommerceCartUpdate(grandTotal);
+ }
+
+ };
+ }
+
+ /************************************************************
+ * Proxy object
+ * - this allows the caller to continue push()'ing to _paq
+ * after the Tracker has been initialized and loaded
+ ************************************************************/
+
+ function TrackerProxy() {
+ return {
+ push: apply
+ };
+ }
+
+ /************************************************************
+ * Constructor
+ ************************************************************/
+
+ // initialize the Piwik singleton
+ addEventListener(windowAlias, 'beforeunload', beforeUnloadHandler, false);
+ addReadyListener();
+
+ Date.prototype.getTimeAlias = Date.prototype.getTime;
+
+ asyncTracker = new Tracker();
+
+ for (i = 0; i < _paq.length; i++) {
+ apply(_paq[i]);
+ }
+
+ // replace initialization array with proxy object
+ _paq = new TrackerProxy();
+
+ /************************************************************
+ * Public data and methods
+ ************************************************************/
+
+ return {
+ /**
+ * Add plugin
+ *
+ * @param string pluginName
+ * @param Object pluginObj
+ */
+ addPlugin: function (pluginName, pluginObj) {
+ plugins[pluginName] = pluginObj;
+ },
+
+ /**
+ * Get Tracker (factory method)
+ *
+ * @param string piwikUrl
+ * @param int|string siteId
+ * @return Tracker
+ */
+ getTracker: function (piwikUrl, siteId) {
+ return new Tracker(piwikUrl, siteId);
+ },
+
+ /**
+ * Get internal asynchronous tracker object
+ *
+ * @return Tracker
+ */
+ getAsyncTracker: function () {
+ return asyncTracker;
+ }
+ };
+ }()),
+
+ /************************************************************
+ * Deprecated functionality below
+ * - for legacy piwik.js compatibility
+ ************************************************************/
+
+ /*
+ * Piwik globals
+ *
+ * var piwik_install_tracker, piwik_tracker_pause, piwik_download_extensions, piwik_hosts_alias, piwik_ignore_classes;
+ */
+
+ piwik_track,
+
+ /**
+ * Track page visit
+ *
+ * @param string documentTitle
+ * @param int|string siteId
+ * @param string piwikUrl
+ * @param mixed customData
+ */
+ piwik_log = function (documentTitle, siteId, piwikUrl, customData) {
+ "use strict";
+
+ function getOption(optionName) {
+ try {
+ return eval('piwik_' + optionName);
+ } catch (e) { }
+
+ return; // undefined
+ }
+
+ // instantiate the tracker
+ var option,
+ piwikTracker = Piwik.getTracker(piwikUrl, siteId);
+
+ // initialize tracker
+ piwikTracker.setDocumentTitle(documentTitle);
+ piwikTracker.setCustomData(customData);
+
+ // handle Piwik globals
+ option = getOption('tracker_pause');
+ if (option) {
+ piwikTracker.setLinkTrackingTimer(option);
+ }
+ option = getOption('download_extensions');
+ if (option) {
+ piwikTracker.setDownloadExtensions(option);
+ }
+ option = getOption('hosts_alias');
+ if (option) {
+ piwikTracker.setDomains(option);
+ }
+ option = getOption('ignore_classes');
+ if (option) {
+ piwikTracker.setIgnoreClasses(option);
+ }
+
+ // track this page view
+ piwikTracker.trackPageView();
+
+ // default is to install the link tracker
+ if (getOption('install_tracker')) {
+
+ /**
+ * Track click manually (function is defined below)
+ *
+ * @param string sourceUrl
+ * @param int|string siteId
+ * @param string piwikUrl
+ * @param string linkType
+ */
+ piwik_track = function (sourceUrl, siteId, piwikUrl, linkType) {
+ piwikTracker.setSiteId(siteId);
+ piwikTracker.setTrackerUrl(piwikUrl);
+ piwikTracker.trackLink(sourceUrl, linkType);
+ };
+
+ // set-up link tracking
+ piwikTracker.enableLinkTracking();
+ }
+ };