Ubuntu

­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­ /*! * Matomo - free/libre analytics platform * * Matomo Tag Manager * * @link https://matomo.org * @source https://github.com/matomo-org/tag-manager/blob/master/js/piwik.js * @license https://matomo.org/free-software/bsd/ BSD-3 Clause (also in js/LICENSE.txt) */ /** * To minify execute * cat javascripts/tagmanager.js | java -jar ../../js/yuicompressor-2.4.8.jar --type js --line-break 1000 | sed 's/^[/][*]/\/*!/' > javascripts/tagmanager.min.js */ /* startjslint */ /*jslint browser:true, plusplus:true, vars:true, nomen:true, evil:true, regexp: false, bitwise: true, white: true */ /*global window */ /*global unescape */ /*global ActiveXObject */ (function () { var documentAlias = document; var windowAlias = window; /*!! previewModeHook */ if (typeof window.MatomoTagManager !== 'object') { if (typeof window._mtm !== 'object') { window._mtm = []; } window.MatomoTagManager = (function () { var timeScriptLoaded = new Date().getTime(); function pushDebugLogMessage() { if (window.mtmPreviewWindow && 'object' === typeof window.mtmPreviewWindow.mtmLogs) { var now = new Date(); var messages = []; for (var i = 0; i < arguments.length; i++) { messages.push(JSON.stringify(arguments[i], function( key, value) { if (typeof value === 'object' && value instanceof Node) { return value.nodeName; } else { return value; }; })); } window.mtmPreviewWindow.mtmLogs.push({time: now.toLocaleTimeString() + '.' + now.getMilliseconds(), messages: messages}); } } function pushDebugEvent(event) { if (window.mtmPreviewWindow && 'object' === typeof window.mtmPreviewWindow.mtmEvents && event) { var now = new Date(); event.time = now.toLocaleTimeString() + '.' + now.getMilliseconds(); window.mtmPreviewWindow.mtmEvents.push(event); } } var Debug = { enabled: !!window.mtmPreviewWindow, log: function () { pushDebugLogMessage.apply(windowAlias, arguments); if (this.enabled && 'undefined' !== typeof console && console && console.debug) { console.debug.apply(console, arguments); } }, error: function () { pushDebugLogMessage.apply(windowAlias, arguments); // cannot be disabled if ('undefined' !== typeof console && console && console.error) { console.error.apply(console, arguments); } }, }; function throwError(message) { Debug.error(message); if (typeof TagManager !== 'object' || TagManager.THROW_ERRORS) { throw new Error(message); } } function resolveNestedDotVar(key, obj) { if (utils.isString(key) && key.indexOf('.') !== -1) { var parts = key.split('.'); var i; for (i = 0; i < parts.length; i++) { if (parts[i] in obj) { obj = obj[parts[i]]; } else { // value does not exist return; } } return obj; } } function Storage(storageInterface) { var namespace = 'mtm:'; var values = {}; function hasStorage (method) { return storageInterface in windowAlias && utils.isObject(windowAlias[storageInterface]); } function hasFeature (method) { return hasStorage() && utils.isFunction(windowAlias[storageInterface][method]); } function set(group, value) { if (hasFeature('setItem')) { try { windowAlias[storageInterface].setItem(namespace + group, JSON.stringify(value)); } catch (e) {} } else { values[group] = value; } } function get(group) { if (hasFeature('getItem')) { try { var value = windowAlias[storageInterface].getItem(namespace + group); if (value) { value = JSON.parse(value); if (utils.isObject(value)) { return value; } } } catch(e) {} return {}; } else { if (group in values) { return values[group]; } } } function remove(group) { if (hasFeature('removeItem')) { try { windowAlias[storageInterface].removeItem(namespace + group); } catch(e) {} } else { if (group in values) { delete values[group]; } } } this.set = function (group, key, val, ttl) { var expireTime = null; if (ttl) { expireTime = (new Date().getTime()) + (parseInt(ttl,10) * 1000); } var value = get(group); value[key] = {value: val, expire: expireTime}; set(group, value); }; this.get = function (group, key) { var value = get(group); if (value && key in value && 'value' in value[key]) { if (value[key].expire && value[key].expire < (new Date().getTime())) { delete value[key]; set(group) return; } return value[key].value; } }; this.clearAll = function () { values = {}; if (hasStorage() && utils.isFunction(Object.keys)) { var items = Object.keys(windowAlias[storageInterface]); if (items) { for (var i = 0; i < items.length; i++) { if (String(items[i]).substr(0, namespace.length) === namespace) { remove(String(items[i]).substr(namespace.length)); } } } } }; } var localStorage = new Storage('localStorage'); var sessionStorage = new Storage('sessionStorage'); var utils = { _compare: function (actualValue, expectedValue, comparison) { var comparisonsToTreatLowerCase = ['equals', 'starts_with', 'contains', 'ends_with']; if (this.indexOfArray(comparisonsToTreatLowerCase, comparison) !== -1) { actualValue = String(actualValue).toLowerCase(); expectedValue = String(expectedValue).toLowerCase(); } switch (comparison) { case 'equals': return String(actualValue) === String(expectedValue); case 'equals_exactly': return String(actualValue) === String(expectedValue); case 'regexp': return null !== (String(actualValue).match(new RegExp(expectedValue))); case 'regexp_ignore_case': return null !== (String(actualValue).match(new RegExp(expectedValue, 'i'))); case 'lower_than': return actualValue < expectedValue; case 'lower_than_or_equals': return actualValue <= expectedValue; case 'greater_than': return actualValue > expectedValue; case 'greater_than_or_equals': return actualValue >= expectedValue; case 'contains': return String(actualValue).indexOf(expectedValue) !== -1; case 'match_css_selector': if (!expectedValue || !actualValue) { return false; } var nodes = DOM.bySelector(expectedValue) return utils.indexOfArray(nodes, actualValue) !== -1; case 'starts_with': return String(actualValue).indexOf(expectedValue) === 0; case 'ends_with': return String(actualValue).substring(actualValue.length - expectedValue.length, actualValue.length) === expectedValue; } return false; }, compare: function (actualValue, expectedValue, comparison) { var isInverted = String(comparison).indexOf('not_') === 0; if (isInverted) { comparison = String(comparison).substr('not_'.length); } var result = this._compare(actualValue, expectedValue, comparison); if (isInverted) { return !result; } return result; }, trim: function (text) { if (text && String(text) === text) { return text.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); } return text; }, isDefined: function (property) { var propertyType = typeof property; return propertyType !== 'undefined'; }, isFunction: function (property) { return typeof property === 'function'; }, isObject: function (property) { return typeof property === 'object' && property !== null; }, isString: function (property) { return typeof property === 'string'; }, isNumber: function (property) { return typeof property === 'number'; }, isArray: function (property) { if (!utils.isObject(property)) { return false; } if ('function' === typeof Array.isArray && Array.isArray) { return Array.isArray(property); } var toString = Object.prototype.toString; var arrayString = toString.call([]); return toString.call(property) === arrayString; }, hasProperty: function (object, key) { return Object.prototype.hasOwnProperty.call(object, key); }, indexOfArray: function (anArray, element) { if (!anArray) { return -1; } if ('function' === typeof anArray.indexOf && anArray.indexOf) { return anArray.indexOf(element); } if (!this.isArray(anArray)) { return -1; } for (var i = 0; i < anArray.length; i++) { if (anArray[i] === element) { return i; } } return -1; }, setMethodWrapIfNeeded: function (contextObject, methodNameToReplace, callback) { if (!(methodNameToReplace in contextObject)) { contextObject[methodNameToReplace] = callback; return; } var oldMethodBackup = contextObject[methodNameToReplace]; if (!TagManager.utils.isFunction(oldMethodBackup)) { contextObject[methodNameToReplace] = callback; return; } try { contextObject[methodNameToReplace] = function() { try { var value = oldMethodBackup.apply(contextObject, [].slice.call(arguments, 0)); } catch (e) { callback.apply(contextObject, [].slice.call(arguments, 0)); throw e; } callback.apply(contextObject, [].slice.call(arguments, 0)); return value; }; } catch (error) { } } }; var DataLayer = function () { this.values = {}; this.events = []; this.callbacks = []; this.reset = function () { this.values = {}; this.events = []; this.callbacks = []; }; this.push = function (value) { if (!utils.isObject(value)) { Debug.log('pushed dataLayer value is not an object', value); return; } this.events.push(value); var i; for (i in value) { if (utils.hasProperty(value, i)) { this.set(i, value[i]); } } for (i = 0; i < this.callbacks.length; i++) { if (this.callbacks[i]) { this.callbacks[i](value); } } }; this.on = function (callback) { this.callbacks.push(callback); return this.callbacks.length - 1; }; this.off = function (callbackIndex) { if (callbackIndex in this.callbacks) { // we do not call .splice() as it would change the order of all indexes of other callbacks this.callbacks[callbackIndex] = null; } }; this.set = function (key, value) { this.values[key] = value; }; this.getAllEvents = function (container) { return this.events; }; this.get = function (name) { if (name in this.values) { if (utils.isFunction(this.values[name])) { return this.values[name](); } else if (utils.isObject(this.values[name]) && utils.isFunction(this.values[name].get)) { return this.values[name].get(); } return this.values[name]; } var val = resolveNestedDotVar(name, this.values); if (utils.isDefined(val)) { return val; } }; }; var dataLayer = new DataLayer(); var dateHelper = { matchesDateRange: function(now, startDateTime, endDateTime) { var currentTimestampUTC = Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), now.getUTCHours(), now.getUTCMinutes(), now.getUTCSeconds()); if (startDateTime) { // needed eg for safari: 2014/01/02 instead of 2014-01-02 startDateTime = String(startDateTime).replace(/-/g, '/'); } if (endDateTime) { endDateTime = String(endDateTime).replace(/-/g, '/'); } var start, end; try { start = this.convertStringToDate(startDateTime); } catch (e) { if (startDateTime) { throwError('Invalid startDateTime given'); } } try { end = this.convertStringToDate(endDateTime); } catch (e) { if (endDateTime) { throwError('Invalid endDateTime given'); } } if (startDateTime && isNaN && isNaN(start.getTime())) { throwError('Invalid startDateTime given'); } if (endDateTime && isNaN && isNaN(end.getTime())) { throwError('Invalid endDateTime given'); } if (startDateTime && currentTimestampUTC < start.getTime()) { return false; } if (endDateTime && currentTimestampUTC > end.getTime()) { return false; } return true; }, convertStringToDate: function (dateString) { var timezonePresent = (dateString && dateString.split(' ').length > 2); dateString = dateString + (dateString && dateString.toLowerCase() !== 'invalid date' && !timezonePresent ? ' UTC' : ''); return new Date(dateString); } }; var urlHelper = { parseUrl: function (urlToParse, urlPart) { try { var loc = document.createElement("a"); loc.href = urlToParse; var absUrl = loc.href; // needed to make tests work in IE10... we first need to convert URL to abs url loc = document.createElement("a"); loc.href = absUrl; if (urlPart && urlPart in loc) { if ('hash' === urlPart) { return String(loc[urlPart]).replace('#', ''); } else if ('protocol' === urlPart) { return String(loc[urlPart]).replace(':', ''); } else if ('search' === urlPart) { return String(loc[urlPart]).replace('?', ''); } else if ('port' === urlPart && !loc[urlPart]) { if (loc.protocol === 'https:') { return '443'; } else if (loc.protocol === 'http:') { return '80'; } } if ('pathname' === urlPart && loc[urlPart] && String(loc[urlPart]).substr(0,1) !== '/') { return '/' + loc[urlPart]; // ie 10 doesn't return leading slash when not added to the dom } if ('port' === urlPart && loc[urlPart]) { return String(loc[urlPart]); // ie 10 returns int } return loc[urlPart]; } if ('origin' === urlPart && 'protocol' in loc && loc.protocol) { // fix for ie10 return loc.protocol + "//" + loc.hostname + (loc.port ? ':' + loc.port : ''); } return; } catch (e) { if ('function' === typeof URL) { var theUrl = new URL(urlToParse); if (urlPart && urlPart in theUrl) { if ('hash' === urlPart) { return String(theUrl[urlPart]).replace('#', ''); } else if ('protocol' === urlPart) { return String(theUrl[urlPart]).replace(':', ''); } else if ('search' === urlPart) { return String(theUrl[urlPart]).replace('?', ''); } else if ('port' === urlPart && !theUrl[urlPart]) { if (theUrl.protocol === 'https:') { return '443'; } else if (theUrl.protocol === 'http:') { return '80'; } } return theUrl[urlPart]; } return; } } }, decodeSafe: function (text) { try { return windowAlias.decodeURIComponent(text); } catch (e) { return windowAlias.unescape(text); } }, getQueryParameter: function (parameter, locationSearch) { if (!utils.isDefined(locationSearch)) { locationSearch = windowAlias.location.search; } if (!locationSearch || !utils.isDefined(parameter) || parameter === null || parameter === false || parameter === '') { return null; } var locationStart = locationSearch.substr(0,1); if (locationSearch !== '?' && locationSearch !== '&'){ locationSearch = '?' + locationSearch; } parameter = parameter.replace('[', '\\['); parameter = parameter.replace(']', '\\]'); var regexp = new RegExp('[?&]' + parameter + '(=([^&#]*)|&|#|$)'); var matches = regexp.exec(locationSearch); if (!matches) { return null; } if (!matches[2]) { return ''; } var value = matches[2].replace(/\+/g, " "); return this.decodeSafe(value); } }; var windowHelperScrollTimeout; var windowHelper = { hasSetupScroll: false, scrollCallbacks: [], scrollListenEvents: ['scroll', 'resize'], offScroll: function (scrollIndex) { if (scrollIndex in this.scrollCallbacks) { this.scrollCallbacks[scrollIndex] = null; } // when there are no longer any callbacks, we should remove all event listeners for better performance var i = 0, numCallbacks = 0; for (i in this.scrollCallbacks) { if (this.scrollCallbacks[i]) { numCallbacks++; } } if (!numCallbacks) { for (i = 0; i < this.scrollListenEvents.length; i++) { if (documentAlias.removeEventListener) { windowAlias.removeEventListener(this.scrollListenEvents[i], this.didScroll, true); } else { windowAlias.detachEvent('on' + this.scrollListenEvents[i], this.didScroll); } } this.hasSetupScroll = false; } }, didScroll: function (event) { if (windowHelperScrollTimeout) { return; } if (event && event.type && event.type === 'scroll' && event.target && event.target !== documentAlias && event.target !== windowAlias) { // we only listen to scrolls on whole page by default as this is WINDOW listener, not element listener return; } // scroll event is executed after each pixel, so we make sure not to // execute event too often. otherwise FPS goes down a lot! windowHelperScrollTimeout = setTimeout(function () { windowHelperScrollTimeout = null; var i; for (i = 0; i < windowHelper.scrollCallbacks.length; i++) { if (windowHelper.scrollCallbacks[i]) { windowHelper.scrollCallbacks[i](event); } } }, 120); }, onScroll: function (callback) { this.scrollCallbacks.push(callback); if (!this.hasSetupScroll) { this.hasSetupScroll = true; var index = 0; for (index = 0; index < this.scrollListenEvents.length; index++) { if (documentAlias.addEventListener) { windowAlias.addEventListener(this.scrollListenEvents[index], this.didScroll, true); } else { windowAlias.attachEvent('on' + this.scrollListenEvents[index], this.didScroll); } } } return this.scrollCallbacks.length - 1; }, getScreenHeight: function () { return windowAlias.screen.height; }, getScreenWidth: function () { return windowAlias.screen.width; }, getViewportWidth: function () { var width = windowAlias.innerWidth || documentAlias.documentElement.clientWidth || documentAlias.body.clientWidth; if (!width) { return 0; } return width; }, getViewportHeight: function () { var height = windowAlias.innerHeight || documentAlias.documentElement.clientHeight || documentAlias.body.clientHeight; if (!height) { return 0; } return height; }, getPerformanceTiming: function (keyword) { if ('performance' in windowAlias && utils.isObject(windowAlias.performance) && utils.isObject(windowAlias.performance.timing) && keyword in windowAlias.performance.timing) { return windowAlias.performance.timing[keyword]; } return 0; } }; var DOM = { loadScriptUrl: function (url, options) { if (!options) { options = {}; } if (!utils.isDefined(options.defer)) { options.defer = true; } if (!utils.isDefined(options.async)) { options.async = true; } if (!utils.isDefined(options.type)) { options.type = 'text/javascript'; } var script = document.createElement('script'); script.src = url; script.type = options.type; script.defer = !!options.defer; script.async = !!options.async; if (utils.isFunction(options.onload)) { script.onload = options.onload; } if (utils.isFunction(options.onerror)) { script.onerror = options.onerror; } if (utils.isDefined(options.charset)) { script.charset = options.charset; } if (utils.isDefined(options.id)) { script.id = options.id; } documentAlias.head.appendChild(script); }, getScrollLeft: function () { return windowAlias.document.body.scrollLeft || windowAlias.document.documentElement.scrollLeft; }, getScrollTop: function () { return windowAlias.document.body.scrollTop || windowAlias.document.documentElement.scrollTop; }, getDocumentHeight: function () { // we use at least one px to prevent divisions by zero etc return Math.max(documentAlias.body.offsetHeight, documentAlias.body.scrollHeight, documentAlias.documentElement.offsetHeight, documentAlias.documentElement.clientHeight, documentAlias.documentElement.scrollHeight, 1); }, getDocumentWidth: function () { // we use at least one px to prevent divisions by zero etc return Math.max(documentAlias.body.offsetWidth, documentAlias.body.scrollWidth, documentAlias.documentElement.offsetWidth, documentAlias.documentElement.clientWidth, documentAlias.documentElement.scrollWidth, 1); }, addEventListener: function (element, eventType, eventHandler, useCapture) { if (!element) { Debug.log('element not found, cannot add event listener', element, this); return; } if (element.addEventListener) { useCapture = useCapture || false; element.addEventListener(eventType, eventHandler, useCapture); return true; } if (element.attachEvent) { return element.attachEvent('on' + eventType, eventHandler); } element['on' + eventType] = eventHandler; }, getElementText: function (node) { if (!node) { return; } // If the content belongs to a masked element and it doesn't have any children, return a masked string. if (TagManager.dom.shouldElementBeMasked(node) && node.children.length === 0) { return '*******'; } // If the element has children that should be masked, deal with that. if (TagManager.dom.elementHasMaskedChild(node)) { return TagManager.dom.getElementTextWithMaskedChildren(node); } var content = node.innerText || node.textContent || ''; content = content.replace(/([\s\uFEFF\xA0])+/g, " "); content = content.replace(/(\s)+/g, " "); return utils.trim(content); }, getElementClassNames: function (node) { if (node && node.className) { return utils.trim(String(node.className).replace(/\s{2,}/g, ' ')); } return ''; }, getElementAttribute: function (node, attributeName) { if (!node || !attributeName) { return; } // If the attribute is one of the restricted attributes and belongs to a masked element, return a masked string. var attr = attributeName.toLowerCase(); if ((attr === 'value' || attr === 'title' || attr === 'alt' || attr === 'label' || attr === 'placeholder') && TagManager.dom.shouldElementBeMasked(node)) { return '*******'; } if (node && node.getAttribute) { return node.getAttribute(attributeName); } if (!node || !node.attributes) { return; } var typeOfAttr = (typeof node.attributes[attributeName]); if ('undefined' === typeOfAttr) { return null; } if (node.attributes[attributeName].value) { return node.attributes[attributeName].value; // nodeValue is deprecated ie Chrome } if (node.attributes[attributeName].nodeValue) { return node.attributes[attributeName].nodeValue; } return null; }, _htmlCollectionToArray: function (foundNodes) { var nodes = []; if (!foundNodes || !foundNodes.length) { return nodes; } var index; for (index = 0; index < foundNodes.length; index++) { nodes.push(foundNodes[index]); } return nodes; }, byId: function (id) { if (utils.isString(id) && id.substr(0,1) === '#') { id = id.substr(1); } return documentAlias.getElementById(id); }, byClassName: function (className) { if (className && 'getElementsByClassName' in documentAlias) { return this._htmlCollectionToArray(documentAlias.getElementsByClassName(className)); } return []; }, byTagName: function (tagName) { if (tagName && 'getElementsByTagName' in documentAlias) { return this._htmlCollectionToArray(documentAlias.getElementsByTagName(tagName)); } return []; }, bySelector: function (selector) { if (selector && 'querySelectorAll' in documentAlias) { return this._htmlCollectionToArray(documentAlias.querySelectorAll(selector)); } return []; }, /** @ignore **/ isElementContext: function (htmlString, tag) { // not part of API at this moment if (!htmlString || !tag) { return false; } htmlString = String(htmlString).toLowerCase(); tag = String(tag).toLowerCase(); var lastScriptPos = htmlString.lastIndexOf('<' + tag); if (lastScriptPos === -1) { return false; } var lastPiece = htmlString.substring(lastScriptPos); return !lastPiece.match(new RegExp('<\\s*/\\s*' + tag + '>')); }, /** @ignore **/ isAttributeContext: function (htmlString, attr) { // not part of API at this moment if (!htmlString || !attr) { return false; } // we remove all whitespace around equal signs in case there is an attribute like this " href = 'fff'" // for easier matching htmlString = String(htmlString).replace(/([\s\uFEFF\xA0]*=[\s\uFEFF\xA0]*)/g, '='); // htmlString = eg "sdsds
'); if (endingElementPos !== -1) { return false; // the tag was closed so we cannot be within an attribute } var posAttrEnd = lastPiece.lastIndexOf('='); if (posAttrEnd === -1) { return false; // no attribute with value within the tag } var posAttrStart = lastPiece.lastIndexOf(' ', posAttrEnd); // attrName = eg " style" var attrName = lastPiece.substring(posAttrStart, posAttrEnd); attrName = utils.trim(attrName); if (attrName.toLowerCase() !== attr.toLowerCase()) { return false; } // attrValue = eg "'color:" var attrValue = lastPiece.substring(posAttrEnd).replace('=', ''); var quote = attrValue.substring(0, 1); if ('"' === quote) { return -1 === attrValue.substring(1).indexOf('"'); } else if ("'" === quote) { return -1 === attrValue.substring(1).indexOf("'"); } // seems like user did not put quotes around the attribute! we check for a space for attr separation return -1 === attrValue.indexOf(' '); }, onLoad: function (callback) { if (documentAlias.readyState === 'complete') { callback(); } else if (windowAlias.addEventListener) { windowAlias.addEventListener('load', callback); } else if (windowAlias.attachEvent) { windowAlias.attachEvent('onload', callback); } }, onReady: function (callback) { var loaded = false; if (documentAlias.attachEvent) { loaded = documentAlias.readyState === 'complete'; } else { loaded = documentAlias.readyState !== 'loading'; } if (loaded) { callback(); return; } if (documentAlias.addEventListener) { this.addEventListener(documentAlias, 'DOMContentLoaded', function ready() { documentAlias.removeEventListener('DOMContentLoaded', ready, false); if (!loaded) { loaded = true; callback(); } }); } else if (documentAlias.attachEvent) { documentAlias.attachEvent('onreadystatechange', function ready() { if (documentAlias.readyState === 'complete') { documentAlias.detachEvent('onreadystatechange', ready); if (!loaded) { loaded = true; callback(); } } }); } // fallback this.onLoad(function () { if (!loaded) { loaded = true; callback(); } }); }, onClick: function (callback, element) { if (typeof element === 'undefined') { element = documentAlias.body; } TagManager.dom.addEventListener(element, 'click', function (event) { var clickKey = (event.which ? event.which : 1); if (clickKey === 1) { callback(event, 'left'); } }, true) TagManager.dom.addEventListener(element, 'auxclick', function (event) { var clickKey = (event.which ? event.which : 2); if (clickKey === 2) { callback(event, 'middle'); } }, true) TagManager.dom.addEventListener(element, 'contextmenu', function (event) { var clickKey = (event.which ? event.which : 3); if (clickKey === 3) { callback(event, 'right'); } }, true) }, shouldElementBeMasked: function (element) { if (typeof element === 'undefined') { return false; } // If the element has the attribute indicating that it should be masked, return true. if (element.hasAttribute('data-matomo-mask') || element.hasAttribute('data-piwik-mask')) { return true; } // If the element has the attribute indicating that it shouldn't be masked, return false. if (element.hasAttribute('data-matomo-unmask') || element.hasAttribute('data-piwik-unmask')) { return false; } // Find the closest parent with the mask or unmask attribute. If it's the mask, return true. I originally used the closest function, but it appears that some browsers don't support it. var parentElement = element.parentElement; while (parentElement) { if (parentElement.hasAttribute('data-matomo-mask') || parentElement.hasAttribute('data-piwik-mask')) { return true; } if (parentElement.hasAttribute('data-matomo-unmask') || parentElement.hasAttribute('data-piwik-unmask')) { return false; } parentElement = parentElement.parentElement; } return false; }, elementHasMaskedChild: function (element) { if (typeof element === 'undefined') { return false; } // Does the element even have any children? if (element.children.length === 0) { return false; } // Does the current node have a mask attribute or a parent that does? if (element.hasAttribute('data-matomo-mask') || element.hasAttribute('data-piwik-mask') || TagManager.dom.shouldElementBeMasked(element)) { return true; } return element.querySelector('[data-matomo-mask],[data-piwik-mask]') !== null; }, getElementTextWithMaskedChildren: function (element) { var text = ''; var descendents = element.children; for (var i = 0; i < descendents.length; i++) { var item = descendents[i]; text += TagManager.dom.getElementText(item) + ' '; } return utils.trim(text); } }; function TemplateParameters(params) { this.window = windowAlias; this.document = documentAlias; this.set = function (index, value) { this[index] = value; }; this.get = function (key, defaultValue) { if (key === null || key === false || !utils.isDefined(key)) { return defaultValue; } if (key in this) { if (utils.isObject(this[key]) && 'get' in this[key] && utils.isFunction(this[key].get)) { return this[key].get(); } return this[key]; } var value = resolveNestedDotVar(key, this); if (utils.isDefined(value)) { return value; } return defaultValue; }; this.buildVariable = function (variable) { return buildVariable(variable, this.get('container')); }; if (utils.isObject(params)) { for (var i in params) { if (utils.hasProperty(params, i)) { this.set(i, params[i]); } } } } function Condition(condition, container) { this.isValid = function () { var actualValue = buildVariable(condition.actual, container).get(); var expectedValue = buildVariable(condition.expected, container).get(); return utils.compare(actualValue, expectedValue, condition.comparison); }; } function buildVariable(variable, container) { if (utils.isObject(variable) && variable.joinedVariable && utils.isArray(variable.joinedVariable)) { return new JoinedVariable(variable.joinedVariable, container); } else if (utils.isObject(variable) && variable.type) { return new Variable(variable, container); } return new ConstantVariable(variable, container); } function JoinedVariable(variables, container) { this.name = ''; this.type = 'JoinedVariable'; this.getDefinition = function () { return variables; }; this.get = function () { var value = '', varReturn; for (var i = 0; i < variables.length; i++) { varReturn = buildVariable(variables[i], container).toString(); if (varReturn !== false && varReturn !== null && utils.isDefined(varReturn)) { value += varReturn; } } return value; }; this.toString = function () { return this.get(); }; this.addDebugValues = function (variables) { variables.push({ name: null, type: '_joined', value: this.get() }); }; } function ConstantVariable(value, container) { this.name = ''; this.type = 'ConstantVariable'; this.getDefinition = function () { return value; }; function isVariableDefinition(value) { return value && utils.isObject(value) && !utils.isArray(value) && (utils.hasProperty(value, 'type') || utils.hasProperty(value, 'joinedVariable')); } function deepClone(value) { if (value == null || typeof value !== 'object') { return value; } var newVal = new value.constructor(); var key; for (key in value) { if (utils.hasProperty(value, key)) { newVal[key] = deepClone(value[key]); } } return newVal; } function convertVariableTemplateIfNeeded(value) { var i; if (isVariableDefinition(value)) { value = buildVariable(value, container).get(); } else if (value && utils.isArray(value)) { for (i = 0; i < value.length; i++) { value[i] = convertVariableTemplateIfNeeded(value[i]); } } else if (value && utils.isObject(value)) { for (i in value) { if (utils.hasProperty(value, i)) { value[i] = convertVariableTemplateIfNeeded(value[i]); } } } return value; } this.get = function () { var result = value; if (utils.isObject(result)) { // we potentially make modifications to the initial parameter and therefore need to // clone it to ensure we always resolve all variables. cannot use json.parse(json.stringify) as // there will be functions and objects result = deepClone(result); result= convertVariableTemplateIfNeeded(result); } return result; }; this.toString = function () { return value; }; this.addDebugValues = function (variables) { variables.push({ name: null, type: '_constant', value: this.get() }); }; } function Variable(variable, container) { this.type = variable.type; this.name = variable.name; this.lookUpTable = variable.lookUpTable || []; this.defaultValue = undefined; this.parameters = variable.parameters || {}; this.getDefinition = function () { return variable; }; this.get = function () { var value; try { value = this.theVariable.get(); } catch (e) { Debug.error('Failed to get value of variable', e, this); value = undefined; } if ((!utils.isDefined(value) || value === null || value === false) && utils.isDefined(this.defaultValue)) { value = this.defaultValue; } var i; for (i = 0; i < this.lookUpTable.length; i++) { var lookUp = this.lookUpTable[i]; if (utils.compare(value, lookUp.matchValue, lookUp.comparison)) { return lookUp.outValue; } } return value; }; this.toString = function () { if (this.theVariable && utils.hasProperty(this.theVariable, 'toString') && utils.isFunction(this.theVariable.toString)) { try { return this.theVariable.toString(); } catch (e) { Debug.error('Failed to get toString of variable', e, this); return; } } return this.get(); }; this.addDebugValues = function (variables) { variables.push({ name: this.name, type: this.type, value: this.get() }); }; if ('undefined' !== typeof variable.defaultValue) { this.defaultValue = variable.defaultValue; } if (!utils.isDefined(variable.Variable) || !variable.Variable) { Debug.log('no template defined for variable ', variable); return; } var i, parameters = new TemplateParameters({variable: this, container: container}); if (utils.isObject(variable.parameters)) { for (i in variable.parameters) { if (utils.hasProperty(variable.parameters, i)) { parameters.set(i, buildVariable(variable.parameters[i], container)); } } } if (utils.isFunction(variable.Variable)) { this.theVariable = new variable.Variable(parameters, TagManager); } else if (utils.isObject(variable.Variable)) { this.theVariable = variable.Variable; } else if (variable.Variable in container.templates) { this.theVariable = new container.templates[variable.Variable](parameters, TagManager); } else { throwError('No matching variable template found'); } } function Trigger(trigger, container) { this.referencedTags = []; this.id = trigger.id; this.type = trigger.type; this.name = trigger.name; this.conditions = []; this.parameters = trigger.parameters || {}; var self = this; this.getId = function () { return this.id; }; this.setUp = function () { if (this.theTrigger && this.theTrigger.setUp && utils.isFunction(this.theTrigger.setUp)) { this.theTrigger.setUp(function (event) { dataLayer.push(event); if (!('event' in event)) { return; } var result = { tags: [], variables: [], metTrigger: null, name: event.event, eventData: event, container: {} }; var i, j; if (self.meetsConditions()) { Debug.log('The condition is met for trigger ' + self.name, self); result.metTrigger = {name: self.name, type: self.type}; var tags = self.getReferencedTags(); for (j = 0; j < tags.length; j++) { if (tags[j].hasBlockTrigger(self)) { tags[j].block(); tags[j].addDebugValues(result.tags, 'Block'); } else if (tags[j].hasFireTrigger(self)) { // todo we could further optimize this that when a trigger is no longer needed because // eg the tag can be only triggered once, then we remove the trigger from this.triggers // and ideally even call a teardown method to remove possible event listeners etc! tags[j].fire(); tags[j].addDebugValues(result.tags, 'Fire'); } } } if (window.mtmPreviewWindow || Debug.enabled) { container.addDebugValues(result.container); pushDebugEvent(result); if (Debug.enabled) { Debug.log('event: ', result); } } }); } }; this.addReferencedTag = function (tag) { this.referencedTags.push(tag); }; this.getReferencedTags = function () { return this.referencedTags; }; this.meetsConditions = function () { var i,condition; for (i = 0; i < this.conditions.length; i++) { condition = new Condition(this.conditions[i], container); if (!condition.isValid()) { return false; } } return true; }; if (trigger.conditions && utils.isArray(trigger.conditions)) { this.conditions = trigger.conditions; } var i, parameters = new TemplateParameters({trigger: this, container: container}); if (utils.isObject(trigger.parameters)) { for (i in trigger.parameters) { if (utils.hasProperty(trigger.parameters, i)) { parameters.set(i, buildVariable(trigger.parameters[i], container)); } } } if (!utils.isDefined(trigger.Trigger) || !trigger.Trigger) { Debug.error('no template defined for trigger ', trigger); return; } if (utils.isFunction(trigger.Trigger)) { this.theTrigger = new trigger.Trigger(parameters, TagManager); } else if (utils.isObject(trigger.Trigger)) { this.theTrigger = trigger.Trigger; } else if (trigger.Trigger in container.templates) { this.theTrigger = new container.templates[trigger.Trigger](parameters, TagManager); } else { throwError('No matching trigger template found'); } parameters = null; } function Tag (tag, container) { this.type = tag.type; this.name = tag.name; this.fireTriggerIds = tag.fireTriggerIds ? tag.fireTriggerIds : []; this.blockTriggerIds = tag.blockTriggerIds ? tag.blockTriggerIds : []; this.fireLimit = tag.fireLimit ? tag.fireLimit : Tag.FIRE_LIMIT_UNLIMITED; this.fireDelay = tag.fireDelay ? parseInt(tag.fireDelay, 10) : 0; this.startDate = tag.startDate ? tag.startDate : null; this.endDate = tag.endDate ? tag.endDate : null; this.numExecuted = 0; this.blocked = false; this.parameters = tag.parameters || {}; var self = this; this.addDebugValues = function (tags, action) { tags.push({ action: action, type: this.type, name: this.name, numExecuted: this.numExecuted, }); }; this._doFire = function () { if (this.blocked) { Debug.log('not firing as this tag is blocked', this); return 'tag is blocked'; } if (this.fireLimit !== Tag.FIRE_LIMIT_UNLIMITED && this.numExecuted) { Debug.log('not firing as this tag has limit reached', this); return 'fire limit is restricted'; } var storageKey = 'tag'; if (container.id) { // the same name may be used in different containers therefore need to save it per container storageKey += '_' + container.id; } if (this.fireLimit === Tag.FIRE_LIMIT_ONCE_24HOURS && !window.mtmPreviewWindow) { // in preview/debug mode we make sure to execute it if (localStorage.get(storageKey, this.name)) { Debug.log('not firing as this tag has 24hours limit reached', this); return 'fire limit 24hours is restricted'; } } if (this.fireLimit === Tag.FIRE_LIMIT_ONCE_LIFETIME && !window.mtmPreviewWindow) { // in preview/debug mode we make sure to execute it if (localStorage.get(storageKey, this.name)) { Debug.log('not firing as this tag has limit reached', this); return 'fire limit lifetime is restricted'; } } if (!dateHelper.matchesDateRange(new Date(), this.startDate, this.endDate)) { Debug.log('not firing as this tag does not match date', this); return 'date range does not match'; } if (!this.theTag || !this.theTag.fire) { Debug.log('not firing as tag does not exist anymore', this); return 'tag not found'; } Debug.log('firing this tag', this); this.numExecuted++; if (this.fireLimit === Tag.FIRE_LIMIT_ONCE_24HOURS) { var ttl24Hours = 24 * 60 * 60; localStorage.set(storageKey, this.name, '1', ttl24Hours); } if (this.fireLimit === Tag.FIRE_LIMIT_ONCE_LIFETIME) { localStorage.set(storageKey, this.name, '1'); } this.theTag.fire(); Debug.log('fired this tag', this); }; this.fire = function () { if (this.fireDelay) { setTimeout(function () { self._doFire(); }, this.fireDelay); } else { return this._doFire(); } }; this.block = function () { this.blocked = true; }; this.hasFireTrigger = function (trigger) { if (!this.fireTriggerIds || !this.fireTriggerIds.length) { return false; } if (!trigger) { return false; } var id = trigger.getId(); return utils.indexOfArray(this.fireTriggerIds, id) !== -1; }; this.hasBlockTrigger = function (trigger) { if (!this.blockTriggerIds || !this.blockTriggerIds.length) { return false; } if (!trigger) { return false; } var id = trigger.getId(); return utils.indexOfArray(this.blockTriggerIds, id) !== -1; }; if (!utils.isDefined(tag.Tag) || !tag.Tag) { Debug.error('no template defined for tag ', tag); return; } var i, parameters = new TemplateParameters({tag: this, container: container}); if (utils.isObject(tag.parameters)) { for (i in tag.parameters) { if (utils.hasProperty(tag.parameters, i)) { parameters.set(i, buildVariable(tag.parameters[i], container)); } } } if (utils.isFunction(tag.Tag)) { this.theTag = new tag.Tag(parameters, TagManager); } else if (utils.isObject(tag.Tag)) { this.theTag = tag.Tag; } else if (tag.Tag in container.templates) { this.theTag = new container.templates[tag.Tag](parameters, TagManager); } else { throwError('No matching tag template found'); } } Tag.FIRE_LIMIT_ONCE_PAGE = 'once_page'; Tag.FIRE_LIMIT_ONCE_24HOURS = 'once_24hours'; Tag.FIRE_LIMIT_ONCE_LIFETIME = 'once_lifetime'; Tag.FIRE_LIMIT_UNLIMITED = 'unlimited'; function Container(container, templates) { var self = this; this.id = container.id; this.idsite = container.idsite || null; this.versionName = container.versionName || null; this.revision = container.revision || null; this.environment = container.environment || null; this.templates = templates || {}; this.dataLayer = new DataLayer(); // this.variables currently only used for debug mode actually! this.variables = []; this.triggers = []; this.tags = []; // only there for debugging this.onNewGlobalDataLayerValue = function (value) { this.dataLayer.push(value); }; dataLayer.on(function (value) { self.onNewGlobalDataLayerValue(value); }); this.addDebugValues = function (container) { container.variables = []; var i; for (i = 0; i < this.variables.length; i++) { this.variables[i].addDebugValues(container.variables); } container.tags = []; for (i = 0; i < this.tags.length; i++) { this.tags[i].addDebugValues(container.tags, 'Not Fired Yet'); } container.id = this.id; container.versionName = this.versionName; container.dataLayer = JSON.parse(JSON.stringify(this.dataLayer.values, function( key, value) { if (typeof value === 'object' && value instanceof Node) { return value.nodeName; } else { return value; }; })); }; this.getTriggerById = function (idTrigger){ if (!idTrigger) { return; } var i; for (i = 0; i < this.triggers.length; i++) { if (this.triggers[i].getId() === idTrigger) { return this.triggers[i]; } } }; this.addTrigger = function (triggerObj) { if (!triggerObj) { return; } var triggerInstance = this.getTriggerById(triggerObj.id); if (!triggerInstance) { triggerInstance = new Trigger(triggerObj, this); this.triggers.push(triggerInstance); } return triggerInstance; }; var i, j, tag, tagDefinition, trigger; if (container.variables && utils.isArray(container.variables)) { for (i = 0; i < container.variables.length; i++) { this.variables.push(buildVariable(container.variables[i], this)); } } if (container.triggers && utils.isArray(container.triggers)) { if (container.tags && utils.isArray(container.tags)) { // we need to try and add triggers first that are block triggers. This way block triggers will be // executed before fire triggers (unless a trigger is a block and a fire trigger in which case it // may still cause issues and then a tag delay needs to be used. // this is interesting when you have say 2 page view triggers. The first page view trigger is triggered // and then would immediately fire a tag. Next the second page view trigger will be triggered // immediately afterwards and would then block the previously fired tag. The tag should not have been fired // basically. By sorting them to add triggers that block tags first, these triggers would be executed // first and then the scenario is that basically the 2nd trigger would be executed first and correctly block // the tag. Then the first trigger will be triggered and it won't fire the tag because it was blocked. container.triggers.sort(function (a, b) { var isABlockTrigger = false, isBBlockTrigger = false, tag, z; for (z= 0; z < container.tags.length; z++) { tag = container.tags[z]; if (tag && tag.blockTriggerIds && utils.isArray(tag.blockTriggerIds)) { isABlockTrigger = isABlockTrigger || utils.indexOfArray(tag.blockTriggerIds, a.id) !== -1; isBBlockTrigger = isBBlockTrigger || utils.indexOfArray(tag.blockTriggerIds, b.id) !== -1; } } if (isABlockTrigger && !isBBlockTrigger) { return -1; } else if (isBBlockTrigger && !isABlockTrigger) { return 1; } if (a.id < b.id) { return -1; } return 1; }); } for (i = 0; i < container.triggers.length; i++) { this.addTrigger(container.triggers[i]); } } if (container.tags && utils.isArray(container.tags)) { for (i = 0; i < container.tags.length; i++) { tagDefinition = container.tags[i]; tag = new Tag(tagDefinition, this); this.tags.push(tag); if (tagDefinition.blockTriggerIds && utils.isArray(tagDefinition.blockTriggerIds)) { for (j = 0; j < tagDefinition.blockTriggerIds.length; j++) { trigger = this.getTriggerById(tagDefinition.blockTriggerIds[j]); if (trigger) { trigger.addReferencedTag(tag); } } } if (tagDefinition.fireTriggerIds && utils.isArray(tagDefinition.fireTriggerIds)) { for (j = 0; j < tagDefinition.fireTriggerIds.length; j++) { trigger = this.getTriggerById(tagDefinition.fireTriggerIds[j]); if (trigger) { trigger.addReferencedTag(tag); } } } } } this.run = function () { var missedEvents = dataLayer.getAllEvents(); var i; for (i = 0; i < missedEvents.length; i++) { this.onNewGlobalDataLayerValue(missedEvents[i]); } for (i = 0; i < this.triggers.length; i++) { this.triggers[i].setUp(); } }; } var TagManager = { THROW_ERRORS: true, dataLayer: dataLayer, containers: [], url: urlHelper, date: dateHelper, utils: utils, debug: Debug, dom: DOM, window: windowHelper, Variable: Variable, storage: {local: localStorage, session: sessionStorage}, _buildVariable: buildVariable, Condition: Condition, TemplateParameters: TemplateParameters, Trigger: Trigger, Tag: Tag, throwError: throwError, Container: Container, addContainer: function (containerConfig, templates) { var mtmSetDebugFlag = urlHelper.getQueryParameter('mtmSetDebugFlag'); if (mtmSetDebugFlag) { var idSite = encodeURIComponent(containerConfig.idsite); var containerID = encodeURIComponent(containerConfig.id); if (mtmSetDebugFlag == 1) { var date = new Date(); date.setTime(date.getTime() + (7 * 24 * 60 * 60 * 1000)); document.cookie = 'mtmPreviewMode=mtmPreview' + idSite + '_' + containerID + '%3D1;expires=' + date.toUTCString() + ';SameSite=Lax'; } else { document.cookie = 'mtmPreviewMode=mtmPreview' + idSite + '_' + containerID + '%3D1;expires=Thu, 01 Jan 1970 00:00:00 UTC;SameSite=Lax'; window.close(); } } if (!window.mtmPreviewWindow) { // interesting when multiple containers are registered... we check if meanwhile a // debug container has been added var previewFrame = documentAlias.getElementById('mtmDebugFrame'); if (previewFrame && previewFrame.contentWindow) { window.mtmPreviewWindow = previewFrame.contentWindow; } } Debug.log('creating container'); var container = new Container(containerConfig, templates); this.containers.push(container); container.dataLayer.push({'mtm.containerId': container.id}); Debug.log('running container'); container.run(); return container; }, enableDebugMode: function () { Debug.enabled = true; } }; if ('matomoTagManagerAsyncInit' in windowAlias && utils.isFunction(windowAlias.matomoTagManagerAsyncInit)) { windowAlias.matomoTagManagerAsyncInit(TagManager); } function processMtmPush() { var i, j, methodName, parameterArray, theCall; for (i = 0; i < arguments.length; i += 1) { theCall = null; if (arguments[i] && arguments[i].slice) { theCall = arguments[i].slice(); } parameterArray = arguments[i]; if (utils.isObject(parameterArray) && !utils.isArray(parameterArray)) { dataLayer.push(parameterArray); // we assume dataLayer push continue; } methodName = parameterArray.shift(); var isStaticPluginCall = utils.isString(methodName) && methodName.indexOf('::') > 0; if (isStaticPluginCall) { var fParts, context; // a static method will not be called on a tracker and is not dependent on the existence of a // tracker etc fParts = methodName.split('::'); context = fParts[0]; methodName = fParts[1]; if ('object' === typeof TagManager[context] && utils.isFunction(TagManager[context][methodName])) { TagManager[context][methodName].apply(TagManager[context], parameterArray); } } else { if (methodName && methodName in TagManager && utils.isFunction(TagManager[methodName])) { TagManager[methodName].apply(TagManager, parameterArray); } else { Debug.error('method ' + methodName + ' is not valid'); } } } } utils.setMethodWrapIfNeeded(windowAlias._mtm, 'push', processMtmPush); var i; for (i = 0; i < windowAlias._mtm.length; i++) { processMtmPush(windowAlias._mtm[i]); } dataLayer.push({'mtm.mtmScriptLoadedTime': timeScriptLoaded}); if ('undefined' !== typeof windowAlias.dataLayer && utils.isArray(windowAlias.dataLayer)) { // compatibility for GTM for ( i = 0; i < windowAlias.dataLayer.length; i++) { if (utils.isObject(windowAlias.dataLayer[i])) { dataLayer.push(windowAlias.dataLayer[i]); } } } return TagManager; })(); } // we initialize container outside regular code so multiple containers can be embedded on the same site /*!! initContainerHook */ })();