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
diff options
context:
space:
mode:
Diffstat (limited to 'js/piwik.js')
-rw-r--r--js/piwik.js2673
1 files changed, 1490 insertions, 1183 deletions
diff --git a/js/piwik.js b/js/piwik.js
index aa3d2f8921..9a135e5edc 100644
--- a/js/piwik.js
+++ b/js/piwik.js
@@ -957,7 +957,7 @@ if (typeof JSON2 !== 'object' && typeof window.JSON === 'object' && window.JSON.
/*global unescape */
/*global ActiveXObject */
/*members Piwik, encodeURIComponent, decodeURIComponent, getElementsByTagName,
- shift, unshift, piwikAsyncInit, frameElement, self, hasFocus,
+ shift, unshift, piwikAsyncInit, piwikPluginAsyncInit, frameElement, self, hasFocus,
createElement, appendChild, characterSet, charset, all,
addEventListener, attachEvent, removeEventListener, detachEvent, disableCookies,
cookie, domain, readyState, documentElement, doScroll, title, text,
@@ -972,9 +972,9 @@ if (typeof JSON2 !== 'object' && typeof window.JSON === 'object' && window.JSON.
onload, src,
min, round, random,
exec,
- res, width, height, devicePixelRatio,
+ res, width, height,
pdf, qt, realp, wma, dir, fla, java, gears, ag,
- hook, getHook, getVisitorId, getVisitorInfo, setUserId, getUserId, setSiteId, getSiteId, setTrackerUrl, getTrackerUrl, appendToTrackingUrl, getRequest, addPlugin,
+ initialized, hook, getHook, getVisitorId, getVisitorInfo, setUserId, getUserId, setSiteId, getSiteId, setTrackerUrl, getTrackerUrl, appendToTrackingUrl, getRequest, addPlugin,
getAttributionInfo, getAttributionCampaignName, getAttributionCampaignKeyword,
getAttributionReferrerTimestamp, getAttributionReferrerUrl,
setCustomData, getCustomData,
@@ -993,7 +993,7 @@ if (typeof JSON2 !== 'object' && typeof window.JSON === 'object' && window.JSON.
doNotTrack, setDoNotTrack, msDoNotTrack, getValuesFromVisitorIdCookie,
addListener, enableLinkTracking, enableJSErrorTracking, setLinkTrackingTimer,
enableHeartBeatTimer, disableHeartBeatTimer, killFrame, redirectFile, setCountPreRendered,
- trackGoal, trackLink, trackPageView, trackSiteSearch, trackEvent,
+ trackGoal, trackLink, trackPageView, trackRequest, trackSiteSearch, trackEvent,
setEcommerceView, addEcommerceItem, trackEcommerceOrder, trackEcommerceCartUpdate,
deleteCookie, deleteCookies, offsetTop, offsetLeft, offsetHeight, offsetWidth, nodeType, defaultView,
innerHTML, scrollLeft, scrollTop, currentStyle, getComputedStyle, querySelectorAll, splice,
@@ -1025,12 +1025,12 @@ if (typeof JSON2 !== 'object' && typeof window.JSON === 'object' && window.JSON.
newVisitor, uuid, createTs, visitCount, currentVisitTs, lastVisitTs, lastEcommerceOrderTs,
"", "\b", "\t", "\n", "\f", "\r", "\"", "\\", apply, call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, lastIndex, length, parse, prototype, push, replace,
- sort, slice, stringify, test, toJSON, toString, valueOf, objectToJSON
+ sort, slice, stringify, test, toJSON, toString, valueOf, objectToJSON, addTracker, removeAllAsyncTrackersButFirst
*/
/*global _paq:true */
/*members push */
/*global Piwik:true */
-/*members addPlugin, getTracker, getAsyncTracker */
+/*members addPlugin, getTracker, getAsyncTracker, getAsyncTrackers, addTracker, trigger, on, off */
/*global Piwik_Overlay_Client */
/*global AnalyticsTracker:true */
/*members initialize */
@@ -1059,6 +1059,8 @@ if (typeof window.Piwik !== 'object') {
/* plugins */
plugins = {},
+ eventHandlers = {},
+
/* alias frequently used globals for added minification */
documentAlias = document,
navigatorAlias = navigator,
@@ -1078,7 +1080,7 @@ if (typeof window.Piwik !== 'object') {
urldecode = unescape,
/* asynchronous tracker */
- asyncTracker,
+ asyncTrackers = [],
/* iterator */
iterator,
@@ -1156,6 +1158,17 @@ if (typeof window.Piwik !== 'object') {
return isEmpty;
}
+ /**
+ * Logs an error in the console.
+ * Note: it does not generate a JavaScript error, so make sure to also generate an error if needed.
+ * @param message
+ */
+ function logConsoleError(message) {
+ if (console !== undefined && console && console.error) {
+ console.error(message);
+ }
+ }
+
/*
* apply wrapper
*
@@ -1165,17 +1178,62 @@ if (typeof window.Piwik !== 'object') {
* [ functionObject, optional_parameters ]
*/
function apply() {
- var i, f, parameterArray;
+ var i, j, 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);
+ for (j = 0; j < asyncTrackers.length; j++) {
+ if (isString(f)) {
+ var context = asyncTrackers[j];
+ var fParts;
+
+ var isStaticPluginCall = f.indexOf('::') > 0;
+ if (isStaticPluginCall) {
+ fParts = f.split('::');
+ context = fParts[0];
+ f = fParts[1];
+
+ if ('object' === typeof Piwik[context] && 'function' === typeof Piwik[context][f]) {
+ Piwik[context][f].apply(Piwik[context], parameterArray);
+ }
+
+ return;
+ }
+
+ var isPluginTrackerCall = f.indexOf('.') > 0;
+
+ if (isPluginTrackerCall) {
+ fParts = f.split('.');
+ context = context[fParts[0]];
+ f = fParts[1];
+ }
+
+ if (context[f]) {
+ context[f].apply(context, parameterArray);
+ } else {
+ var message = 'The method \'' + f + '\' was not found in "_paq" variable. Please have a look at the Piwik tracker documentation: http://developer.piwik.org/api-reference/tracking-javascript';
+ logConsoleError(message);
+ if (!isPluginTrackerCall) {
+ throw new TypeError(message);
+ }
+ }
+
+ if (f === 'addTracker') {
+ // addTracker adds an entry to asyncTrackers and would otherwise result in an endless loop
+ break;
+ }
+
+ if (f === 'setTrackerUrl' || f === 'setSiteId') {
+ // these two methods should be only executed on the first tracker
+ break;
+ }
+ } else {
+ f.apply(asyncTrackers[j], parameterArray);
+ }
}
+
}
}
@@ -1202,14 +1260,17 @@ if (typeof window.Piwik !== 'object') {
function executePluginMethod(methodName, callback) {
var result = '',
i,
- pluginMethod;
+ pluginMethod, value;
for (i in plugins) {
if (Object.prototype.hasOwnProperty.call(plugins, i)) {
pluginMethod = plugins[i][methodName];
if (isFunction(pluginMethod)) {
- result += pluginMethod(callback);
+ value = pluginMethod(callback);
+ if (value) {
+ result += value;
+ }
}
}
}
@@ -1632,6 +1693,11 @@ if (typeof window.Piwik !== 'object') {
return -1;
}
+ function stringStartsWith(str, prefix) {
+ str = String(str);
+ return str.lastIndexOf(prefix, 0) === 0;
+ }
+
function stringEndsWith(str, suffix) {
str = String(str);
return str.indexOf(suffix, str.length - suffix.length) !== -1;
@@ -2692,13 +2758,24 @@ if (typeof window.Piwik !== 'object') {
}
function isInsideAnIframe () {
- if (isDefined(windowAlias.frameElement)) {
- return (windowAlias.frameElement && String(windowAlias.frameElement.nodeName).toLowerCase() === 'iframe');
+ var frameElement;
+
+ try {
+ // If the parent window has another origin, then accessing frameElement
+ // throws an Error in IE. see issue #10105.
+ frameElement = windowAlias.frameElement;
+ } catch(e) {
+ // When there was an Error, then we know we are inside an iframe.
+ return true;
+ }
+
+ if (isDefined(frameElement)) {
+ return (frameElement && String(frameElement.nodeName).toLowerCase() === 'iframe') ? true : false;
}
try {
return windowAlias.self !== windowAlias.top;
- } catch (e) {
+ } catch (e2) {
return true;
}
}
@@ -2768,7 +2845,7 @@ if (typeof window.Piwik !== 'object') {
configCustomUrl,
// Document title
- configTitle = documentAlias.title,
+ configTitle = '',
// Extensions to be treated as download links
configDownloadExtensions = ['7z','aac','apk','arc','arj','asf','asx','avi','azw3','bin','csv','deb','dmg','doc','docx','epub','exe','flv','gif','gz','gzip','hqx','ibooks','jar','jpg','jpeg','js','mobi','mp2','mp3','mp4','mpg','mpeg','mov','movie','msi','msp','odb','odf','odg','ods','odt','ogg','ogv','pdf','phps','png','ppt','pptx','qt','qtm','ra','ram','rar','rpm','sea','sit','tar','tbz','tbz2','bz','bz2','tgz','torrent','txt','wav','wma','wmv','wpd','xls','xlsx','xml','z','zip'],
@@ -2820,7 +2897,7 @@ if (typeof window.Piwik !== 'object') {
// Default is user agent defined.
configCookiePath,
- // Cookies are disabled
+ // First-party cookies are disabled
configCookiesDisabled = false,
// Do Not Track
@@ -2910,6 +2987,13 @@ if (typeof window.Piwik !== 'object') {
// Domain hash value
domainHash;
+ // Document title
+ try {
+ configTitle = documentAlias.title;
+ } catch(e) {
+ configTitle = '';
+ }
+
/*
* Set cookie value
*/
@@ -3029,10 +3113,17 @@ if (typeof window.Piwik !== 'object') {
function getPathName(url) {
var parser = document.createElement('a');
if (url.indexOf('//') !== 0 && url.indexOf('http') !== 0) {
+ if (url.indexOf('*') === 0) {
+ url = url.substr(1);
+ }
+ if (url.indexOf('.') === 0) {
+ url = url.substr(1);
+ }
url = 'http://' + url;
}
parser.href = content.toAbsoluteUrl(url);
+
if (parser.pathname) {
return parser.pathname;
}
@@ -3042,7 +3133,15 @@ if (typeof window.Piwik !== 'object') {
function isSitePath (path, pathAlias)
{
- var matchesAnyPath = (!pathAlias || pathAlias === '/' || pathAlias === '/*');
+ if(!stringStartsWith(pathAlias, '/')) {
+ pathAlias = '/' + pathAlias;
+ }
+
+ if(!stringStartsWith(path, '/')) {
+ path = '/' + path;
+ }
+
+ var matchesAnyPath = (pathAlias === '/' || pathAlias === '/*');
if (matchesAnyPath) {
return true;
@@ -3052,10 +3151,6 @@ if (typeof window.Piwik !== 'object') {
return true;
}
- if (!path) {
- return false;
- }
-
pathAlias = String(pathAlias).toLowerCase();
path = String(path).toLowerCase();
@@ -3163,6 +3258,8 @@ if (typeof window.Piwik !== 'object') {
iterator = 0; // To avoid JSLint warning of empty block
if (typeof callback === 'function') { callback(); }
};
+ // make sure to actually load an image so callback gets invoked
+ request = request.replace("send_image=0","send_image=1");
image.src = configTrackerUrl + (configTrackerUrl.indexOf('?') < 0 ? '?' : '&') + request;
}
@@ -3191,7 +3288,7 @@ if (typeof window.Piwik !== 'object') {
if (this.readyState === 4 && !(this.status >= 200 && this.status < 300) && fallbackToGet) {
getImage(request, callback);
} else {
- if (typeof callback === 'function') { callback(); }
+ if (this.readyState === 4 && (typeof callback === 'function')) { callback(); }
}
};
@@ -3859,7 +3956,7 @@ if (typeof window.Piwik !== 'object') {
// custom dimensions
for (i in customDimensions) {
if (Object.prototype.hasOwnProperty.call(customDimensions, i)) {
- var isNotSetYet = (-1 === customDimensionIdsAlreadyHandled.indexOf(i));
+ var isNotSetYet = (-1 === indexOfArray(customDimensionIdsAlreadyHandled, i));
if (isNotSetYet) {
request += '&dimension' + i + '=' + customDimensions[i];
}
@@ -3956,9 +4053,10 @@ if (typeof window.Piwik !== 'object') {
lastEcommerceOrderTs,
now = new Date(),
items = [],
- sku;
+ sku,
+ isEcommerceOrder = String(orderId).length;
- if (String(orderId).length) {
+ if (isEcommerceOrder) {
request += '&ec_id=' + encodeWrapper(orderId);
// Record date of order in the visitor cookie
lastEcommerceOrderTs = Math.round(now.getTime() / 1000);
@@ -4014,6 +4112,10 @@ if (typeof window.Piwik !== 'object') {
}
request = getRequest(request, configCustomData, 'ecommerce', lastEcommerceOrderTs);
sendRequest(request, configTrackerPause);
+
+ if (isEcommerceOrder) {
+ ecommerceItems = {};
+ }
}
function logEcommerceOrder(orderId, grandTotal, subTotal, tax, shipping, discount) {
@@ -4032,10 +4134,10 @@ if (typeof window.Piwik !== 'object') {
/*
* Log the page view / visit
*/
- function logPageView(customTitle, customData) {
+ function logPageView(customTitle, customData, callback) {
var request = getRequest('action_name=' + encodeWrapper(titleFixup(customTitle || configTitle)), customData, 'log');
- sendRequest(request, configTrackerPause);
+ sendRequest(request, configTrackerPause, callback);
}
/*
@@ -4973,8 +5075,7 @@ if (typeof window.Piwik !== 'object') {
java: 'application/x-java-vm',
gears: 'application/x-googlegears',
ag: 'application/x-silverlight'
- },
- devicePixelRatio = windowAlias.devicePixelRatio || 1;
+ };
// detect browser features except IE < 11 (IE 11 user agent is no longer MSIE)
if (!((new RegExp('MSIE')).test(navigatorAlias.userAgent))) {
@@ -5005,8 +5106,8 @@ if (typeof window.Piwik !== 'object') {
browserFeatures.cookie = hasCookies();
}
- var width = parseInt(screenAlias.width, 10) * devicePixelRatio;
- var height = parseInt(screenAlias.height, 10) * devicePixelRatio;
+ var width = parseInt(screenAlias.width, 10);
+ var height = parseInt(screenAlias.height, 10);
browserFeatures.res = parseInt(width, 10) + 'x' + parseInt(height, 10);
}
@@ -5056,1279 +5157,1339 @@ if (typeof window.Piwik !== 'object') {
* Public data and methods
************************************************************/
- return {
+
/*<DEBUG>*/
- /*
- * Test hook accessors
- */
- hook: registeredHooks,
- getHook: function (hookName) {
- return registeredHooks[hookName];
- },
- getQuery: function () {
- return query;
- },
- getContent: function () {
- return content;
- },
+ /*
+ * Test hook accessors
+ */
+ this.hook = registeredHooks;
+ this.getHook = function (hookName) {
+ return registeredHooks[hookName];
+ };
+ this.getQuery = function () {
+ return query;
+ };
+ this.getContent = function () {
+ return content;
+ };
- buildContentImpressionRequest: buildContentImpressionRequest,
- buildContentInteractionRequest: buildContentInteractionRequest,
- buildContentInteractionRequestNode: buildContentInteractionRequestNode,
- buildContentInteractionTrackingRedirectUrl: buildContentInteractionTrackingRedirectUrl,
- getContentImpressionsRequestsFromNodes: getContentImpressionsRequestsFromNodes,
- getCurrentlyVisibleContentImpressionsRequestsIfNotTrackedYet: getCurrentlyVisibleContentImpressionsRequestsIfNotTrackedYet,
- trackCallbackOnLoad: trackCallbackOnLoad,
- trackCallbackOnReady: trackCallbackOnReady,
- buildContentImpressionsRequests: buildContentImpressionsRequests,
- wasContentImpressionAlreadyTracked: wasContentImpressionAlreadyTracked,
- appendContentInteractionToRequestIfPossible: getContentInteractionToRequestIfPossible,
- setupInteractionsTracking: setupInteractionsTracking,
- trackContentImpressionClickInteraction: trackContentImpressionClickInteraction,
- internalIsNodeVisible: isVisible,
- isNodeAuthorizedToTriggerInteraction: isNodeAuthorizedToTriggerInteraction,
- replaceHrefIfInternalLink: replaceHrefIfInternalLink,
- getDomains: function () {
- return configHostsAlias;
- },
- getConfigCookiePath: function () {
- return configCookiePath;
- },
- getConfigDownloadExtensions: function () {
- return configDownloadExtensions;
- },
- enableTrackOnlyVisibleContent: function (checkOnScroll, timeIntervalInMs) {
- return enableTrackOnlyVisibleContent(checkOnScroll, timeIntervalInMs, this);
- },
- clearTrackedContentImpressions: function () {
- trackedContentImpressions = [];
- },
- getTrackedContentImpressions: function () {
- return trackedContentImpressions;
- },
- clearEnableTrackOnlyVisibleContent: function () {
- isTrackOnlyVisibleContentEnabled = false;
- },
- disableLinkTracking: function () {
- linkTrackingInstalled = false;
- linkTrackingEnabled = false;
- },
- getConfigVisitorCookieTimeout: function () {
- return configVisitorCookieTimeout;
- },
- getRemainingVisitorCookieTimeout: getRemainingVisitorCookieTimeout,
+ this.buildContentImpressionRequest = buildContentImpressionRequest;
+ this.buildContentInteractionRequest = buildContentInteractionRequest;
+ this.buildContentInteractionRequestNode = buildContentInteractionRequestNode;
+ this.buildContentInteractionTrackingRedirectUrl = buildContentInteractionTrackingRedirectUrl;
+ this.getContentImpressionsRequestsFromNodes = getContentImpressionsRequestsFromNodes;
+ this.getCurrentlyVisibleContentImpressionsRequestsIfNotTrackedYet = getCurrentlyVisibleContentImpressionsRequestsIfNotTrackedYet;
+ this.trackCallbackOnLoad = trackCallbackOnLoad;
+ this.trackCallbackOnReady = trackCallbackOnReady;
+ this.buildContentImpressionsRequests = buildContentImpressionsRequests;
+ this.wasContentImpressionAlreadyTracked = wasContentImpressionAlreadyTracked;
+ this.appendContentInteractionToRequestIfPossible = getContentInteractionToRequestIfPossible;
+ this.setupInteractionsTracking = setupInteractionsTracking;
+ this.trackContentImpressionClickInteraction = trackContentImpressionClickInteraction;
+ this.internalIsNodeVisible = isVisible;
+ this.isNodeAuthorizedToTriggerInteraction = isNodeAuthorizedToTriggerInteraction;
+ this.replaceHrefIfInternalLink = replaceHrefIfInternalLink;
+ this.getDomains = function () {
+ return configHostsAlias;
+ };
+ this.getConfigCookiePath = function () {
+ return configCookiePath;
+ };
+ this.getConfigDownloadExtensions = function () {
+ return configDownloadExtensions;
+ };
+ this.enableTrackOnlyVisibleContent = function (checkOnScroll, timeIntervalInMs) {
+ return enableTrackOnlyVisibleContent(checkOnScroll, timeIntervalInMs, this);
+ };
+ this.clearTrackedContentImpressions = function () {
+ trackedContentImpressions = [];
+ };
+ this.getTrackedContentImpressions = function () {
+ return trackedContentImpressions;
+ };
+ this.clearEnableTrackOnlyVisibleContent = function () {
+ isTrackOnlyVisibleContentEnabled = false;
+ };
+ this.disableLinkTracking = function () {
+ linkTrackingInstalled = false;
+ linkTrackingEnabled = false;
+ };
+ this.getConfigVisitorCookieTimeout = function () {
+ return configVisitorCookieTimeout;
+ };
+ this.removeAllAsyncTrackersButFirst = function () {
+ var firstTracker = asyncTrackers[0];
+ asyncTrackers = [firstTracker];
+ };
+ this.getRemainingVisitorCookieTimeout = getRemainingVisitorCookieTimeout;
/*</DEBUG>*/
- /**
- * Get visitor ID (from first party cookie)
- *
- * @return string Visitor ID in hexits (or empty string, if not yet known)
- */
- getVisitorId: function () {
- return getValuesFromVisitorIdCookie().uuid;
- },
-
- /**
- * Get the visitor information (from first party cookie)
- *
- * @return array
- */
- getVisitorInfo: function () {
- // Note: in a new method, we could return also return getValuesFromVisitorIdCookie()
- // which returns named parameters rather than returning integer indexed array
- return loadVisitorIdCookie();
- },
+ /**
+ * Get visitor ID (from first party cookie)
+ *
+ * @return string Visitor ID in hexits (or empty string, if not yet known)
+ */
+ this.getVisitorId = function () {
+ return getValuesFromVisitorIdCookie().uuid;
+ };
- /**
- * 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 visitor information (from first party cookie)
+ *
+ * @return array
+ */
+ this.getVisitorInfo = function () {
+ // Note: in a new method, we could return also return getValuesFromVisitorIdCookie()
+ // which returns named parameters rather than returning integer indexed array
+ return loadVisitorIdCookie();
+ };
- /**
- * 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 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()
+ */
+ this.getAttributionInfo = function () {
+ return loadReferrerAttributionCookie();
+ };
- /**
- * 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 Campaign name that was parsed from the landing page URL when the visitor
+ * landed on the site originally
+ *
+ * @return string
+ */
+ this.getAttributionCampaignName = function () {
+ return loadReferrerAttributionCookie()[0];
+ };
- /**
- * 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 Campaign keyword that was parsed from the landing page URL when the visitor
+ * landed on the site originally
+ *
+ * @return string
+ */
+ this.getAttributionCampaignKeyword = function () {
+ return loadReferrerAttributionCookie()[1];
+ };
- /**
- * 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];
- },
+ /**
+ * Get the time at which the referrer (used for Goal Attribution) was detected
+ *
+ * @return int Timestamp or 0 if no referrer currently set
+ */
+ this.getAttributionReferrerTimestamp = function () {
+ return loadReferrerAttributionCookie()[2];
+ };
- /**
- * Specify the Piwik server URL
- *
- * @param string trackerUrl
- */
- setTrackerUrl: function (trackerUrl) {
- configTrackerUrl = trackerUrl;
- },
+ /**
+ * Get the full referrer URL that will be used for Goal Attribution
+ *
+ * @return string Raw URL, or empty string '' if no referrer currently set
+ */
+ this.getAttributionReferrerUrl = function () {
+ return loadReferrerAttributionCookie()[3];
+ };
+ /**
+ * Specify the Piwik server URL
+ *
+ * @param string trackerUrl
+ */
+ this.setTrackerUrl = function (trackerUrl) {
+ configTrackerUrl = trackerUrl;
+ };
- /**
- * Returns the Piwik server URL
- * @returns string
- */
- getTrackerUrl: function () {
- return configTrackerUrl;
- },
+ /**
+ * Returns the Piwik server URL
+ * @returns string
+ */
+ this.getTrackerUrl = function () {
+ return configTrackerUrl;
+ };
+ /**
+ * Adds a new tracker. All sent requests will be also sent to the given siteId and piwikUrl.
+ * If piwikUrl is not set, current url will be used.
+ *
+ * @param null|string piwikUrl If null, will reuse the same tracker URL of the current tracker instance
+ * @param int|string siteId
+ * @return Tracker
+ */
+ this.addTracker = function (piwikUrl, siteId) {
+ if (!siteId) {
+ throw new Error('A siteId must be given to add a new tracker');
+ }
- /**
- * Returns the site ID
- *
- * @returns int
- */
- getSiteId: function() {
- return configTrackerSiteId;
- },
+ if (!isDefined(piwikUrl) || null === piwikUrl) {
+ piwikUrl = this.getTrackerUrl();
+ }
- /**
- * Specify the site ID
- *
- * @param int|string siteId
- */
- setSiteId: function (siteId) {
- setSiteId(siteId);
- },
+ var tracker = new Tracker(piwikUrl, siteId);
- /**
- * Sets a User ID to this user (such as an email address or a username)
- *
- * @param string User ID
- */
- setUserId: function (userId) {
- if(!isDefined(userId) || !userId.length) {
- return;
- }
- configUserId = userId;
- visitorUUID = hash(configUserId).substr(0, 16);
- },
+ asyncTrackers.push(tracker);
- /**
- * Gets the User ID if set.
- *
- * @returns string User ID
- */
- getUserId: function() {
- return configUserId;
- },
+ return tracker;
+ };
- /**
- * 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;
- }
- },
+ /**
+ * Returns the site ID
+ *
+ * @returns int
+ */
+ this.getSiteId = function() {
+ return configTrackerSiteId;
+ };
- /**
- * Get custom data
- *
- * @return mixed
- */
- getCustomData: function () {
- return configCustomData;
- },
+ /**
+ * Specify the site ID
+ *
+ * @param int|string siteId
+ */
+ this.setSiteId = function (siteId) {
+ setSiteId(siteId);
+ };
- /**
- * Configure function with custom request content processing logic.
- * It gets called after request content in form of query parameters string has been prepared and before request content gets sent.
- *
- * Examples:
- * tracker.setCustomRequestProcessing(function(request){
- * var pairs = request.split('&');
- * var result = {};
- * pairs.forEach(function(pair) {
- * pair = pair.split('=');
- * result[pair[0]] = decodeURIComponent(pair[1] || '');
- * });
- * return JSON.stringify(result);
- * });
- *
- * @param function customRequestContentProcessingLogic
- */
- setCustomRequestProcessing: function (customRequestContentProcessingLogic) {
- configCustomRequestContentProcessing = customRequestContentProcessingLogic;
- },
+ /**
+ * Sets a User ID to this user (such as an email address or a username)
+ *
+ * @param string User ID
+ */
+ this.setUserId = function (userId) {
+ if(!isDefined(userId) || !userId.length) {
+ return;
+ }
+ configUserId = userId;
+ visitorUUID = hash(configUserId).substr(0, 16);
+ };
- /**
- * Appends the specified query string to the piwik.php?... Tracking API URL
- *
- * @param string queryString eg. 'lat=140&long=100'
- */
- appendToTrackingUrl: function (queryString) {
- configAppendToTrackingUrl = queryString;
- },
+ /**
+ * Gets the User ID if set.
+ *
+ * @returns string User ID
+ */
+ this.getUserId = function() {
+ return configUserId;
+ };
- /**
- * Returns the query string for the current HTTP Tracking API request.
- * Piwik would prepend the hostname and path to Piwik: http://example.org/piwik/piwik.php?
- * prior to sending the request.
- *
- * @param request eg. "param=value&param2=value2"
- */
- getRequest: function (request) {
- return getRequest(request);
- },
+ /**
+ * Pass custom data to the server
+ *
+ * Examples:
+ * tracker.setCustomData(object);
+ * tracker.setCustomData(key, value);
+ *
+ * @param mixed key_or_obj
+ * @param mixed opt_value
+ */
+ this.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;
+ }
+ };
- /**
- * Add plugin defined by a name and a callback function.
- * The callback function will be called whenever a tracking request is sent.
- * This can be used to append data to the tracking request, or execute other custom logic.
- *
- * @param string pluginName
- * @param Object pluginObj
- */
- addPlugin: function (pluginName, pluginObj) {
- plugins[pluginName] = pluginObj;
- },
+ /**
+ * Get custom data
+ *
+ * @return mixed
+ */
+ this.getCustomData = function () {
+ return configCustomData;
+ };
- /**
- * Set Custom Dimensions. Any set Custom Dimension will be cleared after a tracked pageview. Make
- * sure to set them again if needed.
- *
- * @param int index A Custom Dimension index
- * @param string value
- */
- setCustomDimension: function (customDimensionId, value) {
- customDimensionId = parseInt(customDimensionId, 10);
- if (customDimensionId > 0) {
- if (!isDefined(value)) {
- value = '';
- }
- if (!isString(value)) {
- value = String(value);
- }
- customDimensions[customDimensionId] = value;
- }
- },
+ /**
+ * Configure function with custom request content processing logic.
+ * It gets called after request content in form of query parameters string has been prepared and before request content gets sent.
+ *
+ * Examples:
+ * tracker.setCustomRequestProcessing(function(request){
+ * var pairs = request.split('&');
+ * var result = {};
+ * pairs.forEach(function(pair) {
+ * pair = pair.split('=');
+ * result[pair[0]] = decodeURIComponent(pair[1] || '');
+ * });
+ * return JSON.stringify(result);
+ * });
+ *
+ * @param function customRequestContentProcessingLogic
+ */
+ this.setCustomRequestProcessing = function (customRequestContentProcessingLogic) {
+ configCustomRequestContentProcessing = customRequestContentProcessingLogic;
+ };
- /**
- * Get a stored value for a specific Custom Dimension index.
- *
- * @param int index A Custom Dimension index
- */
- getCustomDimension: function (customDimensionId) {
- customDimensionId = parseInt(customDimensionId, 10);
- if (customDimensionId > 0 && Object.prototype.hasOwnProperty.call(customDimensions, customDimensionId)) {
- return customDimensions[customDimensionId];
- }
- },
+ /**
+ * Appends the specified query string to the piwik.php?... Tracking API URL
+ *
+ * @param string queryString eg. 'lat=140&long=100'
+ */
+ this.appendToTrackingUrl = function (queryString) {
+ configAppendToTrackingUrl = queryString;
+ };
- /**
- * Delete a custom dimension.
- *
- * @param int index Custom dimension Id
- */
- deleteCustomDimension: function (customDimensionId) {
- customDimensionId = parseInt(customDimensionId, 10);
- if (customDimensionId > 0) {
- delete customDimensions[customDimensionId];
- }
- },
+ /**
+ * Returns the query string for the current HTTP Tracking API request.
+ * Piwik would prepend the hostname and path to Piwik: http://example.org/piwik/piwik.php?
+ * prior to sending the request.
+ *
+ * @param request eg. "param=value&param2=value2"
+ */
+ this.getRequest = function (request) {
+ return getRequest(request);
+ };
- /**
- * Set custom variable within this visit
- *
- * @param int index Custom variable slot ID from 1-5
- * @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 next page view tracked.
- * - "event" will store the name/value in the next event tracked.
- */
- setCustomVariable: function (index, name, value, scope) {
- var toRecord;
+ /**
+ * Add plugin defined by a name and a callback function.
+ * The callback function will be called whenever a tracking request is sent.
+ * This can be used to append data to the tracking request, or execute other custom logic.
+ *
+ * @param string pluginName
+ * @param Object pluginObj
+ */
+ this.addPlugin = function (pluginName, pluginObj) {
+ plugins[pluginName] = pluginObj;
+ };
- if (!isDefined(scope)) {
- scope = 'visit';
- }
- if (!isDefined(name)) {
- return;
- }
+ /**
+ * Set Custom Dimensions. Set Custom Dimensions will not be cleared after a tracked pageview and will
+ * be sent along all following tracking requests. It is possible to remove/clear a value via `deleteCustomDimension`.
+ *
+ * @param int index A Custom Dimension index
+ * @param string value
+ */
+ this.setCustomDimension = function (customDimensionId, value) {
+ customDimensionId = parseInt(customDimensionId, 10);
+ if (customDimensionId > 0) {
if (!isDefined(value)) {
- value = "";
+ value = '';
}
- if (index > 0) {
- name = !isString(name) ? String(name) : name;
- value = !isString(value) ? String(value) : value;
- toRecord = [name.slice(0, customVariableMaximumLength), value.slice(0, customVariableMaximumLength)];
- // numeric scope is there for GA compatibility
- if (scope === 'visit' || scope === 2) {
- loadCustomVariables();
- customVariables[index] = toRecord;
- } else if (scope === 'page' || scope === 3) {
- customVariablesPage[index] = toRecord;
- } else if (scope === 'event') { /* GA does not have 'event' scope but we do */
- customVariablesEvent[index] = toRecord;
- }
+ if (!isString(value)) {
+ value = String(value);
}
- },
+ customDimensions[customDimensionId] = value;
+ }
+ };
- /**
- * Get custom variable
- *
- * @param int index Custom variable slot ID from 1-5
- * @param string scope Scope of Custom Variable: "visit" or "page" or "event"
- */
- getCustomVariable: function (index, scope) {
- var cvar;
+ /**
+ * Get a stored value for a specific Custom Dimension index.
+ *
+ * @param int index A Custom Dimension index
+ */
+ this.getCustomDimension = function (customDimensionId) {
+ customDimensionId = parseInt(customDimensionId, 10);
+ if (customDimensionId > 0 && Object.prototype.hasOwnProperty.call(customDimensions, customDimensionId)) {
+ return customDimensions[customDimensionId];
+ }
+ };
- if (!isDefined(scope)) {
- scope = "visit";
- }
+ /**
+ * Delete a custom dimension.
+ *
+ * @param int index Custom dimension Id
+ */
+ this.deleteCustomDimension = function (customDimensionId) {
+ customDimensionId = parseInt(customDimensionId, 10);
+ if (customDimensionId > 0) {
+ delete customDimensions[customDimensionId];
+ }
+ };
- if (scope === "page" || scope === 3) {
- cvar = customVariablesPage[index];
- } else if (scope === "event") {
- cvar = customVariablesEvent[index];
- } else if (scope === "visit" || scope === 2) {
+ /**
+ * Set custom variable within this visit
+ *
+ * @param int index Custom variable slot ID from 1-5
+ * @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 next page view tracked.
+ * - "event" will store the name/value in the next event tracked.
+ */
+ this.setCustomVariable = function (index, name, value, scope) {
+ var toRecord;
+
+ if (!isDefined(scope)) {
+ scope = 'visit';
+ }
+ if (!isDefined(name)) {
+ return;
+ }
+ if (!isDefined(value)) {
+ value = "";
+ }
+ if (index > 0) {
+ name = !isString(name) ? String(name) : name;
+ value = !isString(value) ? String(value) : value;
+ toRecord = [name.slice(0, customVariableMaximumLength), value.slice(0, customVariableMaximumLength)];
+ // numeric scope is there for GA compatibility
+ if (scope === 'visit' || scope === 2) {
loadCustomVariables();
- cvar = customVariables[index];
+ customVariables[index] = toRecord;
+ } else if (scope === 'page' || scope === 3) {
+ customVariablesPage[index] = toRecord;
+ } else if (scope === 'event') { /* GA does not have 'event' scope but we do */
+ customVariablesEvent[index] = toRecord;
}
+ }
+ };
- if (!isDefined(cvar)
- || (cvar && cvar[0] === '')) {
- return false;
- }
+ /**
+ * Get custom variable
+ *
+ * @param int index Custom variable slot ID from 1-5
+ * @param string scope Scope of Custom Variable: "visit" or "page" or "event"
+ */
+ this.getCustomVariable = function (index, scope) {
+ var cvar;
- return cvar;
- },
+ if (!isDefined(scope)) {
+ scope = "visit";
+ }
- /**
- * Delete custom variable
- *
- * @param int index Custom variable slot ID from 1-5
- * @param string scope
- */
- deleteCustomVariable: function (index, scope) {
- // Only delete if it was there already
- if (this.getCustomVariable(index, scope)) {
- this.setCustomVariable(index, '', '', scope);
- }
- },
+ if (scope === "page" || scope === 3) {
+ cvar = customVariablesPage[index];
+ } else if (scope === "event") {
+ cvar = customVariablesEvent[index];
+ } else if (scope === "visit" || scope === 2) {
+ loadCustomVariables();
+ cvar = customVariables[index];
+ }
- /**
- * When called then the Custom Variables of scope "visit" will be stored (persisted) in a first party cookie
- * for the duration of the visit. This is useful if you want to call getCustomVariable later in the visit.
- *
- * By default, Custom Variables of scope "visit" are not stored on the visitor's computer.
- */
- storeCustomVariablesInCookie: function () {
- configStoreCustomVariablesInCookie = true;
- },
+ if (!isDefined(cvar)
+ || (cvar && cvar[0] === '')) {
+ return false;
+ }
- /**
- * Set delay for link tracking (in milliseconds)
- *
- * @param int delay
- */
- setLinkTrackingTimer: function (delay) {
- configTrackerPause = delay;
- },
+ return cvar;
+ };
- /**
- * Set list of file extensions to be recognized as downloads
- *
- * @param string|array extensions
- */
- setDownloadExtensions: function (extensions) {
- if(isString(extensions)) {
- extensions = extensions.split('|');
- }
- configDownloadExtensions = extensions;
- },
+ /**
+ * Delete custom variable
+ *
+ * @param int index Custom variable slot ID from 1-5
+ * @param string scope
+ */
+ this.deleteCustomVariable = function (index, scope) {
+ // Only delete if it was there already
+ if (this.getCustomVariable(index, scope)) {
+ this.setCustomVariable(index, '', '', scope);
+ }
+ };
- /**
- * Specify additional file extensions to be recognized as downloads
- *
- * @param string|array extensions for example 'custom' or ['custom1','custom2','custom3']
- */
- addDownloadExtensions: function (extensions) {
- var i;
- if(isString(extensions)) {
- extensions = extensions.split('|');
- }
- for (i=0; i < extensions.length; i++) {
- configDownloadExtensions.push(extensions[i]);
- }
- },
+ /**
+ * When called then the Custom Variables of scope "visit" will be stored (persisted) in a first party cookie
+ * for the duration of the visit. This is useful if you want to call getCustomVariable later in the visit.
+ *
+ * By default, Custom Variables of scope "visit" are not stored on the visitor's computer.
+ */
+ this.storeCustomVariablesInCookie = function () {
+ configStoreCustomVariablesInCookie = true;
+ };
- /**
- * Removes specified file extensions from the list of recognized downloads
- *
- * @param string|array extensions for example 'custom' or ['custom1','custom2','custom3']
- */
- removeDownloadExtensions: function (extensions) {
- var i, newExtensions = [];
- if(isString(extensions)) {
- extensions = extensions.split('|');
- }
- for (i=0; i < configDownloadExtensions.length; i++) {
- if (indexOfArray(extensions, configDownloadExtensions[i]) === -1) {
- newExtensions.push(configDownloadExtensions[i]);
- }
+ /**
+ * Set delay for link tracking (in milliseconds)
+ *
+ * @param int delay
+ */
+ this.setLinkTrackingTimer = function (delay) {
+ configTrackerPause = delay;
+ };
+
+ /**
+ * Set list of file extensions to be recognized as downloads
+ *
+ * @param string|array extensions
+ */
+ this.setDownloadExtensions = function (extensions) {
+ if(isString(extensions)) {
+ extensions = extensions.split('|');
+ }
+ configDownloadExtensions = extensions;
+ };
+
+ /**
+ * Specify additional file extensions to be recognized as downloads
+ *
+ * @param string|array extensions for example 'custom' or ['custom1','custom2','custom3']
+ */
+ this.addDownloadExtensions = function (extensions) {
+ var i;
+ if(isString(extensions)) {
+ extensions = extensions.split('|');
+ }
+ for (i=0; i < extensions.length; i++) {
+ configDownloadExtensions.push(extensions[i]);
+ }
+ };
+
+ /**
+ * Removes specified file extensions from the list of recognized downloads
+ *
+ * @param string|array extensions for example 'custom' or ['custom1','custom2','custom3']
+ */
+ this.removeDownloadExtensions = function (extensions) {
+ var i, newExtensions = [];
+ if(isString(extensions)) {
+ extensions = extensions.split('|');
+ }
+ for (i=0; i < configDownloadExtensions.length; i++) {
+ if (indexOfArray(extensions, configDownloadExtensions[i]) === -1) {
+ newExtensions.push(configDownloadExtensions[i]);
}
- configDownloadExtensions = newExtensions;
- },
+ }
+ configDownloadExtensions = newExtensions;
+ };
- /**
- * Set array of domains to be treated as local. Also supports path, eg '.piwik.org/subsite1'. In this
- * case all links that don't go to '*.piwik.org/subsite1/ *' would be treated as outlinks.
- * For example a link to 'piwik.org/' or 'piwik.org/subsite2' both would be treated as outlinks.
- *
- * Also supports page wildcard, eg 'piwik.org/index*'. In this case all links
- * that don't go to piwik.org/index* would be treated as outlinks.
- *
- * @param string|array hostsAlias
- */
- setDomains: function (hostsAlias) {
- configHostsAlias = isString(hostsAlias) ? [hostsAlias] : hostsAlias;
-
- var hasDomainAliasAlready = false, i;
- for (i in configHostsAlias) {
- if (Object.prototype.hasOwnProperty.call(configHostsAlias, i)
- && isSameHost(domainAlias, domainFixup(String(configHostsAlias[i])))) {
- hasDomainAliasAlready = true;
- }
+ /**
+ * Set array of domains to be treated as local. Also supports path, eg '.piwik.org/subsite1'. In this
+ * case all links that don't go to '*.piwik.org/subsite1/ *' would be treated as outlinks.
+ * For example a link to 'piwik.org/' or 'piwik.org/subsite2' both would be treated as outlinks.
+ *
+ * Also supports page wildcard, eg 'piwik.org/index*'. In this case all links
+ * that don't go to piwik.org/index* would be treated as outlinks.
+ *
+ * The current domain will be added automatically if no given host alias contains a path and if no host
+ * alias is already given for the current host alias. Say you are on "example.org" and set
+ * "hostAlias = ['example.com', 'example.org/test']" then the current "example.org" domain will not be
+ * added as there is already a more restrictive hostAlias 'example.org/test' given. We also do not add
+ * it automatically if there was any other host specifying any path like
+ * "['example.com', 'example2.com/test']". In this case we would also not add the current
+ * domain "example.org" automatically as the "path" feature is used. As soon as someone uses the path
+ * feature, for Piwik JS Tracker to work correctly in all cases, one needs to specify all hosts
+ * manually.
+ *
+ * @param string|array hostsAlias
+ */
+ this.setDomains = function (hostsAlias) {
+ configHostsAlias = isString(hostsAlias) ? [hostsAlias] : hostsAlias;
+
+ var hasDomainAliasAlready = false, i = 0, alias;
+ for (i; i < configHostsAlias.length; i++) {
+ alias = String(configHostsAlias[i]);
+
+ if (isSameHost(domainAlias, domainFixup(alias))) {
+ hasDomainAliasAlready = true;
+ break;
}
- if (!hasDomainAliasAlready) {
- /**
- * eg if domainAlias = 'piwik.org' and someone set hostsAlias = ['piwik.org/foo'] then we should
- * not add piwik.org as it would increase the allowed scope.
- */
- configHostsAlias.push(domainAlias);
+ var pathName = getPathName(alias);
+ if (pathName && pathName !== '/' && pathName !== '/*') {
+ hasDomainAliasAlready = true;
+ break;
}
- },
+ }
- /**
- * Set array of classes to be ignored if present in link
- *
- * @param string|array ignoreClasses
- */
- setIgnoreClasses: function (ignoreClasses) {
- configIgnoreClasses = isString(ignoreClasses) ? [ignoreClasses] : ignoreClasses;
- },
+ // The current domain will be added automatically if no given host alias contains a path
+ // and if no host alias is already given for the current host alias.
+ if (!hasDomainAliasAlready) {
+ /**
+ * eg if domainAlias = 'piwik.org' and someone set hostsAlias = ['piwik.org/foo'] then we should
+ * not add piwik.org as it would increase the allowed scope.
+ */
+ configHostsAlias.push(domainAlias);
+ }
+ };
- /**
- * Set request method
- *
- * @param string method GET or POST; default is GET
- */
- setRequestMethod: function (method) {
- configRequestMethod = method || defaultRequestMethod;
- },
+ /**
+ * Set array of classes to be ignored if present in link
+ *
+ * @param string|array ignoreClasses
+ */
+ this.setIgnoreClasses = function (ignoreClasses) {
+ configIgnoreClasses = isString(ignoreClasses) ? [ignoreClasses] : ignoreClasses;
+ };
- /**
- * Set request Content-Type header value, applicable when POST request method is used for submitting tracking events.
- * See XMLHttpRequest Level 2 spec, section 4.7.2 for invalid headers
- * @link http://dvcs.w3.org/hg/xhr/raw-file/tip/Overview.html
- *
- * @param string requestContentType; default is 'application/x-www-form-urlencoded; charset=UTF-8'
- */
- setRequestContentType: function (requestContentType) {
- configRequestContentType = requestContentType || defaultRequestContentType;
- },
+ /**
+ * Set request method
+ *
+ * @param string method GET or POST; default is GET
+ */
+ this.setRequestMethod = function (method) {
+ configRequestMethod = method || defaultRequestMethod;
+ };
- /**
- * Override referrer
- *
- * @param string url
- */
- setReferrerUrl: function (url) {
- configReferrerUrl = url;
- },
+ /**
+ * Set request Content-Type header value, applicable when POST request method is used for submitting tracking events.
+ * See XMLHttpRequest Level 2 spec, section 4.7.2 for invalid headers
+ * @link http://dvcs.w3.org/hg/xhr/raw-file/tip/Overview.html
+ *
+ * @param string requestContentType; default is 'application/x-www-form-urlencoded; charset=UTF-8'
+ */
+ this.setRequestContentType = function (requestContentType) {
+ configRequestContentType = requestContentType || defaultRequestContentType;
+ };
- /**
- * Override url
- *
- * @param string url
- */
- setCustomUrl: function (url) {
- configCustomUrl = resolveRelativeReference(locationHrefAlias, url);
- },
+ /**
+ * Override referrer
+ *
+ * @param string url
+ */
+ this.setReferrerUrl = function (url) {
+ configReferrerUrl = url;
+ };
- /**
- * Override document.title
- *
- * @param string title
- */
- setDocumentTitle: function (title) {
- configTitle = title;
- },
+ /**
+ * Override url
+ *
+ * @param string url
+ */
+ this.setCustomUrl = function (url) {
+ configCustomUrl = resolveRelativeReference(locationHrefAlias, url);
+ };
- /**
- * Set the URL of the Piwik API. It is used for Page Overlay.
- * This method should only be called when the API URL differs from the tracker URL.
- *
- * @param string apiUrl
- */
- setAPIUrl: function (apiUrl) {
- configApiUrl = apiUrl;
- },
+ /**
+ * Override document.title
+ *
+ * @param string title
+ */
+ this.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 the URL of the Piwik API. It is used for Page Overlay.
+ * This method should only be called when the API URL differs from the tracker URL.
+ *
+ * @param string apiUrl
+ */
+ this.setAPIUrl = function (apiUrl) {
+ configApiUrl = apiUrl;
+ };
- /**
- * Set array of classes to be treated as outlinks
- *
- * @param string|array linkClasses
- */
- setLinkClasses: function (linkClasses) {
- configLinkClasses = isString(linkClasses) ? [linkClasses] : linkClasses;
- },
+ /**
+ * Set array of classes to be treated as downloads
+ *
+ * @param string|array downloadClasses
+ */
+ this.setDownloadClasses = function (downloadClasses) {
+ configDownloadClasses = isString(downloadClasses) ? [downloadClasses] : downloadClasses;
+ };
- /**
- * 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 classes to be treated as outlinks
+ *
+ * @param string|array linkClasses
+ */
+ this.setLinkClasses = function (linkClasses) {
+ configLinkClasses = isString(linkClasses) ? [linkClasses] : linkClasses;
+ };
- /**
- * 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;
- },
+ /**
+ * Set array of campaign name parameters
+ *
+ * @see http://piwik.org/faq/how-to/#faq_120
+ * @param string|array campaignNames
+ */
+ this.setCampaignNameKey = function (campaignNames) {
+ configCampaignNameParameters = isString(campaignNames) ? [campaignNames] : campaignNames;
+ };
- /**
- * Strip hash tag (or anchor) from URL
- * Note: this can be done in the Piwik>Settings>Websites on a per-website basis
- *
- * @deprecated
- * @param bool enableFilter
- */
- discardHashTag: function (enableFilter) {
- configDiscardHashTag = enableFilter;
- },
+ /**
+ * Set array of campaign keyword parameters
+ *
+ * @see http://piwik.org/faq/how-to/#faq_120
+ * @param string|array campaignKeywords
+ */
+ this.setCampaignKeywordKey = function (campaignKeywords) {
+ configCampaignKeywordParameters = isString(campaignKeywords) ? [campaignKeywords] : campaignKeywords;
+ };
- /**
- * Set first-party cookie name prefix
- *
- * @param string cookieNamePrefix
- */
- setCookieNamePrefix: function (cookieNamePrefix) {
- configCookieNamePrefix = cookieNamePrefix;
- // Re-init the Custom Variables cookie
- customVariables = getCustomVariablesFromCookie();
- },
+ /**
+ * Strip hash tag (or anchor) from URL
+ * Note: this can be done in the Piwik>Settings>Websites on a per-website basis
+ *
+ * @deprecated
+ * @param bool enableFilter
+ */
+ this.discardHashTag = function (enableFilter) {
+ configDiscardHashTag = enableFilter;
+ };
- /**
- * Set first-party cookie domain
- *
- * @param string domain
- */
- setCookieDomain: function (domain) {
- var domainFixed = domainFixup(domain);
+ /**
+ * Set first-party cookie name prefix
+ *
+ * @param string cookieNamePrefix
+ */
+ this.setCookieNamePrefix = function (cookieNamePrefix) {
+ configCookieNamePrefix = cookieNamePrefix;
+ // Re-init the Custom Variables cookie
+ customVariables = getCustomVariablesFromCookie();
+ };
- if (isPossibleToSetCookieOnDomain(domainFixed)) {
- configCookieDomain = domainFixed;
- updateDomainHash();
- }
- },
+ /**
+ * Set first-party cookie domain
+ *
+ * @param string domain
+ */
+ this.setCookieDomain = function (domain) {
+ var domainFixed = domainFixup(domain);
- /**
- * Set first-party cookie path
- *
- * @param string domain
- */
- setCookiePath: function (path) {
- configCookiePath = path;
+ if (isPossibleToSetCookieOnDomain(domainFixed)) {
+ configCookieDomain = domainFixed;
updateDomainHash();
- },
+ }
+ };
- /**
- * Set visitor cookie timeout (in seconds)
- * Defaults to 13 months (timeout=33955200)
- *
- * @param int timeout
- */
- setVisitorCookieTimeout: function (timeout) {
- configVisitorCookieTimeout = timeout * 1000;
- },
+ /**
+ * Set first-party cookie path
+ *
+ * @param string domain
+ */
+ this.setCookiePath = function (path) {
+ configCookiePath = path;
+ updateDomainHash();
+ };
- /**
- * Set session cookie timeout (in seconds).
- * Defaults to 30 minutes (timeout=1800000)
- *
- * @param int timeout
- */
- setSessionCookieTimeout: function (timeout) {
- configSessionCookieTimeout = timeout * 1000;
- },
+ /**
+ * Set visitor cookie timeout (in seconds)
+ * Defaults to 13 months (timeout=33955200)
+ *
+ * @param int timeout
+ */
+ this.setVisitorCookieTimeout = function (timeout) {
+ configVisitorCookieTimeout = timeout * 1000;
+ };
- /**
- * Set referral cookie timeout (in seconds).
- * Defaults to 6 months (15768000000)
- *
- * @param int timeout
- */
- setReferralCookieTimeout: function (timeout) {
- configReferralCookieTimeout = timeout * 1000;
- },
+ /**
+ * Set session cookie timeout (in seconds).
+ * Defaults to 30 minutes (timeout=1800)
+ *
+ * @param int timeout
+ */
+ this.setSessionCookieTimeout = function (timeout) {
+ configSessionCookieTimeout = 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;
- },
+ /**
+ * Set referral cookie timeout (in seconds).
+ * Defaults to 6 months (15768000000)
+ *
+ * @param int timeout
+ */
+ this.setReferralCookieTimeout = function (timeout) {
+ configReferralCookieTimeout = timeout * 1000;
+ };
- /**
- * Disables all cookies from being set
- *
- * Existing cookies will be deleted on the next call to track
- */
- disableCookies: function () {
- configCookiesDisabled = true;
- browserFeatures.cookie = '0';
+ /**
+ * 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)
+ */
+ this.setConversionAttributionFirstReferrer = function (enable) {
+ configConversionAttributionFirstReferrer = enable;
+ };
- if (configTrackerSiteId) {
- deleteCookies();
- }
- },
+ /**
+ * Disables all cookies from being set
+ *
+ * Existing cookies will be deleted on the next call to track
+ */
+ this.disableCookies = function () {
+ configCookiesDisabled = true;
+ browserFeatures.cookie = '0';
- /**
- * One off cookies clearing. Useful to call this when you know for sure a new visitor is using the same browser,
- * it maybe helps to "reset" tracking cookies to prevent data reuse for different users.
- */
- deleteCookies: function () {
+ if (configTrackerSiteId) {
deleteCookies();
- },
+ }
+ };
- /**
- * 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');
+ /**
+ * One off cookies clearing. Useful to call this when you know for sure a new visitor is using the same browser,
+ * it maybe helps to "reset" tracking cookies to prevent data reuse for different users.
+ */
+ this.deleteCookies = function () {
+ deleteCookies();
+ };
- // do not track also disables cookies and deletes existing cookies
- if (configDoNotTrack) {
- this.disableCookies();
- }
- },
+ /**
+ * Handle do-not-track requests
+ *
+ * @param bool enable If true, don't track if user agent sends 'do-not-track' header
+ */
+ this.setDoNotTrack = function (enable) {
+ var dnt = navigatorAlias.doNotTrack || navigatorAlias.msDoNotTrack;
+ configDoNotTrack = enable && (dnt === 'yes' || dnt === '1');
- /**
- * 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 (middle click + context menu)
- */
- addListener: function (element, enable) {
- addClickListener(element, enable);
- },
+ // do not track also disables cookies and deletes existing cookies
+ if (configDoNotTrack) {
+ this.disableCookies();
+ }
+ };
- /**
- * 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 (treat middle click and open contextmenu as
- * left click). A right click (or any click that opens the context menu) on a link
- * will be tracked as clicked even if "Open in new tab" is not selected. If
- * "false" (default), nothing will be tracked on open context menu or middle click.
- * The context menu is usually opened to open a link / download in a new tab
- * therefore you can get more accurate results by treat it as a click but it can lead
- * to wrong click numbers.
- */
- enableLinkTracking: function (enable) {
- linkTrackingEnabled = true;
+ /**
+ * 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 (middle click + context menu)
+ */
+ this.addListener = function (element, enable) {
+ addClickListener(element, enable);
+ };
- trackCallback(function () {
- trackCallbackOnReady(function () {
- addClickListeners(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 (treat middle click and open contextmenu as
+ * left click). A right click (or any click that opens the context menu) on a link
+ * will be tracked as clicked even if "Open in new tab" is not selected. If
+ * "false" (default), nothing will be tracked on open context menu or middle click.
+ * The context menu is usually opened to open a link / download in a new tab
+ * therefore you can get more accurate results by treat it as a click but it can lead
+ * to wrong click numbers.
+ */
+ this.enableLinkTracking = function (enable) {
+ linkTrackingEnabled = true;
+
+ trackCallback(function () {
+ trackCallbackOnReady(function () {
+ addClickListeners(enable);
});
- },
+ });
+ };
- /**
- * Enable tracking of uncatched JavaScript errors
- *
- * If enabled, uncaught JavaScript Errors will be tracked as an event by defining a
- * window.onerror handler. If a window.onerror handler is already defined we will make
- * sure to call this previously registered error handler after tracking the error.
- *
- * By default we return false in the window.onerror handler to make sure the error still
- * appears in the browser's console etc. Note: Some older browsers might behave differently
- * so it could happen that an actual JavaScript error will be suppressed.
- * If a window.onerror handler was registered we will return the result of this handler.
- *
- * Make sure not to overwrite the window.onerror handler after enabling the JS error
- * tracking as the error tracking won't work otherwise. To capture all JS errors we
- * recommend to include the Piwik JavaScript tracker in the HTML as early as possible.
- * If possible directly in <head></head> before loading any other JavaScript.
- */
- enableJSErrorTracking: function () {
- if (enableJSErrorTracking) {
- return;
- }
+ /**
+ * Enable tracking of uncatched JavaScript errors
+ *
+ * If enabled, uncaught JavaScript Errors will be tracked as an event by defining a
+ * window.onerror handler. If a window.onerror handler is already defined we will make
+ * sure to call this previously registered error handler after tracking the error.
+ *
+ * By default we return false in the window.onerror handler to make sure the error still
+ * appears in the browser's console etc. Note: Some older browsers might behave differently
+ * so it could happen that an actual JavaScript error will be suppressed.
+ * If a window.onerror handler was registered we will return the result of this handler.
+ *
+ * Make sure not to overwrite the window.onerror handler after enabling the JS error
+ * tracking as the error tracking won't work otherwise. To capture all JS errors we
+ * recommend to include the Piwik JavaScript tracker in the HTML as early as possible.
+ * If possible directly in <head></head> before loading any other JavaScript.
+ */
+ this.enableJSErrorTracking = function () {
+ if (enableJSErrorTracking) {
+ return;
+ }
- enableJSErrorTracking = true;
- var onError = windowAlias.onerror;
+ enableJSErrorTracking = true;
+ var onError = windowAlias.onerror;
- windowAlias.onerror = function (message, url, linenumber, column, error) {
- trackCallback(function () {
- var category = 'JavaScript Errors';
+ windowAlias.onerror = function (message, url, linenumber, column, error) {
+ trackCallback(function () {
+ var category = 'JavaScript Errors';
- var action = url + ':' + linenumber;
- if (column) {
- action += ':' + column;
- }
+ var action = url + ':' + linenumber;
+ if (column) {
+ action += ':' + column;
+ }
- logEvent(category, action, message);
- });
+ logEvent(category, action, message);
+ });
- if (onError) {
- return onError(message, url, linenumber, column, error);
- }
+ if (onError) {
+ return onError(message, url, linenumber, column, error);
+ }
- return false;
- };
- },
+ return false;
+ };
+ };
- /**
- * Disable automatic performance tracking
- */
- disablePerformanceTracking: function () {
- configPerformanceTrackingEnabled = false;
- },
+ /**
+ * Disable automatic performance tracking
+ */
+ this.disablePerformanceTracking = function () {
+ configPerformanceTrackingEnabled = false;
+ };
- /**
- * Set the server generation time.
- * If set, the browser's performance.timing API in not used anymore to determine the time.
- *
- * @param int generationTime
- */
- setGenerationTimeMs: function (generationTime) {
- configPerformanceGenerationTime = parseInt(generationTime, 10);
- },
+ /**
+ * Set the server generation time.
+ * If set, the browser's performance.timing API in not used anymore to determine the time.
+ *
+ * @param int generationTime
+ */
+ this.setGenerationTimeMs = function (generationTime) {
+ configPerformanceGenerationTime = parseInt(generationTime, 10);
+ };
- /**
- * Set heartbeat (in seconds)
- *
- * @param int heartBeatDelayInSeconds Defaults to 15. Cannot be lower than 1.
- */
- enableHeartBeatTimer: function (heartBeatDelayInSeconds) {
- heartBeatDelayInSeconds = Math.max(heartBeatDelayInSeconds, 1);
- configHeartBeatDelay = (heartBeatDelayInSeconds || 15) * 1000;
+ /**
+ * Set heartbeat (in seconds)
+ *
+ * @param int heartBeatDelayInSeconds Defaults to 15. Cannot be lower than 1.
+ */
+ this.enableHeartBeatTimer = function (heartBeatDelayInSeconds) {
+ heartBeatDelayInSeconds = Math.max(heartBeatDelayInSeconds, 1);
+ configHeartBeatDelay = (heartBeatDelayInSeconds || 15) * 1000;
- // if a tracking request has already been sent, start the heart beat timeout
- if (lastTrackerRequestTime !== null) {
- setUpHeartBeat();
- }
- },
+ // if a tracking request has already been sent, start the heart beat timeout
+ if (lastTrackerRequestTime !== null) {
+ setUpHeartBeat();
+ }
+ };
/*<DEBUG>*/
- /**
- * Clear heartbeat.
- */
- disableHeartBeatTimer: function () {
- heartBeatDown();
- configHeartBeatDelay = null;
+ /**
+ * Clear heartbeat.
+ */
+ this.disableHeartBeatTimer = function () {
+ heartBeatDown();
+ configHeartBeatDelay = null;
- window.removeEventListener('focus', heartBeatOnFocus);
- window.removeEventListener('blur', heartBeatOnBlur);
- },
+ window.removeEventListener('focus', heartBeatOnFocus);
+ window.removeEventListener('blur', heartBeatOnBlur);
+ };
/*</DEBUG>*/
- /**
- * Frame buster
- */
- killFrame: function () {
- if (windowAlias.location !== windowAlias.top.location) {
- windowAlias.top.location = windowAlias.location;
- }
- },
+ /**
+ * Frame buster
+ */
+ this.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;
- }
- },
+ /**
+ * Redirect if browsing offline (aka file: buster)
+ *
+ * @param string url Redirect to this URL
+ */
+ this.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;
- },
+ /**
+ * Count sites in pre-rendered state
+ *
+ * @param bool enable If true, track when in pre-rendered state
+ */
+ this.setCountPreRendered = function (enable) {
+ configCountPreRendered = enable;
+ };
- /**
- * Trigger a goal
- *
- * @param int|string idGoal
- * @param int|float customRevenue
- * @param mixed customData
- */
- trackGoal: function (idGoal, customRevenue, customData) {
+ /**
+ * Trigger a goal
+ *
+ * @param int|string idGoal
+ * @param int|float customRevenue
+ * @param mixed customData
+ */
+ this.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
+ * @param function callback
+ */
+ this.trackLink = function (sourceUrl, linkType, customData, callback) {
+ trackCallback(function () {
+ logLink(sourceUrl, linkType, customData, callback);
+ });
+ };
+
+ /**
+ * Log visit to this page
+ *
+ * @param string customTitle
+ * @param mixed customData
+ * @param function callback
+ */
+ this.trackPageView = function (customTitle, customData, callback) {
+ trackedContentImpressions = [];
+
+ if (isOverlaySession(configTrackerSiteId)) {
trackCallback(function () {
- logGoal(idGoal, customRevenue, customData);
+ injectOverlayScripts(configTrackerUrl, configApiUrl, configTrackerSiteId);
});
- },
-
- /**
- * Manually log a click from your own code
- *
- * @param string sourceUrl
- * @param string linkType
- * @param mixed customData
- * @param function callback
- */
- trackLink: function (sourceUrl, linkType, customData, callback) {
+ } else {
trackCallback(function () {
- logLink(sourceUrl, linkType, customData, callback);
+ logPageView(customTitle, customData, callback);
});
- },
-
- /**
- * Log visit to this page
- *
- * @param string customTitle
- * @param mixed customData
- */
- trackPageView: function (customTitle, customData) {
- trackedContentImpressions = [];
-
- if (isOverlaySession(configTrackerSiteId)) {
- trackCallback(function () {
- injectOverlayScripts(configTrackerUrl, configApiUrl, configTrackerSiteId);
- });
- } else {
- trackCallback(function () {
- logPageView(customTitle, customData);
- });
- }
- },
+ }
+ };
- /**
- * Scans the entire DOM for all content blocks and tracks all impressions once the DOM ready event has
- * been triggered.
- *
- * If you only want to track visible content impressions have a look at `trackVisibleContentImpressions()`.
- * We do not track an impression of the same content block twice if you call this method multiple times
- * unless `trackPageView()` is called meanwhile. This is useful for single page applications.
- */
- trackAllContentImpressions: function () {
- if (isOverlaySession(configTrackerSiteId)) {
- return;
- }
+ /**
+ * Scans the entire DOM for all content blocks and tracks all impressions once the DOM ready event has
+ * been triggered.
+ *
+ * If you only want to track visible content impressions have a look at `trackVisibleContentImpressions()`.
+ * We do not track an impression of the same content block twice if you call this method multiple times
+ * unless `trackPageView()` is called meanwhile. This is useful for single page applications.
+ */
+ this.trackAllContentImpressions = function () {
+ if (isOverlaySession(configTrackerSiteId)) {
+ return;
+ }
- trackCallback(function () {
- trackCallbackOnReady(function () {
- // we have to wait till DOM ready
- var contentNodes = content.findContentNodes();
- var requests = getContentImpressionsRequestsFromNodes(contentNodes);
+ trackCallback(function () {
+ trackCallbackOnReady(function () {
+ // we have to wait till DOM ready
+ var contentNodes = content.findContentNodes();
+ var requests = getContentImpressionsRequestsFromNodes(contentNodes);
- sendBulkRequest(requests, configTrackerPause);
- });
+ sendBulkRequest(requests, configTrackerPause);
});
- },
+ });
+ };
- /**
- * Scans the entire DOM for all content blocks as soon as the page is loaded. It tracks an impression
- * only if a content block is actually visible. Meaning it is not hidden and the content is or was at
- * some point in the viewport.
- *
- * If you want to track all content blocks have a look at `trackAllContentImpressions()`.
- * We do not track an impression of the same content block twice if you call this method multiple times
- * unless `trackPageView()` is called meanwhile. This is useful for single page applications.
- *
- * Once you have called this method you can no longer change `checkOnScroll` or `timeIntervalInMs`.
- *
- * If you do want to only track visible content blocks but not want us to perform any automatic checks
- * as they can slow down your frames per second you can call `trackVisibleContentImpressions()` or
- * `trackContentImpressionsWithinNode()` manually at any time to rescan the entire DOM for newly
- * visible content blocks.
- * o Call `trackVisibleContentImpressions(false, 0)` to initially track only visible content impressions
- * o Call `trackVisibleContentImpressions()` at any time again to rescan the entire DOM for newly visible content blocks or
- * o Call `trackContentImpressionsWithinNode(node)` at any time to rescan only a part of the DOM for newly visible content blocks
- *
- * @param boolean [checkOnScroll=true] Optional, you can disable rescanning the entire DOM automatically
- * after each scroll event by passing the value `false`. If enabled,
- * we check whether a previously hidden content blocks became visible
- * after a scroll and if so track the impression.
- * Note: If a content block is placed within a scrollable element
- * (`overflow: scroll`), we can currently not detect when this block
- * becomes visible.
- * @param integer [timeIntervalInMs=750] Optional, you can define an interval to rescan the entire DOM
- * for new impressions every X milliseconds by passing
- * for instance `timeIntervalInMs=500` (rescan DOM every 500ms).
- * Rescanning the entire DOM and detecting the visible state of content
- * blocks can take a while depending on the browser and amount of content.
- * In case your frames per second goes down you might want to increase
- * this value or disable it by passing the value `0`.
- */
- trackVisibleContentImpressions: function (checkOnSroll, timeIntervalInMs) {
- if (isOverlaySession(configTrackerSiteId)) {
- return;
- }
+ /**
+ * Scans the entire DOM for all content blocks as soon as the page is loaded. It tracks an impression
+ * only if a content block is actually visible. Meaning it is not hidden and the content is or was at
+ * some point in the viewport.
+ *
+ * If you want to track all content blocks have a look at `trackAllContentImpressions()`.
+ * We do not track an impression of the same content block twice if you call this method multiple times
+ * unless `trackPageView()` is called meanwhile. This is useful for single page applications.
+ *
+ * Once you have called this method you can no longer change `checkOnScroll` or `timeIntervalInMs`.
+ *
+ * If you do want to only track visible content blocks but not want us to perform any automatic checks
+ * as they can slow down your frames per second you can call `trackVisibleContentImpressions()` or
+ * `trackContentImpressionsWithinNode()` manually at any time to rescan the entire DOM for newly
+ * visible content blocks.
+ * o Call `trackVisibleContentImpressions(false, 0)` to initially track only visible content impressions
+ * o Call `trackVisibleContentImpressions()` at any time again to rescan the entire DOM for newly visible content blocks or
+ * o Call `trackContentImpressionsWithinNode(node)` at any time to rescan only a part of the DOM for newly visible content blocks
+ *
+ * @param boolean [checkOnScroll=true] Optional, you can disable rescanning the entire DOM automatically
+ * after each scroll event by passing the value `false`. If enabled,
+ * we check whether a previously hidden content blocks became visible
+ * after a scroll and if so track the impression.
+ * Note: If a content block is placed within a scrollable element
+ * (`overflow: scroll`), we can currently not detect when this block
+ * becomes visible.
+ * @param integer [timeIntervalInMs=750] Optional, you can define an interval to rescan the entire DOM
+ * for new impressions every X milliseconds by passing
+ * for instance `timeIntervalInMs=500` (rescan DOM every 500ms).
+ * Rescanning the entire DOM and detecting the visible state of content
+ * blocks can take a while depending on the browser and amount of content.
+ * In case your frames per second goes down you might want to increase
+ * this value or disable it by passing the value `0`.
+ */
+ this.trackVisibleContentImpressions = function (checkOnSroll, timeIntervalInMs) {
+ if (isOverlaySession(configTrackerSiteId)) {
+ return;
+ }
- if (!isDefined(checkOnSroll)) {
- checkOnSroll = true;
- }
+ if (!isDefined(checkOnSroll)) {
+ checkOnSroll = true;
+ }
- if (!isDefined(timeIntervalInMs)) {
- timeIntervalInMs = 750;
- }
+ if (!isDefined(timeIntervalInMs)) {
+ timeIntervalInMs = 750;
+ }
- enableTrackOnlyVisibleContent(checkOnSroll, timeIntervalInMs, this);
+ enableTrackOnlyVisibleContent(checkOnSroll, timeIntervalInMs, this);
- trackCallback(function () {
- trackCallbackOnLoad(function () {
- // we have to wait till CSS parsed and applied
- var contentNodes = content.findContentNodes();
- var requests = getCurrentlyVisibleContentImpressionsRequestsIfNotTrackedYet(contentNodes);
+ trackCallback(function () {
+ trackCallbackOnLoad(function () {
+ // we have to wait till CSS parsed and applied
+ var contentNodes = content.findContentNodes();
+ var requests = getCurrentlyVisibleContentImpressionsRequestsIfNotTrackedYet(contentNodes);
- sendBulkRequest(requests, configTrackerPause);
- });
+ sendBulkRequest(requests, configTrackerPause);
});
- },
-
- /**
- * Tracks a content impression using the specified values. You should not call this method too often
- * as each call causes an XHR tracking request and can slow down your site or your server.
- *
- * @param string contentName For instance "Ad Sale".
- * @param string [contentPiece='Unknown'] For instance a path to an image or the text of a text ad.
- * @param string [contentTarget] For instance the URL of a landing page.
- */
- trackContentImpression: function (contentName, contentPiece, contentTarget) {
- if (isOverlaySession(configTrackerSiteId)) {
- return;
- }
+ });
+ };
- if (!contentName) {
- return;
- }
+ /**
+ * Tracks a content impression using the specified values. You should not call this method too often
+ * as each call causes an XHR tracking request and can slow down your site or your server.
+ *
+ * @param string contentName For instance "Ad Sale".
+ * @param string [contentPiece='Unknown'] For instance a path to an image or the text of a text ad.
+ * @param string [contentTarget] For instance the URL of a landing page.
+ */
+ this.trackContentImpression = function (contentName, contentPiece, contentTarget) {
+ if (isOverlaySession(configTrackerSiteId)) {
+ return;
+ }
- contentPiece = contentPiece || 'Unknown';
+ if (!contentName) {
+ return;
+ }
- trackCallback(function () {
- var request = buildContentImpressionRequest(contentName, contentPiece, contentTarget);
- sendRequest(request, configTrackerPause);
- });
- },
+ contentPiece = contentPiece || 'Unknown';
- /**
- * Scans the given DOM node and its children for content blocks and tracks an impression for them if
- * no impression was already tracked for it. If you have called `trackVisibleContentImpressions()`
- * upfront only visible content blocks will be tracked. You can use this method if you, for instance,
- * dynamically add an element using JavaScript to your DOM after we have tracked the initial impressions.
- *
- * @param Element domNode
- */
- trackContentImpressionsWithinNode: function (domNode) {
- if (isOverlaySession(configTrackerSiteId) || !domNode) {
- return;
- }
+ trackCallback(function () {
+ var request = buildContentImpressionRequest(contentName, contentPiece, contentTarget);
+ sendRequest(request, configTrackerPause);
+ });
+ };
- trackCallback(function () {
- if (isTrackOnlyVisibleContentEnabled) {
- trackCallbackOnLoad(function () {
- // we have to wait till CSS parsed and applied
- var contentNodes = content.findContentNodesWithinNode(domNode);
-
- var requests = getCurrentlyVisibleContentImpressionsRequestsIfNotTrackedYet(contentNodes);
- sendBulkRequest(requests, configTrackerPause);
- });
- } else {
- trackCallbackOnReady(function () {
- // we have to wait till DOM ready
- var contentNodes = content.findContentNodesWithinNode(domNode);
+ /**
+ * Scans the given DOM node and its children for content blocks and tracks an impression for them if
+ * no impression was already tracked for it. If you have called `trackVisibleContentImpressions()`
+ * upfront only visible content blocks will be tracked. You can use this method if you, for instance,
+ * dynamically add an element using JavaScript to your DOM after we have tracked the initial impressions.
+ *
+ * @param Element domNode
+ */
+ this.trackContentImpressionsWithinNode = function (domNode) {
+ if (isOverlaySession(configTrackerSiteId) || !domNode) {
+ return;
+ }
- var requests = getContentImpressionsRequestsFromNodes(contentNodes);
- sendBulkRequest(requests, configTrackerPause);
- });
- }
- });
- },
+ trackCallback(function () {
+ if (isTrackOnlyVisibleContentEnabled) {
+ trackCallbackOnLoad(function () {
+ // we have to wait till CSS parsed and applied
+ var contentNodes = content.findContentNodesWithinNode(domNode);
- /**
- * Tracks a content interaction using the specified values. You should use this method only in conjunction
- * with `trackContentImpression()`. The specified `contentName` and `contentPiece` has to be exactly the
- * same as the ones that were used in `trackContentImpression()`. Otherwise the interaction will not count.
- *
- * @param string contentInteraction The type of interaction that happened. For instance 'click' or 'submit'.
- * @param string contentName The name of the content. For instance "Ad Sale".
- * @param string [contentPiece='Unknown'] The actual content. For instance a path to an image or the text of a text ad.
- * @param string [contentTarget] For instance the URL of a landing page.
- */
- trackContentInteraction: function (contentInteraction, contentName, contentPiece, contentTarget) {
- if (isOverlaySession(configTrackerSiteId)) {
- return;
- }
+ var requests = getCurrentlyVisibleContentImpressionsRequestsIfNotTrackedYet(contentNodes);
+ sendBulkRequest(requests, configTrackerPause);
+ });
+ } else {
+ trackCallbackOnReady(function () {
+ // we have to wait till DOM ready
+ var contentNodes = content.findContentNodesWithinNode(domNode);
- if (!contentInteraction || !contentName) {
- return;
+ var requests = getContentImpressionsRequestsFromNodes(contentNodes);
+ sendBulkRequest(requests, configTrackerPause);
+ });
}
+ });
+ };
- contentPiece = contentPiece || 'Unknown';
+ /**
+ * Tracks a content interaction using the specified values. You should use this method only in conjunction
+ * with `trackContentImpression()`. The specified `contentName` and `contentPiece` has to be exactly the
+ * same as the ones that were used in `trackContentImpression()`. Otherwise the interaction will not count.
+ *
+ * @param string contentInteraction The type of interaction that happened. For instance 'click' or 'submit'.
+ * @param string contentName The name of the content. For instance "Ad Sale".
+ * @param string [contentPiece='Unknown'] The actual content. For instance a path to an image or the text of a text ad.
+ * @param string [contentTarget] For instance the URL of a landing page.
+ */
+ this.trackContentInteraction = function (contentInteraction, contentName, contentPiece, contentTarget) {
+ if (isOverlaySession(configTrackerSiteId)) {
+ return;
+ }
- trackCallback(function () {
- var request = buildContentInteractionRequest(contentInteraction, contentName, contentPiece, contentTarget);
- sendRequest(request, configTrackerPause);
- });
- },
+ if (!contentInteraction || !contentName) {
+ return;
+ }
- /**
- * Tracks an interaction with the given DOM node / content block.
- *
- * By default we track interactions on click but sometimes you might want to track interactions yourself.
- * For instance you might want to track an interaction manually on a double click or a form submit.
- * Make sure to disable the automatic interaction tracking in this case by specifying either the CSS
- * class `piwikContentIgnoreInteraction` or the attribute `data-content-ignoreinteraction`.
- *
- * @param Element domNode This element itself or any of its parent elements has to be a content block
- * element. Meaning one of those has to have a `piwikTrackContent` CSS class or
- * a `data-track-content` attribute.
- * @param string [contentInteraction='Unknown] The name of the interaction that happened. For instance
- * 'click', 'formSubmit', 'DblClick', ...
- */
- trackContentInteractionNode: function (domNode, contentInteraction) {
- if (isOverlaySession(configTrackerSiteId) || !domNode) {
- return;
- }
+ contentPiece = contentPiece || 'Unknown';
- trackCallback(function () {
- var request = buildContentInteractionRequestNode(domNode, contentInteraction);
- sendRequest(request, configTrackerPause);
- });
- },
+ trackCallback(function () {
+ var request = buildContentInteractionRequest(contentInteraction, contentName, contentPiece, contentTarget);
+ sendRequest(request, configTrackerPause);
+ });
+ };
- /**
- * Useful to debug content tracking. This method will log all detected content blocks to console
- * (if the browser supports the console). It will list the detected name, piece, and target of each
- * content block.
- */
- logAllContentBlocksOnPage: function () {
- var contentNodes = content.findContentNodes();
- var contents = content.collectContent(contentNodes);
+ /**
+ * Tracks an interaction with the given DOM node / content block.
+ *
+ * By default we track interactions on click but sometimes you might want to track interactions yourself.
+ * For instance you might want to track an interaction manually on a double click or a form submit.
+ * Make sure to disable the automatic interaction tracking in this case by specifying either the CSS
+ * class `piwikContentIgnoreInteraction` or the attribute `data-content-ignoreinteraction`.
+ *
+ * @param Element domNode This element itself or any of its parent elements has to be a content block
+ * element. Meaning one of those has to have a `piwikTrackContent` CSS class or
+ * a `data-track-content` attribute.
+ * @param string [contentInteraction='Unknown] The name of the interaction that happened. For instance
+ * 'click', 'formSubmit', 'DblClick', ...
+ */
+ this.trackContentInteractionNode = function (domNode, contentInteraction) {
+ if (isOverlaySession(configTrackerSiteId) || !domNode) {
+ return;
+ }
- if (console !== undefined && console && console.log) {
- console.log(contents);
- }
- },
+ trackCallback(function () {
+ var request = buildContentInteractionRequestNode(domNode, contentInteraction);
+ sendRequest(request, configTrackerPause);
+ });
+ };
- /**
- * Records an event
- *
- * @param string category The Event Category (Videos, Music, Games...)
- * @param string action The Event's Action (Play, Pause, Duration, Add Playlist, Downloaded, Clicked...)
- * @param string name (optional) The Event's object Name (a particular Movie name, or Song name, or File name...)
- * @param float value (optional) The Event's value
- * @param mixed customData
- */
- trackEvent: function (category, action, name, value, customData) {
- trackCallback(function () {
- logEvent(category, action, name, value, customData);
- });
- },
+ /**
+ * Useful to debug content tracking. This method will log all detected content blocks to console
+ * (if the browser supports the console). It will list the detected name, piece, and target of each
+ * content block.
+ */
+ this.logAllContentBlocksOnPage = function () {
+ var contentNodes = content.findContentNodes();
+ var contents = content.collectContent(contentNodes);
- /**
- * Log special pageview: Internal search
- *
- * @param string keyword
- * @param string category
- * @param int resultsCount
- * @param mixed customData
- */
- trackSiteSearch: function (keyword, category, resultsCount, customData) {
- trackCallback(function () {
- logSiteSearch(keyword, category, resultsCount, customData);
- });
- },
+ if (console !== undefined && console && console.log) {
+ console.log(contents);
+ }
+ };
- /**
- * 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);
- }
+ /**
+ * Records an event
+ *
+ * @param string category The Event Category (Videos, Music, Games...)
+ * @param string action The Event's Action (Play, Pause, Duration, Add Playlist, Downloaded, Clicked...)
+ * @param string name (optional) The Event's object Name (a particular Movie name, or Song name, or File name...)
+ * @param float value (optional) The Event's value
+ * @param mixed customData
+ */
+ this.trackEvent = function (category, action, name, value, customData) {
+ trackCallback(function () {
+ logEvent(category, action, name, value, customData);
+ });
+ };
- customVariablesPage[5] = ['_pkc', category];
+ /**
+ * Log special pageview: Internal search
+ *
+ * @param string keyword
+ * @param string category
+ * @param int resultsCount
+ * @param mixed customData
+ */
+ this.trackSiteSearch = function (keyword, category, resultsCount, customData) {
+ trackCallback(function () {
+ logSiteSearch(keyword, category, resultsCount, customData);
+ });
+ };
- if (isDefined(price) && String(price).length) {
- customVariablesPage[2] = ['_pkp', price];
- }
+ /**
+ * 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.
+ */
+ this.setEcommerceView = function (sku, name, category, price) {
+ if (!isDefined(category) || !category.length) {
+ category = "";
+ } else if (category instanceof Array) {
+ category = JSON2.stringify(category);
+ }
- // On a category page, do not track Product name not defined
- if ((!isDefined(sku) || !sku.length)
- && (!isDefined(name) || !name.length)) {
- return;
- }
+ customVariablesPage[5] = ['_pkc', category];
- if (isDefined(sku) && sku.length) {
- customVariablesPage[3] = ['_pks', sku];
- }
+ if (isDefined(price) && String(price).length) {
+ customVariablesPage[2] = ['_pkp', price];
+ }
- if (!isDefined(name) || !name.length) {
- name = "";
- }
+ // On a category page, do not track Product name not defined
+ if ((!isDefined(sku) || !sku.length)
+ && (!isDefined(name) || !name.length)) {
+ return;
+ }
- customVariablesPage[4] = ['_pkn', name];
- },
+ if (isDefined(sku) && sku.length) {
+ customVariablesPage[3] = ['_pks', sku];
+ }
- /**
- * 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 ];
- }
- },
+ if (!isDefined(name) || !name.length) {
+ name = "";
+ }
- /**
- * 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);
- },
+ customVariablesPage[4] = ['_pkn', name];
+ };
- /**
- * 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);
+ /**
+ * 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.
+ * The items are deleted from this JavaScript object when the Ecommerce order is tracked via the method trackEcommerceOrder.
+ *
+ * @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
+ */
+ this.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.
+ * After calling this method, items added to the cart will be removed from this JavaScript object.
+ *
+ * @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
+ */
+ this.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)
+ * Calling this method does not remove from this JavaScript object the items that were added to the cart via addEcommerceItem
+ *
+ * @param float grandTotal (required) Items (products) amount in the Cart
+ */
+ this.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
- ************************************************************/
+ /**
+ * Sends a tracking request with custom request parameters.
+ * Piwik will prepend the hostname and path to Piwik, as well as all other needed tracking request
+ * parameters prior to sending the request. Useful eg if you track custom dimensions via a plugin.
+ *
+ * @param request eg. "param=value&param2=value2"
+ * @param customData
+ * @param callback
+ */
+ this.trackRequest = function (request, customData, callback) {
+ trackCallback(function () {
+ var fullRequest = getRequest(request, customData);
+ sendRequest(fullRequest, configTrackerPause, callback);
+ });
+ };
+
+ Piwik.trigger('TrackerSetup', [this]);
+ }
function TrackerProxy() {
return {
@@ -6362,9 +6523,7 @@ if (typeof window.Piwik !== 'object') {
delete paq[iterator];
if (appliedMethods[methodName] > 1) {
- if (console !== undefined && console && console.error) {
- console.error('The method ' + methodName + ' is registered more than once in "paq" variable. Only the last call has an effect. Please have a look at the multiple Piwik trackers documentation: http://developer.piwik.org/guides/tracking-javascript-guide#multiple-piwik-trackers');
- }
+ logConsoleError('The method ' + methodName + ' is registered more than once in "_paq" variable. Only the last call has an effect. Please have a look at the multiple Piwik trackers documentation: http://developer.piwik.org/guides/tracking-javascript-guide#multiple-piwik-trackers');
}
appliedMethods[methodName]++;
@@ -6380,31 +6539,97 @@ if (typeof window.Piwik !== 'object') {
* Constructor
************************************************************/
- // initialize the Piwik singleton
- addEventListener(windowAlias, 'beforeunload', beforeUnloadHandler, false);
+ var applyFirst = ['addTracker', 'disableCookies', 'setTrackerUrl', 'setAPIUrl', 'setCookiePath', 'setCookieDomain', 'setDomains', 'setUserId', 'setSiteId', 'enableLinkTracking'];
- Date.prototype.getTimeAlias = Date.prototype.getTime;
-
- asyncTracker = new Tracker();
+ function createFirstTracker(piwikUrl, siteId)
+ {
+ var tracker = new Tracker(piwikUrl, siteId);
+ asyncTrackers.push(tracker);
- var applyFirst = ['disableCookies', 'setTrackerUrl', 'setAPIUrl', 'setCookiePath', 'setCookieDomain', 'setDomains', 'setUserId', 'setSiteId', 'enableLinkTracking'];
- _paq = applyMethodsInOrder(_paq, applyFirst);
+ _paq = applyMethodsInOrder(_paq, applyFirst);
- // apply the queue of actions
- for (iterator = 0; iterator < _paq.length; iterator++) {
- if (_paq[iterator]) {
- apply(_paq[iterator]);
+ // apply the queue of actions
+ for (iterator = 0; iterator < _paq.length; iterator++) {
+ if (_paq[iterator]) {
+ apply(_paq[iterator]);
+ }
}
+
+ // replace initialization array with proxy object
+ _paq = new TrackerProxy();
+
+ return tracker;
}
- // replace initialization array with proxy object
- _paq = new TrackerProxy();
+ /************************************************************
+ * Proxy object
+ * - this allows the caller to continue push()'ing to _paq
+ * after the Tracker has been initialized and loaded
+ ************************************************************/
+
+ // initialize the Piwik singleton
+ addEventListener(windowAlias, 'beforeunload', beforeUnloadHandler, false);
+
+ Date.prototype.getTimeAlias = Date.prototype.getTime;
/************************************************************
* Public data and methods
************************************************************/
Piwik = {
+ initialized: false,
+
+ /**
+ * Listen to an event and invoke the handler when a the event is triggered.
+ *
+ * @param string event
+ * @param function handler
+ */
+ on: function (event, handler) {
+ if (!eventHandlers[event]) {
+ eventHandlers[event] = [];
+ }
+
+ eventHandlers[event].push(handler);
+ },
+
+ /**
+ * Remove a handler to no longer listen to the event. Must pass the same handler that was used when
+ * attaching the event via ".on".
+ * @param string event
+ * @param function handler
+ */
+ off: function (event, handler) {
+ if (!eventHandlers[event]) {
+ return;
+ }
+
+ var i = 0;
+ for (i; i < eventHandlers[event].length; i++) {
+ if (eventHandlers[event][i] === handler) {
+ delete eventHandlers[event][i];
+ }
+ }
+ },
+
+ /**
+ * Triggers the given event and passes the parameters to all handlers.
+ *
+ * @param string event
+ * @param Array extraParameters
+ * @param Object context If given the handler will be executed in this context
+ */
+ trigger: function (event, extraParameters, context) {
+ if (!eventHandlers[event]) {
+ return;
+ }
+
+ var i = 0;
+ for (i; i < eventHandlers[event].length; i++) {
+ eventHandlers[event][i].apply(context || windowAlias, extraParameters);
+ }
+ },
+
/**
* Add plugin
*
@@ -6423,22 +6648,82 @@ if (typeof window.Piwik !== 'object') {
* @return Tracker
*/
getTracker: function (piwikUrl, siteId) {
- if(!isDefined(siteId)) {
+ if (!isDefined(siteId)) {
siteId = this.getAsyncTracker().getSiteId();
}
- if(!isDefined(piwikUrl)) {
+ if (!isDefined(piwikUrl)) {
piwikUrl = this.getAsyncTracker().getTrackerUrl();
}
+
return new Tracker(piwikUrl, siteId);
},
/**
- * Get internal asynchronous tracker object
+ * Get all created async trackers
+ *
+ * @return Tracker[]
+ */
+ getAsyncTrackers: function () {
+ return asyncTrackers;
+ },
+
+ /**
+ * Adds a new tracker. All sent requests will be also sent to the given siteId and piwikUrl.
+ * If piwikUrl is not set, current url will be used.
+ *
+ * @param null|string piwikUrl If null, will reuse the same tracker URL of the current tracker instance
+ * @param int|string siteId
+ * @return Tracker
+ */
+ addTracker: function (piwikUrl, siteId) {
+ if (!asyncTrackers.length) {
+ createFirstTracker(piwikUrl, siteId);
+ } else {
+ asyncTrackers[0].addTracker(piwikUrl, siteId);
+ }
+ },
+
+ /**
+ * Get internal asynchronous tracker object.
+ *
+ * If no parameters are given, it returns the internal asynchronous tracker object. If a piwikUrl and idSite
+ * is given, it will try to find an optional
*
+ * @param string piwikUrl
+ * @param int|string siteId
* @return Tracker
*/
- getAsyncTracker: function () {
- return asyncTracker;
+ getAsyncTracker: function (piwikUrl, siteId) {
+
+ var firstTracker;
+ if (asyncTrackers && asyncTrackers[0]) {
+ firstTracker = asyncTrackers[0];
+ }
+
+ if (!siteId && !piwikUrl) {
+ // for BC and by default we just return the initally created tracker
+ return firstTracker;
+ }
+
+ // we look for another tracker created via `addTracker` method
+ if ((!isDefined(siteId) || null === siteId) && firstTracker) {
+ siteId = firstTracker.getSiteId();
+ }
+
+ if ((!isDefined(piwikUrl) || null === piwikUrl) && firstTracker) {
+ piwikUrl = firstTracker.getTrackerUrl();
+ }
+
+ var tracker, i = 0;
+ for (i; i < asyncTrackers.length; i++) {
+ tracker = asyncTrackers[i];
+ if (tracker
+ && String(tracker.getSiteId()) === String(siteId)
+ && tracker.getTrackerUrl() === piwikUrl) {
+
+ return tracker;
+ }
+ }
}
};
@@ -6451,6 +6736,28 @@ if (typeof window.Piwik !== 'object') {
}());
}
+/*!! pluginTrackerHook */
+
+(function () {
+ 'use strict';
+
+ if (window
+ && 'object' === typeof window.piwikPluginAsyncInit
+ && window.piwikPluginAsyncInit.length) {
+ var i = 0;
+ for (i; i < window.piwikPluginAsyncInit.length; i++) {
+ if (typeof window.piwikPluginAsyncInit[i] === 'function') {
+ window.piwikPluginAsyncInit[i]();
+ }
+ }
+ }
+
+ window.Piwik.addTracker();
+
+ window.Piwik.trigger('PiwikInitialized', []);
+ window.Piwik.initialized = true;
+}());
+
if (window && window.piwikAsyncInit) {
window.piwikAsyncInit();
}