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.js352
1 files changed, 324 insertions, 28 deletions
diff --git a/js/piwik.js b/js/piwik.js
index 56dfa28f6e..c97ac02be7 100644
--- a/js/piwik.js
+++ b/js/piwik.js
@@ -990,7 +990,8 @@ if (typeof JSON_PIWIK !== 'object' && typeof window.JSON === 'object' && window.
setVisitorCookieTimeout, setSessionCookieTimeout, setReferralCookieTimeout,
setConversionAttributionFirstReferrer,
disablePerformanceTracking, setGenerationTimeMs,
- doNotTrack, setDoNotTrack, msDoNotTrack, getValuesFromVisitorIdCookie,
+ doNotTrack, setDoNotTrack, msDoNotTrack, getValuesFromVisitorIdCookie, enableCrossDomainLinking,
+ disableCrossDomainLinking, isCrossDomainLinkingEnabled,
addListener, enableLinkTracking, enableJSErrorTracking, setLinkTrackingTimer,
enableHeartBeatTimer, disableHeartBeatTimer, killFrame, redirectFile, setCountPreRendered,
trackGoal, trackLink, trackPageView, trackRequest, trackSiteSearch, trackEvent,
@@ -1468,10 +1469,113 @@ if (typeof window.Piwik !== 'object') {
return matches ? matches[1] : url;
}
+ 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;
+ }
+
+ function stringContains(str, needle) {
+ str = String(str);
+ return str.indexOf(needle) !== -1;
+ }
+
+ function removeCharactersFromEndOfString(str, numCharactersToRemove) {
+ str = String(str);
+ return str.substr(0, str.length - numCharactersToRemove);
+ }
+
+ /**
+ * We do not check whether URL contains already url parameter, please use removeUrlParameter() if needed
+ * before calling this method.
+ * This method makes sure to append URL parameters before a possible hash. Will escape (encode URI component)
+ * the set name and value
+ */
+ function addUrlParameter(url, name, value) {
+ url = String(url);
+
+ if (!value) {
+ value = '';
+ }
+
+ var hashPos = url.indexOf('#');
+ var urlLength = url.length;
+
+ if (hashPos === -1) {
+ hashPos = urlLength;
+ }
+
+ var baseUrl = url.substr(0, hashPos);
+ var urlHash = url.substr(hashPos, urlLength - hashPos);
+
+ if (baseUrl.indexOf('?') === -1) {
+ baseUrl += '?';
+ } else if (!stringEndsWith(baseUrl, '?')) {
+ baseUrl += '&';
+ }
+ // nothing to if ends with ?
+
+ return baseUrl + encodeWrapper(name) + '=' + encodeWrapper(value) + urlHash;
+ }
+
+ function removeUrlParameter(url, name) {
+ url = String(url);
+
+ if (url.indexOf('?' + name + '=') === -1 && url.indexOf('&' + name + '=') === -1) {
+ // nothing to remove, url does not contain this parameter
+ return url;
+ }
+
+ var searchPos = url.indexOf('?');
+ if (searchPos === -1) {
+ // nothing to remove, no query parameters
+ return url;
+ }
+
+ var queryString = url.substr(searchPos + 1);
+ var baseUrl = url.substr(0, searchPos);
+
+ if (queryString) {
+ var urlHash = '';
+ var hashPos = queryString.indexOf('#');
+ if (hashPos !== -1) {
+ urlHash = queryString.substr(hashPos + 1);
+ queryString = queryString.substr(0, hashPos);
+ }
+
+ var param;
+ var paramsArr = queryString.split('&');
+ var i = paramsArr.length - 1;
+
+ for (i; i >= 0; i--) {
+ param = paramsArr[i].split('=')[0];
+ if (param === name) {
+ paramsArr.splice(i, 1);
+ }
+ }
+
+ var newQueryString = paramsArr.join('&');
+
+ if (newQueryString) {
+ baseUrl = baseUrl + '?' + newQueryString;
+ }
+
+ if (urlHash) {
+ baseUrl += '#' + urlHash;
+ }
+ }
+
+ return baseUrl;
+ }
+
/*
* Extract parameter from URL
*/
- function getParameter(url, name) {
+ function getUrlParameter(url, name) {
var regexSearch = "[\\?&#]" + name + "=([^&#]*)";
var regex = new RegExp(regexSearch);
var results = regex.exec(url);
@@ -1650,7 +1754,7 @@ if (typeof window.Piwik !== 'object') {
referrer = href;
}
- href = getParameter(href, 'u');
+ href = getUrlParameter(href, 'u');
hostName = getHostName(href);
} else if (hostName === 'cc.bingj.com' || // Bing
hostName === 'webcache.googleusercontent.com' || // Google
@@ -1787,26 +1891,6 @@ 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;
- }
-
- function stringContains(str, needle) {
- str = String(str);
- return str.indexOf(needle) !== -1;
- }
-
- function removeCharactersFromEndOfString(str, numCharactersToRemove) {
- str = String(str);
- return str.substr(0, str.length - numCharactersToRemove);
- }
-
/************************************************************
* Element Visiblility
************************************************************/
@@ -2983,6 +3067,17 @@ if (typeof window.Piwik !== 'object') {
// First-party cookie name prefix
configCookieNamePrefix = '_pk_',
+ // the URL parameter that will store the visitorId if cross domain linking is enabled
+ // pk_vid = visitor ID
+ // first part of this URL parameter will be 16 char visitor Id.
+ // The second part is the 10 char current timestamp and the third and last part will be a 6 characters deviceId
+ // timestamp is needed to prevent reusing the visitorId when the URL is shared. The visitorId will be
+ // only reused if the timestamp is less than 45 seconds old.
+ // deviceId parameter is needed to prevent reusing the visitorId when the URL is shared. The visitorId
+ // will be only reused if the device is still the same when opening the link.
+ // VDI = visitor device identifier
+ configVisitorIdUrlParameter = 'pk_vid',
+
// First-party cookie domain
// User agent defaults to origin hostname
configCookieDomain,
@@ -3056,6 +3151,7 @@ if (typeof window.Piwik !== 'object') {
// Guard against installing the link tracker more than once per Tracker instance
linkTrackingInstalled = false,
linkTrackingEnabled = false,
+ crossDomainTrackingEnabled = false,
// Guard against installing the activity tracker more than once per Tracker instance
heartBeatSetUp = false,
@@ -3136,6 +3232,10 @@ if (typeof window.Piwik !== 'object') {
function purify(url) {
var targetPattern;
+ // we need to remove this parameter here, they wouldn't be removed in Piwik tracker otherwise eg
+ // for outlinks or referrers
+ url = removeUrlParameter(url, configVisitorIdUrlParameter);
+
if (configDiscardHashTag) {
targetPattern = new RegExp('#.*');
@@ -3528,7 +3628,7 @@ if (typeof window.Piwik !== 'object') {
function sendRequest(request, delay, callback) {
if (!configDoNotTrack && request) {
makeSureThereIsAGapAfterFirstTrackingRequestToPreventMultipleVisitorCreation(function () {
- if (configRequestMethod === 'POST') {
+ if (configRequestMethod === 'POST' || String(request).length > 2000) {
sendXmlHttpRequest(request, callback);
} else {
getImage(request, callback);
@@ -3647,10 +3747,94 @@ if (typeof window.Piwik !== 'object') {
).slice(0, 16);
}
+ function generateBrowserSpecificId() {
+ return hash(
+ (navigatorAlias.userAgent || '') +
+ (navigatorAlias.platform || '') +
+ JSON_PIWIK.stringify(browserFeatures)).slice(0, 6);
+ }
+
+ function getCurrentTimestampInSeconds()
+ {
+ return Math.floor((new Date()).getTime() / 1000);
+ }
+
+ function makeCrossDomainDeviceId()
+ {
+ var timestamp = getCurrentTimestampInSeconds();
+ var browserId = generateBrowserSpecificId();
+ var deviceId = String(timestamp) + browserId;
+
+ return deviceId;
+ }
+
+ function isSameCrossDomainDevice(deviceIdFromUrl)
+ {
+ deviceIdFromUrl = String(deviceIdFromUrl);
+
+ var thisBrowserId = generateBrowserSpecificId();
+ var lengthBrowserId = thisBrowserId.length;
+
+ var browserIdInUrl = deviceIdFromUrl.substr(-1 * lengthBrowserId, lengthBrowserId);
+ var timestampInUrl = parseInt(deviceIdFromUrl.substr(0, deviceIdFromUrl.length - lengthBrowserId), 10);
+
+ if (timestampInUrl && browserIdInUrl && browserIdInUrl === thisBrowserId) {
+ // we only reuse visitorId when used on same device / browser
+
+ var currentTimestampInSeconds = getCurrentTimestampInSeconds();
+ var timeoutInSeconds = 45;
+
+ if (currentTimestampInSeconds >= timestampInUrl
+ && currentTimestampInSeconds <= (timestampInUrl + timeoutInSeconds)) {
+ // we only use visitorId if it was generated max 45 seconds ago
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ function getVisitorIdFromUrl(url) {
+ if (!crossDomainTrackingEnabled) {
+ return '';
+ }
+
+ // problem different timezone or when the time on the computer is not set correctly it may re-use
+ // the same visitorId again. therefore we also have a factor like hashed user agent to reduce possible
+ // activation of a visitorId on other device
+ var visitorIdParam = getUrlParameter(url, configVisitorIdUrlParameter);
+
+ if (!visitorIdParam) {
+ return '';
+ }
+
+ visitorIdParam = String(visitorIdParam);
+
+ var pattern = new RegExp("^[a-zA-Z0-9]+$");
+
+ if (visitorIdParam.length === 32 && pattern.test(visitorIdParam)) {
+ var visitorDevice = visitorIdParam.substr(16, 32);
+
+ if (isSameCrossDomainDevice(visitorDevice)) {
+ var visitorId = visitorIdParam.substr(0, 16);
+ return visitorId;
+ }
+ }
+
+ return '';
+ }
+
/*
* Load visitor ID cookie
*/
function loadVisitorIdCookie() {
+
+ if (!visitorUUID) {
+ // we are using locationHrefAlias and not currentUrl on purpose to for sure get the passed URL parameters
+ // from original URL
+ visitorUUID = getVisitorIdFromUrl(locationHrefAlias);
+ }
+
var now = new Date(),
nowTs = Math.round(now.getTime() / 1000),
visitorIdCookieName = getCookieName('id'),
@@ -3737,7 +3921,6 @@ if (typeof window.Piwik !== 'object') {
};
}
-
function getRemainingVisitorCookieTimeout() {
var now = new Date(),
nowTs = now.getTime(),
@@ -3971,7 +4154,7 @@ if (typeof window.Piwik !== 'object') {
|| !campaignNameDetected.length) {
for (i in configCampaignNameParameters) {
if (Object.prototype.hasOwnProperty.call(configCampaignNameParameters, i)) {
- campaignNameDetected = getParameter(currentUrl, configCampaignNameParameters[i]);
+ campaignNameDetected = getUrlParameter(currentUrl, configCampaignNameParameters[i]);
if (campaignNameDetected.length) {
break;
@@ -3981,7 +4164,7 @@ if (typeof window.Piwik !== 'object') {
for (i in configCampaignKeywordParameters) {
if (Object.prototype.hasOwnProperty.call(configCampaignKeywordParameters, i)) {
- campaignKeywordDetected = getParameter(currentUrl, configCampaignKeywordParameters[i]);
+ campaignKeywordDetected = getUrlParameter(currentUrl, configCampaignKeywordParameters[i]);
if (campaignKeywordDetected.length) {
break;
@@ -4492,6 +4675,7 @@ if (typeof window.Piwik !== 'object') {
}
var link = getLinkIfShouldBeProcessed(targetNode);
+
if (linkTrackingEnabled && link && link.type) {
return false; // will be handled via outlink or download.
@@ -4860,12 +5044,85 @@ if (typeof window.Piwik !== 'object') {
callback();
}
+ function replaceHrefForCrossDomainLink(element)
+ {
+ if (!element) {
+ return;
+ }
+
+ if (!query.hasNodeAttribute(element, 'href')) {
+ return;
+ }
+
+ var link = query.getAttributeValueFromNode(element, 'href');
+
+ if (!link || startsUrlWithTrackerUrl(link)) {
+ return;
+ }
+
+ // we need to remove the parameter and add it again if needed to make sure we have latest timestamp
+ // and visitorId (eg userId might be set etc)
+ link = removeUrlParameter(link, configVisitorIdUrlParameter);
+
+ if (link.indexOf('?') > 0) {
+ link += '&';
+ } else {
+ link += '?';
+ }
+
+ var visitorId = getValuesFromVisitorIdCookie().uuid;
+ var deviceId = makeCrossDomainDeviceId();
+
+ link = addUrlParameter(link, configVisitorIdUrlParameter, visitorId + deviceId);
+
+ query.setAnyAttribute(element, 'href', link);
+ }
+
+ function isLinkToDifferentDomainButSamePiwikWebsite(element)
+ {
+ var targetLink = query.getAttributeValueFromNode(element, 'href');
+
+ if (!targetLink) {
+ return false;
+ }
+
+ targetLink = String(targetLink);
+
+ var isOutlink = targetLink.indexOf('//') === 0
+ || targetLink.indexOf('http://') === 0
+ || targetLink.indexOf('https://') === 0;
+
+ if (!isOutlink) {
+ return false;
+ }
+
+ var originalSourcePath = element.pathname || getPathName(element.href);
+ var originalSourceHostName = (element.hostname || getHostName(element.href)).toLowerCase();
+
+ if (isSiteHostPath(originalSourceHostName, originalSourcePath)) {
+ // we could also check against config cookie domain but this would require that other website
+ // sets actually same cookie domain and we cannot rely on it.
+ if (!isSameHost(domainAlias, domainFixup(originalSourceHostName))) {
+ return true;
+ }
+
+ return false;
+ }
+
+ return false;
+ }
+
/*
* Process clicks
*/
function processClick(sourceElement) {
var link = getLinkIfShouldBeProcessed(sourceElement);
+ if (crossDomainTrackingEnabled && !link && isLinkToDifferentDomainButSamePiwikWebsite(sourceElement)) {
+ // !link means it is a link to same domain or same website (as set in setDomains())
+ replaceHrefForCrossDomainLink(sourceElement);
+ }
+
if (link && link.type) {
link.href = safeDecodeWrapper(link.href);
logLink(link.href, link.type, undefined, null, sourceElement);
@@ -5751,6 +6008,45 @@ if (typeof window.Piwik !== 'object') {
};
/**
+ * Enables cross domain linking. By default, the visitor ID that identifies a unique visitor is stored in
+ * the browser's cookies. This means the cookie can only be accessed by pages on the same domain.
+ * If you own multiple domains and would like to track all the actions and pageviews of a specific visitor
+ * into the same visit, you may enable cross domain linking. Whenever a user clicks on a link it will append
+ * a URL parameter pk_vid to the clicked URL which consists of these parts: 16 char visitorId, a 10 character
+ * current timestamp and the last 6 characters are an id based on the userAgent to identify the users device).
+ * This way the current visitorId is forwarded to the page of the different domain.
+ *
+ * On the different domain, the Piwik tracker will recognize the set visitorId from the URL parameter and
+ * reuse this parameter if the page was loaded within 45 seconds. If cross domain linking was not enabled,
+ * it would create a new visit on that page because we wouldn't be able to access the previously created
+ * cookie. By enabling cross domain linking you can track several different domains into one website and
+ * won't lose for example the original referrer.
+ *
+ * To make cross domain linking work you need to set which domains should be considered as your domains by
+ * calling the method "setDomains()" first. We will add the URL parameter to links that go to a
+ * different domain but only if the domain was previously set with "setDomains()" to make sure not to append
+ * the URL parameters when a link actually goes to a third-party URL.
+ */
+ this.enableCrossDomainLinking = function () {
+ crossDomainTrackingEnabled = true;
+ };
+
+ /**
+ * Disable cross domain linking if it was previously enabled. See enableCrossDomainLinking();
+ */
+ this.disableCrossDomainLinking = function () {
+ crossDomainTrackingEnabled = false;
+ };
+
+ /**
+ * Detect whether cross domain linking is enabled or not. See enableCrossDomainLinking();
+ * @returns bool
+ */
+ this.isCrossDomainLinkingEnabled = function () {
+ return crossDomainTrackingEnabled;
+ };
+
+ /**
* Set array of classes to be ignored if present in link
*
* @param string|array ignoreClasses
@@ -6598,7 +6894,7 @@ if (typeof window.Piwik !== 'object') {
* Constructor
************************************************************/
- var applyFirst = ['addTracker', 'disableCookies', 'setTrackerUrl', 'setAPIUrl', 'setCookiePath', 'setCookieDomain', 'setDomains', 'setUserId', 'setSiteId', 'enableLinkTracking'];
+ var applyFirst = ['addTracker', 'disableCookies', 'setTrackerUrl', 'setAPIUrl', 'enableCrossDomainLinking', 'setCookiePath', 'setCookieDomain', 'setDomains', 'setUserId', 'setSiteId', 'enableLinkTracking'];
function createFirstTracker(piwikUrl, siteId)
{