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

github.com/matomo-org/matomo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/js
diff options
context:
space:
mode:
authorThomas Steur <tsteur@users.noreply.github.com>2019-10-07 04:00:20 +0300
committerGitHub <noreply@github.com>2019-10-07 04:00:20 +0300
commit2bf7ef3dc62fea80da435dc6e906836e99970097 (patch)
tree842e3e6948f19a57ca6139624ee27fe51ceb1582 /js
parent8f7be872a1197e1e04093792043307f077e66d45 (diff)
Remove accidentally committed file (#14960)
* Remove accidentally committed file Only piwik.min.js should exist in that directory. * Delete piwik.js.orig
Diffstat (limited to 'js')
-rw-r--r--js/piwik-min.js76
-rw-r--r--js/piwik.js.orig6972
2 files changed, 0 insertions, 7048 deletions
diff --git a/js/piwik-min.js b/js/piwik-min.js
deleted file mode 100644
index 155dce5f5a..0000000000
--- a/js/piwik-min.js
+++ /dev/null
@@ -1,76 +0,0 @@
-/*!!
- * Matomo - free/libre analytics platform
- *
- * JavaScript tracking client
- *
- * @link https://matomo.org
- * @source https://github.com/piwik/piwik/blob/master/js/piwik.js
- * @license http://piwik.org/free-software/bsd/ BSD-3 Clause (also in js/LICENSE.txt)
- * @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
- */
-if(typeof JSON_PIWIK!=="object"&&typeof window.JSON==="object"&&window.JSON.stringify&&window.JSON.parse){JSON_PIWIK=window.JSON}else{(function(){var a={};
-/*!! JSON v3.3.2 | http://bestiejs.github.io/json3 | Copyright 2012-2014, Kit Cambridge | http://kit.mit-license.org */
-(function(){var c=typeof define==="function"&&define.amd;var e={"function":true,object:true};var h=e[typeof a]&&a&&!a.nodeType&&a;var i=e[typeof window]&&window||this,b=h&&e[typeof module]&&module&&!module.nodeType&&typeof global=="object"&&global;if(b&&(b.global===b||b.window===b||b.self===b)){i=b}function j(ab,V){ab||(ab=i.Object());V||(V=i.Object());var K=ab.Number||i.Number,R=ab.String||i.String,x=ab.Object||i.Object,S=ab.Date||i.Date,T=ab.SyntaxError||i.SyntaxError,aa=ab.TypeError||i.TypeError,J=ab.Math||i.Math,Y=ab.JSON||i.JSON;
-if(typeof Y=="object"&&Y){V.stringify=Y.stringify;V.parse=Y.parse}var n=x.prototype,u=n.toString,r,m,L;var B=new S(-3509827334573292);try{B=B.getUTCFullYear()==-109252&&B.getUTCMonth()===0&&B.getUTCDate()===1&&B.getUTCHours()==10&&B.getUTCMinutes()==37&&B.getUTCSeconds()==6&&B.getUTCMilliseconds()==708}catch(v){}function o(ac){if(o[ac]!==L){return o[ac]}var ad;if(ac=="bug-string-char-index"){ad="a"[0]!="a"}else{if(ac=="json"){ad=o("json-stringify")&&o("json-parse")}else{var ak,ah='{"a":[1,true,false,null,"\\u0000\\b\\n\\f\\r\\t"]}';if(ac=="json-stringify"){var ai=V.stringify,aj=typeof ai=="function"&&B;if(aj){(ak=function(){return 1}).toJSON=ak;try{aj=ai(0)==="0"&&ai(new K())==="0"&&ai(new R())=='""'&&ai(u)===L&&ai(L)===L&&ai()===L&&ai(ak)==="1"&&ai([ak])=="[1]"&&ai([L])=="[null]"&&ai(null)=="null"&&ai([L,u,null])=="[null,null,null]"&&ai({a:[ak,true,false,null,"\x00\b\n\f\r\t"]})==ah&&ai(null,ak)==="1"&&ai([1,2],null,1)=="[\n 1,\n 2\n]"&&ai(new S(-8640000000000000))=='"-271821-04-20T00:00:00.000Z"'&&ai(new S(8640000000000000))=='"+275760-09-13T00:00:00.000Z"'&&ai(new S(-62198755200000))=='"-000001-01-01T00:00:00.000Z"'&&ai(new S(-1))=='"1969-12-31T23:59:59.999Z"'
-}catch(ae){aj=false}}ad=aj}if(ac=="json-parse"){var ag=V.parse;if(typeof ag=="function"){try{if(ag("0")===0&&!ag(false)){ak=ag(ah);var af=ak.a.length==5&&ak.a[0]===1;if(af){try{af=!ag('"\t"')}catch(ae){}if(af){try{af=ag("01")!==1}catch(ae){}}if(af){try{af=ag("1.")!==1}catch(ae){}}}}}catch(ae){af=false}}ad=af}}}return o[ac]=!!ad}if(!o("json")){var U="[object Function]",Q="[object Date]",N="[object Number]",O="[object String]",E="[object Array]",A="[object Boolean]";var F=o("bug-string-char-index");if(!B){var s=J.floor;var Z=[0,31,59,90,120,151,181,212,243,273,304,334];var D=function(ac,ad){return Z[ad]+365*(ac-1970)+s((ac-1969+(ad=+(ad>1)))/4)-s((ac-1901+ad)/100)+s((ac-1601+ad)/400)}}if(!(r=n.hasOwnProperty)){r=function(ae){var ac={},ad;if((ac.__proto__=null,ac.__proto__={toString:1},ac).toString!=u){r=function(ah){var ag=this.__proto__,af=ah in (this.__proto__=null,this);this.__proto__=ag;return af}}else{ad=ac.constructor;r=function(ag){var af=(this.constructor||ad).prototype;return ag in this&&!(ag in af&&this[ag]===af[ag])
-}}ac=null;return r.call(this,ae)}}m=function(ae,ah){var af=0,ac,ad,ag;(ac=function(){this.valueOf=0}).prototype.valueOf=0;ad=new ac();for(ag in ad){if(r.call(ad,ag)){af++}}ac=ad=null;if(!af){ad=["valueOf","toString","toLocaleString","propertyIsEnumerable","isPrototypeOf","hasOwnProperty","constructor"];m=function(aj,an){var am=u.call(aj)==U,al,ak;var ai=!am&&typeof aj.constructor!="function"&&e[typeof aj.hasOwnProperty]&&aj.hasOwnProperty||r;for(al in aj){if(!(am&&al=="prototype")&&ai.call(aj,al)){an(al)}}for(ak=ad.length;al=ad[--ak];ai.call(aj,al)&&an(al)){}}}else{if(af==2){m=function(aj,am){var ai={},al=u.call(aj)==U,ak;for(ak in aj){if(!(al&&ak=="prototype")&&!r.call(ai,ak)&&(ai[ak]=1)&&r.call(aj,ak)){am(ak)}}}}else{m=function(aj,am){var al=u.call(aj)==U,ak,ai;for(ak in aj){if(!(al&&ak=="prototype")&&r.call(aj,ak)&&!(ai=ak==="constructor")){am(ak)}}if(ai||r.call(aj,(ak="constructor"))){am(ak)}}}}return m(ae,ah)};if(!o("json-stringify")){var q={92:"\\\\",34:'\\"',8:"\\b",12:"\\f",10:"\\n",13:"\\r",9:"\\t"};
-var I="000000";var t=function(ac,ad){return(I+(ad||0)).slice(-ac)};var z="\\u00";var C=function(ai){var ad='"',ag=0,ah=ai.length,ac=!F||ah>10;var af=ac&&(F?ai.split(""):ai);for(;ag<ah;ag++){var ae=ai.charCodeAt(ag);switch(ae){case 8:case 9:case 10:case 12:case 13:case 34:case 92:ad+=q[ae];break;default:if(ae<32){ad+=z+t(2,ae.toString(16));break}ad+=ac?af[ag]:ai.charAt(ag)}}return ad+'"'};var p=function(ai,aA,ag,al,ax,ac,aj){var at,ae,ap,az,ay,ak,aw,au,aq,an,ar,ad,ah,af,av,ao;try{at=aA[ai]}catch(am){}if(typeof at=="object"&&at){ae=u.call(at);if(ae==Q&&!r.call(at,"toJSON")){if(at>-1/0&&at<1/0){if(D){ay=s(at/86400000);for(ap=s(ay/365.2425)+1970-1;D(ap+1,0)<=ay;ap++){}for(az=s((ay-D(ap,0))/30.42);D(ap,az+1)<=ay;az++){}ay=1+ay-D(ap,az);ak=(at%86400000+86400000)%86400000;aw=s(ak/3600000)%24;au=s(ak/60000)%60;aq=s(ak/1000)%60;an=ak%1000}else{ap=at.getUTCFullYear();az=at.getUTCMonth();ay=at.getUTCDate();aw=at.getUTCHours();au=at.getUTCMinutes();aq=at.getUTCSeconds();an=at.getUTCMilliseconds()}at=(ap<=0||ap>=10000?(ap<0?"-":"+")+t(6,ap<0?-ap:ap):t(4,ap))+"-"+t(2,az+1)+"-"+t(2,ay)+"T"+t(2,aw)+":"+t(2,au)+":"+t(2,aq)+"."+t(3,an)+"Z"
-}else{at=null}}else{if(typeof at.toJSON=="function"&&((ae!=N&&ae!=O&&ae!=E)||r.call(at,"toJSON"))){at=at.toJSON(ai)}}}if(ag){at=ag.call(aA,ai,at)}if(at===null){return"null"}ae=u.call(at);if(ae==A){return""+at}else{if(ae==N){return at>-1/0&&at<1/0?""+at:"null"}else{if(ae==O){return C(""+at)}}}if(typeof at=="object"){for(af=aj.length;af--;){if(aj[af]===at){throw aa()}}aj.push(at);ar=[];av=ac;ac+=ax;if(ae==E){for(ah=0,af=at.length;ah<af;ah++){ad=p(ah,at,ag,al,ax,ac,aj);ar.push(ad===L?"null":ad)}ao=ar.length?(ax?"[\n"+ac+ar.join(",\n"+ac)+"\n"+av+"]":("["+ar.join(",")+"]")):"[]"}else{m(al||at,function(aC){var aB=p(aC,at,ag,al,ax,ac,aj);if(aB!==L){ar.push(C(aC)+":"+(ax?" ":"")+aB)}});ao=ar.length?(ax?"{\n"+ac+ar.join(",\n"+ac)+"\n"+av+"}":("{"+ar.join(",")+"}")):"{}"}aj.pop();return ao}};V.stringify=function(ac,ae,af){var ad,al,aj,ai;if(e[typeof ae]&&ae){if((ai=u.call(ae))==U){al=ae}else{if(ai==E){aj={};for(var ah=0,ag=ae.length,ak;ah<ag;ak=ae[ah++],((ai=u.call(ak)),ai==O||ai==N)&&(aj[ak]=1)){}}}}if(af){if((ai=u.call(af))==N){if((af-=af%1)>0){for(ad="",af>10&&(af=10);
-ad.length<af;ad+=" "){}}}else{if(ai==O){ad=af.length<=10?af:af.slice(0,10)}}}return p("",(ak={},ak[""]=ac,ak),al,aj,ad,"",[])}}if(!o("json-parse")){var M=R.fromCharCode;var l={92:"\\",34:'"',47:"/",98:"\b",116:"\t",110:"\n",102:"\f",114:"\r"};var G,X;var H=function(){G=X=null;throw T()};var y=function(){var ah=X,af=ah.length,ag,ae,ac,ai,ad;while(G<af){ad=ah.charCodeAt(G);switch(ad){case 9:case 10:case 13:case 32:G++;break;case 123:case 125:case 91:case 93:case 58:case 44:ag=F?ah.charAt(G):ah[G];G++;return ag;case 34:for(ag="@",G++;G<af;){ad=ah.charCodeAt(G);if(ad<32){H()}else{if(ad==92){ad=ah.charCodeAt(++G);switch(ad){case 92:case 34:case 47:case 98:case 116:case 110:case 102:case 114:ag+=l[ad];G++;break;case 117:ae=++G;for(ac=G+4;G<ac;G++){ad=ah.charCodeAt(G);if(!(ad>=48&&ad<=57||ad>=97&&ad<=102||ad>=65&&ad<=70)){H()}}ag+=M("0x"+ah.slice(ae,G));break;default:H()}}else{if(ad==34){break}ad=ah.charCodeAt(G);ae=G;while(ad>=32&&ad!=92&&ad!=34){ad=ah.charCodeAt(++G)}ag+=ah.slice(ae,G)}}}if(ah.charCodeAt(G)==34){G++;
-return ag}H();default:ae=G;if(ad==45){ai=true;ad=ah.charCodeAt(++G)}if(ad>=48&&ad<=57){if(ad==48&&((ad=ah.charCodeAt(G+1)),ad>=48&&ad<=57)){H()}ai=false;for(;G<af&&((ad=ah.charCodeAt(G)),ad>=48&&ad<=57);G++){}if(ah.charCodeAt(G)==46){ac=++G;for(;ac<af&&((ad=ah.charCodeAt(ac)),ad>=48&&ad<=57);ac++){}if(ac==G){H()}G=ac}ad=ah.charCodeAt(G);if(ad==101||ad==69){ad=ah.charCodeAt(++G);if(ad==43||ad==45){G++}for(ac=G;ac<af&&((ad=ah.charCodeAt(ac)),ad>=48&&ad<=57);ac++){}if(ac==G){H()}G=ac}return +ah.slice(ae,G)}if(ai){H()}if(ah.slice(G,G+4)=="true"){G+=4;return true}else{if(ah.slice(G,G+5)=="false"){G+=5;return false}else{if(ah.slice(G,G+4)=="null"){G+=4;return null}}}H()}}return"$"};var W=function(ad){var ac,ae;if(ad=="$"){H()}if(typeof ad=="string"){if((F?ad.charAt(0):ad[0])=="@"){return ad.slice(1)}if(ad=="["){ac=[];for(;;ae||(ae=true)){ad=y();if(ad=="]"){break}if(ae){if(ad==","){ad=y();if(ad=="]"){H()}}else{H()}}if(ad==","){H()}ac.push(W(ad))}return ac}else{if(ad=="{"){ac={};for(;;ae||(ae=true)){ad=y();
-if(ad=="}"){break}if(ae){if(ad==","){ad=y();if(ad=="}"){H()}}else{H()}}if(ad==","||typeof ad!="string"||(F?ad.charAt(0):ad[0])!="@"||y()!=":"){H()}ac[ad.slice(1)]=W(y())}return ac}}H()}return ad};var P=function(ae,ad,af){var ac=w(ae,ad,af);if(ac===L){delete ae[ad]}else{ae[ad]=ac}};var w=function(af,ae,ag){var ad=af[ae],ac;if(typeof ad=="object"&&ad){if(u.call(ad)==E){for(ac=ad.length;ac--;){P(ad,ac,ag)}}else{m(ad,function(ah){P(ad,ah,ag)})}}return ag.call(af,ae,ad)};V.parse=function(ae,af){var ac,ad;G=0;X=""+ae;ac=W(y());if(y()!="$"){H()}G=X=null;return af&&u.call(af)==U?w((ad={},ad[""]=ac,ad),"",af):ac}}}V.runInContext=j;return V}if(h&&!c){j(i,h)}else{var f=i.JSON,k=i.JSON3,d=false;var g=j(i,(i.JSON3={noConflict:function(){if(!d){d=true;i.JSON=f;i.JSON3=k;f=k=null}return g}}));i.JSON={parse:g.parse,stringify:g.stringify}}if(c){define(function(){return g})}}).call(this);JSON_PIWIK=a})()}if(typeof _paq!=="object"){_paq=[]}if(typeof window.Piwik!=="object"){window.Piwik=(function(){var p,a={},v={},D=document,g=navigator,T=screen,Q=window,h=Q.performance||Q.mozPerformance||Q.msPerformance||Q.webkitPerformance,r=Q.encodeURIComponent,P=Q.decodeURIComponent,k=unescape,F=[],B,d,aa=[];
-function n(ah){try{return P(ah)}catch(ai){return unescape(ah)}}function G(ai){var ah=typeof ai;return ah!=="undefined"}function x(ah){return typeof ah==="function"}function S(ah){return typeof ah==="object"}function u(ah){return typeof ah==="string"||ah instanceof String}function y(ai){if(!ai){return true}var ah;var aj=true;for(ah in ai){if(Object.prototype.hasOwnProperty.call(ai,ah)){aj=false}}return aj}function ad(ah){if(console!==undefined&&console&&console.error){console.error(ah)}}function Z(){var am,al,ao,ai,ah;for(am=0;am<arguments.length;am+=1){ah=null;if(arguments[am]&&arguments[am].slice){ah=arguments[am].slice()}ai=arguments[am];ao=ai.shift();var an,aj;var ak=u(ao)&&ao.indexOf("::")>0;if(ak){an=ao.split("::");aj=an[0];ao=an[1];if("object"===typeof d[aj]&&"function"===typeof d[aj][ao]){d[aj][ao].apply(d[aj],ai)}else{if(ah){aa.push(ah)}}}else{for(al=0;al<F.length;al++){if(u(ao)){aj=F[al];var ap=ao.indexOf(".")>0;if(ap){an=ao.split(".");if(aj&&"object"===typeof aj[an[0]]){aj=aj[an[0]];
-ao=an[1]}else{if(ah){aa.push(ah);break}}}if(aj[ao]){aj[ao].apply(aj,ai)}else{var aq="The method '"+ao+'\' was not found in "_paq" variable. Please have a look at the Piwik tracker documentation: http://developer.piwik.org/api-reference/tracking-javascript';ad(aq);if(!ap){throw new TypeError(aq)}}if(ao==="addTracker"){break}if(ao==="setTrackerUrl"||ao==="setSiteId"){break}}else{ao.apply(F[al],ai)}}}}}function ag(ak,aj,ai,ah){if(ak.addEventListener){ak.addEventListener(aj,ai,ah);return true}if(ak.attachEvent){return ak.attachEvent("on"+aj,ai)}ak["on"+aj]=ai}function l(ah){if(D.readyState==="complete"){ah()}else{if(Q.addEventListener){Q.addEventListener("load",ah)}else{if(Q.attachEvent){Q.attachEvent("onload",ah)}}}}function o(ak){var ah=false;if(D.attachEvent){ah=D.readyState==="complete"}else{ah=D.readyState!=="loading"}if(ah){ak();return}var aj;if(D.addEventListener){ag(D,"DOMContentLoaded",function ai(){D.removeEventListener("DOMContentLoaded",ai,false);if(!ah){ah=true;ak()}})}else{if(D.attachEvent){D.attachEvent("onreadystatechange",function ai(){if(D.readyState==="complete"){D.detachEvent("onreadystatechange",ai);
-if(!ah){ah=true;ak()}}});if(D.documentElement.doScroll&&Q===Q.top){(function ai(){if(!ah){try{D.documentElement.doScroll("left")}catch(al){setTimeout(ai,0);return}ah=true;ak()}}())}}}ag(Q,"load",function(){if(!ah){ah=true;ak()}},false)}function W(ai,an,ao){if(!ai){return""}var ah="",ak,aj,al,am;for(ak in a){if(Object.prototype.hasOwnProperty.call(a,ak)){am=a[ak]&&"function"===typeof a[ak][ai];if(am){aj=a[ak][ai];al=aj(an||{},ao);if(al){ah+=al}}}}return ah}function ab(){var ah;W("unload");if(p){do{ah=new Date()}while(ah.getTimeAlias()<p)}}function m(aj,ai){var ah=D.createElement("script");ah.type="text/javascript";ah.src=aj;if(ah.readyState){ah.onreadystatechange=function(){var ak=this.readyState;if(ak==="loaded"||ak==="complete"){ah.onreadystatechange=null;ai()}}}else{ah.onload=ai}D.getElementsByTagName("head")[0].appendChild(ah)}function H(){var ah="";try{ah=Q.top.document.referrer}catch(aj){if(Q.parent){try{ah=Q.parent.document.referrer}catch(ai){ah=""}}}if(ah===""){ah=D.referrer}return ah
-}function q(ah){var aj=new RegExp("^([a-z]+):"),ai=aj.exec(ah);return ai?ai[1]:null}function c(ah){var aj=new RegExp("^(?:(?:https?|ftp):)/*(?:[^@]+@)?([^:/#]+)"),ai=aj.exec(ah);return ai?ai[1]:ah}function ac(ai,ah){ai=String(ai);return ai.lastIndexOf(ah,0)===0}function O(ai,ah){ai=String(ai);return ai.indexOf(ah,ai.length-ah.length)!==-1}function w(ai,ah){ai=String(ai);return ai.indexOf(ah)!==-1}function f(ai,ah){ai=String(ai);return ai.substr(0,ai.length-ah)}function C(ak,aj,am){ak=String(ak);if(!am){am=""}var ah=ak.indexOf("#");var an=ak.length;if(ah===-1){ah=an}var al=ak.substr(0,ah);var ai=ak.substr(ah,an-ah);if(al.indexOf("?")===-1){al+="?"}else{if(!O(al,"?")){al+="&"}}return al+r(aj)+"="+r(am)+ai}function j(ai,aj){ai=String(ai);if(ai.indexOf("?"+aj+"=")===-1&&ai.indexOf("&"+aj+"=")===-1){return ai}var ak=ai.indexOf("?");if(ak===-1){return ai}var ah=ai.substr(ak+1);var ao=ai.substr(0,ak);if(ah){var ap="";var ar=ah.indexOf("#");if(ar!==-1){ap=ah.substr(ar+1);ah=ah.substr(0,ar)}var al;
-var an=ah.split("&");var am=an.length-1;for(am;am>=0;am--){al=an[am].split("=")[0];if(al===aj){an.splice(am,1)}}var aq=an.join("&");if(aq){ao=ao+"?"+aq}if(ap){ao+="#"+ap}}return ao}function e(aj,ai){var ah="[\\?&#]"+ai+"=([^&#]*)";var al=new RegExp(ah);var ak=al.exec(aj);return ak?P(ak[1]):""}function A(ah){return unescape(r(ah))}function af(ax){var aj=function(aD,aC){return(aD<<aC)|(aD>>>(32-aC))},ay=function(aF){var aD="",aE,aC;for(aE=7;aE>=0;aE--){aC=(aF>>>(aE*4))&15;aD+=aC.toString(16)}return aD},am,aA,az,ai=[],aq=1732584193,ao=4023233417,an=2562383102,al=271733878,ak=3285377520,aw,av,au,at,ar,aB,ah,ap=[];ax=A(ax);ah=ax.length;for(aA=0;aA<ah-3;aA+=4){az=ax.charCodeAt(aA)<<24|ax.charCodeAt(aA+1)<<16|ax.charCodeAt(aA+2)<<8|ax.charCodeAt(aA+3);ap.push(az)}switch(ah&3){case 0:aA=2147483648;break;case 1:aA=ax.charCodeAt(ah-1)<<24|8388608;break;case 2:aA=ax.charCodeAt(ah-2)<<24|ax.charCodeAt(ah-1)<<16|32768;break;case 3:aA=ax.charCodeAt(ah-3)<<24|ax.charCodeAt(ah-2)<<16|ax.charCodeAt(ah-1)<<8|128;
-break}ap.push(aA);while((ap.length&15)!==14){ap.push(0)}ap.push(ah>>>29);ap.push((ah<<3)&4294967295);for(am=0;am<ap.length;am+=16){for(aA=0;aA<16;aA++){ai[aA]=ap[am+aA]}for(aA=16;aA<=79;aA++){ai[aA]=aj(ai[aA-3]^ai[aA-8]^ai[aA-14]^ai[aA-16],1)}aw=aq;av=ao;au=an;at=al;ar=ak;for(aA=0;aA<=19;aA++){aB=(aj(aw,5)+((av&au)|(~av&at))+ar+ai[aA]+1518500249)&4294967295;ar=at;at=au;au=aj(av,30);av=aw;aw=aB}for(aA=20;aA<=39;aA++){aB=(aj(aw,5)+(av^au^at)+ar+ai[aA]+1859775393)&4294967295;ar=at;at=au;au=aj(av,30);av=aw;aw=aB}for(aA=40;aA<=59;aA++){aB=(aj(aw,5)+((av&au)|(av&at)|(au&at))+ar+ai[aA]+2400959708)&4294967295;ar=at;at=au;au=aj(av,30);av=aw;aw=aB}for(aA=60;aA<=79;aA++){aB=(aj(aw,5)+(av^au^at)+ar+ai[aA]+3395469782)&4294967295;ar=at;at=au;au=aj(av,30);av=aw;aw=aB}aq=(aq+aw)&4294967295;ao=(ao+av)&4294967295;an=(an+au)&4294967295;al=(al+at)&4294967295;ak=(ak+ar)&4294967295}aB=ay(aq)+ay(ao)+ay(an)+ay(al)+ay(ak);return aB.toLowerCase()}function V(aj,ah,ai){if(!aj){aj=""}if(!ah){ah=""}if(aj==="translate.googleusercontent.com"){if(ai===""){ai=ah
-}ah=e(ah,"u");aj=c(ah)}else{if(aj==="cc.bingj.com"||aj==="webcache.googleusercontent.com"||aj.slice(0,5)==="74.6."){ah=D.links[0].href;aj=c(ah)}}return[aj,ah,ai]}function I(ai){var ah=ai.length;if(ai.charAt(--ah)==="."){ai=ai.slice(0,ah)}if(ai.slice(0,2)==="*."){ai=ai.slice(1)}if(ai.indexOf("/")!==-1){ai=ai.substr(0,ai.indexOf("/"))}return ai}function ae(ai){ai=ai&&ai.text?ai.text:ai;if(!u(ai)){var ah=D.getElementsByTagName("title");if(ah&&G(ah[0])){ai=ah[0].text}}return ai}function M(ah){if(!ah){return[]}if(!G(ah.children)&&G(ah.childNodes)){return ah.children}if(G(ah.children)){return ah.children}return[]}function R(ai,ah){if(!ai||!ah){return false}if(ai.contains){return ai.contains(ah)}if(ai===ah){return true}if(ai.compareDocumentPosition){return !!(ai.compareDocumentPosition(ah)&16)}return false}function J(aj,ak){if(aj&&aj.indexOf){return aj.indexOf(ak)}if(!G(aj)||aj===null){return -1}if(!aj.length){return -1}var ah=aj.length;if(ah===0){return -1}var ai=0;while(ai<ah){if(aj[ai]===ak){return ai
-}ai++}return -1}function i(aj){if(!aj){return false}function ah(al,am){if(Q.getComputedStyle){return D.defaultView.getComputedStyle(al,null)[am]}if(al.currentStyle){return al.currentStyle[am]}}function ak(al){al=al.parentNode;while(al){if(al===D){return true}al=al.parentNode}return false}function ai(an,au,al,aq,ao,ar,ap){var am=an.parentNode,at=1;if(!ak(an)){return false}if(9===am.nodeType){return true}if("0"===ah(an,"opacity")||"none"===ah(an,"display")||"hidden"===ah(an,"visibility")){return false}if(!G(au)||!G(al)||!G(aq)||!G(ao)||!G(ar)||!G(ap)){au=an.offsetTop;ao=an.offsetLeft;aq=au+an.offsetHeight;al=ao+an.offsetWidth;ar=an.offsetWidth;ap=an.offsetHeight}if(aj===an&&(0===ap||0===ar)&&"hidden"===ah(an,"overflow")){return false}if(am){if(("hidden"===ah(am,"overflow")||"scroll"===ah(am,"overflow"))){if(ao+at>am.offsetWidth+am.scrollLeft||ao+ar-at<am.scrollLeft||au+at>am.offsetHeight+am.scrollTop||au+ap-at<am.scrollTop){return false}}if(an.offsetParent===am){ao+=am.offsetLeft;au+=am.offsetTop
-}return ai(am,au,al,aq,ao,ar,ap)}return true}return ai(aj)}var Y={htmlCollectionToArray:function(aj){var ah=[],ai;if(!aj||!aj.length){return ah}for(ai=0;ai<aj.length;ai++){ah.push(aj[ai])}return ah},find:function(ah){if(!document.querySelectorAll||!ah){return[]}var ai=document.querySelectorAll(ah);return this.htmlCollectionToArray(ai)},findMultiple:function(aj){if(!aj||!aj.length){return[]}var ai,ak;var ah=[];for(ai=0;ai<aj.length;ai++){ak=this.find(aj[ai]);ah=ah.concat(ak)}ah=this.makeNodesUnique(ah);return ah},findNodesByTagName:function(ai,ah){if(!ai||!ah||!ai.getElementsByTagName){return[]}var aj=ai.getElementsByTagName(ah);return this.htmlCollectionToArray(aj)},makeNodesUnique:function(ah){var am=[].concat(ah);ah.sort(function(ao,an){if(ao===an){return 0}var aq=J(am,ao);var ap=J(am,an);if(aq===ap){return 0}return aq>ap?-1:1});if(ah.length<=1){return ah}var ai=0;var ak=0;var al=[];var aj;aj=ah[ai++];while(aj){if(aj===ah[ai]){ak=al.push(ai)}aj=ah[ai++]||null}while(ak--){ah.splice(al[ak],1)
-}return ah},getAttributeValueFromNode:function(al,aj){if(!this.hasNodeAttribute(al,aj)){return}if(al&&al.getAttribute){return al.getAttribute(aj)}if(!al||!al.attributes){return}var ak=(typeof al.attributes[aj]);if("undefined"===ak){return}if(al.attributes[aj].value){return al.attributes[aj].value}if(al.attributes[aj].nodeValue){return al.attributes[aj].nodeValue}var ai;var ah=al.attributes;if(!ah){return}for(ai=0;ai<ah.length;ai++){if(ah[ai].nodeName===aj){return ah[ai].nodeValue}}return null},hasNodeAttributeWithValue:function(ai,ah){var aj=this.getAttributeValueFromNode(ai,ah);return !!aj},hasNodeAttribute:function(aj,ah){if(aj&&aj.hasAttribute){return aj.hasAttribute(ah)}if(aj&&aj.attributes){var ai=(typeof aj.attributes[ah]);return"undefined"!==ai}return false},hasNodeCssClass:function(aj,ah){if(aj&&ah&&aj.className){var ai=typeof aj.className==="string"?aj.className.split(" "):[];if(-1!==J(ai,ah)){return true}}return false},findNodesHavingAttribute:function(al,aj,ah){if(!ah){ah=[]}if(!al||!aj){return ah
-}var ak=M(al);if(!ak||!ak.length){return ah}var ai,am;for(ai=0;ai<ak.length;ai++){am=ak[ai];if(this.hasNodeAttribute(am,aj)){ah.push(am)}ah=this.findNodesHavingAttribute(am,aj,ah)}return ah},findFirstNodeHavingAttribute:function(aj,ai){if(!aj||!ai){return}if(this.hasNodeAttribute(aj,ai)){return aj}var ah=this.findNodesHavingAttribute(aj,ai);if(ah&&ah.length){return ah[0]}},findFirstNodeHavingAttributeWithValue:function(ak,aj){if(!ak||!aj){return}if(this.hasNodeAttributeWithValue(ak,aj)){return ak}var ah=this.findNodesHavingAttribute(ak,aj);if(!ah||!ah.length){return}var ai;for(ai=0;ai<ah.length;ai++){if(this.getAttributeValueFromNode(ah[ai],aj)){return ah[ai]}}},findNodesHavingCssClass:function(al,ak,ah){if(!ah){ah=[]}if(!al||!ak){return ah}if(al.getElementsByClassName){var am=al.getElementsByClassName(ak);return this.htmlCollectionToArray(am)}var aj=M(al);if(!aj||!aj.length){return[]}var ai,an;for(ai=0;ai<aj.length;ai++){an=aj[ai];if(this.hasNodeCssClass(an,ak)){ah.push(an)}ah=this.findNodesHavingCssClass(an,ak,ah)
-}return ah},findFirstNodeHavingClass:function(aj,ai){if(!aj||!ai){return}if(this.hasNodeCssClass(aj,ai)){return aj}var ah=this.findNodesHavingCssClass(aj,ai);if(ah&&ah.length){return ah[0]}},isLinkElement:function(ai){if(!ai){return false}var ah=String(ai.nodeName).toLowerCase();var ak=["a","area"];var aj=J(ak,ah);return aj!==-1},setAnyAttribute:function(ai,ah,aj){if(!ai||!ah){return}if(ai.setAttribute){ai.setAttribute(ah,aj)}else{ai[ah]=aj}}};var t={CONTENT_ATTR:"data-track-content",CONTENT_CLASS:"piwikTrackContent",CONTENT_NAME_ATTR:"data-content-name",CONTENT_PIECE_ATTR:"data-content-piece",CONTENT_PIECE_CLASS:"piwikContentPiece",CONTENT_TARGET_ATTR:"data-content-target",CONTENT_TARGET_CLASS:"piwikContentTarget",CONTENT_IGNOREINTERACTION_ATTR:"data-content-ignoreinteraction",CONTENT_IGNOREINTERACTION_CLASS:"piwikContentIgnoreInteraction",location:undefined,findContentNodes:function(){var ai="."+this.CONTENT_CLASS;var ah="["+this.CONTENT_ATTR+"]";var aj=Y.findMultiple([ai,ah]);return aj
-},findContentNodesWithinNode:function(ak){if(!ak){return[]}var ai=Y.findNodesHavingCssClass(ak,this.CONTENT_CLASS);var ah=Y.findNodesHavingAttribute(ak,this.CONTENT_ATTR);if(ah&&ah.length){var aj;for(aj=0;aj<ah.length;aj++){ai.push(ah[aj])}}if(Y.hasNodeAttribute(ak,this.CONTENT_ATTR)){ai.push(ak)}else{if(Y.hasNodeCssClass(ak,this.CONTENT_CLASS)){ai.push(ak)}}ai=Y.makeNodesUnique(ai);return ai},findParentContentNode:function(ai){if(!ai){return}var aj=ai;var ah=0;while(aj&&aj!==D&&aj.parentNode){if(Y.hasNodeAttribute(aj,this.CONTENT_ATTR)){return aj}if(Y.hasNodeCssClass(aj,this.CONTENT_CLASS)){return aj}aj=aj.parentNode;if(ah>1000){break}ah++}},findPieceNode:function(ai){var ah;ah=Y.findFirstNodeHavingAttribute(ai,this.CONTENT_PIECE_ATTR);if(!ah){ah=Y.findFirstNodeHavingClass(ai,this.CONTENT_PIECE_CLASS)}if(ah){return ah}return ai},findTargetNodeNoDefault:function(ah){if(!ah){return}var ai=Y.findFirstNodeHavingAttributeWithValue(ah,this.CONTENT_TARGET_ATTR);if(ai){return ai}ai=Y.findFirstNodeHavingAttribute(ah,this.CONTENT_TARGET_ATTR);
-if(ai){return ai}ai=Y.findFirstNodeHavingClass(ah,this.CONTENT_TARGET_CLASS);if(ai){return ai}},findTargetNode:function(ah){var ai=this.findTargetNodeNoDefault(ah);if(ai){return ai}return ah},findContentName:function(ai){if(!ai){return}var al=Y.findFirstNodeHavingAttributeWithValue(ai,this.CONTENT_NAME_ATTR);if(al){return Y.getAttributeValueFromNode(al,this.CONTENT_NAME_ATTR)}var ah=this.findContentPiece(ai);if(ah){return this.removeDomainIfIsInLink(ah)}if(Y.hasNodeAttributeWithValue(ai,"title")){return Y.getAttributeValueFromNode(ai,"title")}var aj=this.findPieceNode(ai);if(Y.hasNodeAttributeWithValue(aj,"title")){return Y.getAttributeValueFromNode(aj,"title")}var ak=this.findTargetNode(ai);if(Y.hasNodeAttributeWithValue(ak,"title")){return Y.getAttributeValueFromNode(ak,"title")}},findContentPiece:function(ai){if(!ai){return}var ak=Y.findFirstNodeHavingAttributeWithValue(ai,this.CONTENT_PIECE_ATTR);if(ak){return Y.getAttributeValueFromNode(ak,this.CONTENT_PIECE_ATTR)}var ah=this.findPieceNode(ai);
-var aj=this.findMediaUrlInNode(ah);if(aj){return this.toAbsoluteUrl(aj)}},findContentTarget:function(aj){if(!aj){return}var ak=this.findTargetNode(aj);if(Y.hasNodeAttributeWithValue(ak,this.CONTENT_TARGET_ATTR)){return Y.getAttributeValueFromNode(ak,this.CONTENT_TARGET_ATTR)}var ai;if(Y.hasNodeAttributeWithValue(ak,"href")){ai=Y.getAttributeValueFromNode(ak,"href");return this.toAbsoluteUrl(ai)}var ah=this.findPieceNode(aj);if(Y.hasNodeAttributeWithValue(ah,"href")){ai=Y.getAttributeValueFromNode(ah,"href");return this.toAbsoluteUrl(ai)}},isSameDomain:function(ah){if(!ah||!ah.indexOf){return false}if(0===ah.indexOf(this.getLocation().origin)){return true}var ai=ah.indexOf(this.getLocation().host);if(8>=ai&&0<=ai){return true}return false},removeDomainIfIsInLink:function(aj){var ai="^https?://[^/]+";var ah="^.*//[^/]+";if(aj&&aj.search&&-1!==aj.search(new RegExp(ai))&&this.isSameDomain(aj)){aj=aj.replace(new RegExp(ah),"");if(!aj){aj="/"}}return aj},findMediaUrlInNode:function(al){if(!al){return
-}var aj=["img","embed","video","audio"];var ah=al.nodeName.toLowerCase();if(-1!==J(aj,ah)&&Y.findFirstNodeHavingAttributeWithValue(al,"src")){var ak=Y.findFirstNodeHavingAttributeWithValue(al,"src");return Y.getAttributeValueFromNode(ak,"src")}if(ah==="object"&&Y.hasNodeAttributeWithValue(al,"data")){return Y.getAttributeValueFromNode(al,"data")}if(ah==="object"){var am=Y.findNodesByTagName(al,"param");if(am&&am.length){var ai;for(ai=0;ai<am.length;ai++){if("movie"===Y.getAttributeValueFromNode(am[ai],"name")&&Y.hasNodeAttributeWithValue(am[ai],"value")){return Y.getAttributeValueFromNode(am[ai],"value")}}}var an=Y.findNodesByTagName(al,"embed");if(an&&an.length){return this.findMediaUrlInNode(an[0])}}},trim:function(ah){if(ah&&String(ah)===ah){return ah.replace(/^\s+|\s+$/g,"")}return ah},isOrWasNodeInViewport:function(am){if(!am||!am.getBoundingClientRect||am.nodeType!==1){return true}var al=am.getBoundingClientRect();var ak=D.documentElement||{};var aj=al.top<0;if(aj&&am.offsetTop){aj=(am.offsetTop+al.height)>0
-}var ai=ak.clientWidth;if(Q.innerWidth&&ai>Q.innerWidth){ai=Q.innerWidth}var ah=ak.clientHeight;if(Q.innerHeight&&ah>Q.innerHeight){ah=Q.innerHeight}return((al.bottom>0||aj)&&al.right>0&&al.left<ai&&((al.top<ah)||aj))},isNodeVisible:function(ai){var ah=i(ai);var aj=this.isOrWasNodeInViewport(ai);return ah&&aj},buildInteractionRequestParams:function(ah,ai,aj,ak){var al="";if(ah){al+="c_i="+r(ah)}if(ai){if(al){al+="&"}al+="c_n="+r(ai)}if(aj){if(al){al+="&"}al+="c_p="+r(aj)}if(ak){if(al){al+="&"}al+="c_t="+r(ak)}return al},buildImpressionRequestParams:function(ah,ai,aj){var ak="c_n="+r(ah)+"&c_p="+r(ai);if(aj){ak+="&c_t="+r(aj)}return ak},buildContentBlock:function(aj){if(!aj){return}var ah=this.findContentName(aj);var ai=this.findContentPiece(aj);var ak=this.findContentTarget(aj);ah=this.trim(ah);ai=this.trim(ai);ak=this.trim(ak);return{name:ah||"Unknown",piece:ai||"Unknown",target:ak||""}},collectContent:function(ak){if(!ak||!ak.length){return[]}var aj=[];var ah,ai;for(ah=0;ah<ak.length;
-ah++){ai=this.buildContentBlock(ak[ah]);if(G(ai)){aj.push(ai)}}return aj},setLocation:function(ah){this.location=ah},getLocation:function(){var ah=this.location||Q.location;if(!ah.origin){ah.origin=ah.protocol+"//"+ah.hostname+(ah.port?":"+ah.port:"")}return ah},toAbsoluteUrl:function(ai){if((!ai||String(ai)!==ai)&&ai!==""){return ai}if(""===ai){return this.getLocation().href}if(ai.search(/^\/\//)!==-1){return this.getLocation().protocol+ai}if(ai.search(/:\/\//)!==-1){return ai}if(0===ai.indexOf("#")){return this.getLocation().origin+this.getLocation().pathname+ai}if(0===ai.indexOf("?")){return this.getLocation().origin+this.getLocation().pathname+ai}if(0===ai.search("^[a-zA-Z]{2,11}:")){return ai}if(ai.search(/^\//)!==-1){return this.getLocation().origin+ai}var ah="(.*/)";var aj=this.getLocation().origin+this.getLocation().pathname.match(new RegExp(ah))[0];return aj+ai},isUrlToCurrentDomain:function(ai){var aj=this.toAbsoluteUrl(ai);if(!aj){return false}var ah=this.getLocation().origin;
-if(ah===aj){return true}if(0===String(aj).indexOf(ah)){if(":"===String(aj).substr(ah.length,1)){return false}return true}return false},setHrefAttribute:function(ai,ah){if(!ai||!ah){return}Y.setAnyAttribute(ai,"href",ah)},shouldIgnoreInteraction:function(aj){var ai=Y.hasNodeAttribute(aj,this.CONTENT_IGNOREINTERACTION_ATTR);var ah=Y.hasNodeCssClass(aj,this.CONTENT_IGNOREINTERACTION_CLASS);return ai||ah}};function L(ai,al){if(al){return al}ai=t.toAbsoluteUrl(ai);if(w(ai,"?")){var ak=ai.indexOf("?");ai=ai.slice(0,ak)}if(O(ai,"piwik.php")){ai=f(ai,"piwik.php".length)}else{if(O(ai,".php")){var ah=ai.lastIndexOf("/");var aj=1;ai=ai.slice(0,ah+aj)}}if(O(ai,"/js/")){ai=f(ai,"js/".length)}return ai}function K(an){var ap="Piwik_Overlay";var ai=new RegExp("index\\.php\\?module=Overlay&action=startOverlaySession&idSite=([0-9]+)&period=([^&]+)&date=([^&]+)(&segment=.*)?$");var aj=ai.exec(D.referrer);if(aj){var al=aj[1];if(al!==String(an)){return false}var am=aj[2],ah=aj[3],ak=aj[4];if(!ak){ak=""}else{if(ak.indexOf("&segment=")===0){ak=ak.substr("&segment=".length)
-}}Q.name=ap+"###"+am+"###"+ah+"###"+ak}var ao=Q.name.split("###");return ao.length===4&&ao[0]===ap}function U(ai,ao,ak){var an=Q.name.split("###"),am=an[1],ah=an[2],al=an[3],aj=L(ai,ao);m(aj+"plugins/Overlay/client/client.js?v=1",function(){Piwik_Overlay_Client.initialize(aj,ak,am,ah,al)})}function s(){var aj;try{aj=Q.frameElement}catch(ai){return true}if(G(aj)){return(aj&&String(aj.nodeName).toLowerCase()==="iframe")?true:false}try{return Q.self!==Q.top}catch(ah){return true}}function N(bW,bR){var br=this,bM=V(D.domain,Q.location.href,H()),cy=I(bM[0]),bw=n(bM[1]),a9=n(bM[2]),cw=false,b0="GET",cL=b0,az="application/x-www-form-urlencoded; charset=UTF-8",cf=az,av=bW||"",bq="",cC="",bO=bR||"",bj="",bx="",aT,a5="",cI=["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"],ao=[cy],bk=[],bu=[],aW=[],bs=500,co,aU,bA,by,ah,b9=["pk_campaign","piwik_campaign","utm_campaign","utm_source","utm_medium"],bp=["pk_kwd","piwik_kwd","utm_term"],a6="_pk_",an="pk_vid",cA,bb,a7=false,cu,a1,bg,cp=33955200000,b7=1800000,cH=15768000000,aR=true,b5=0,bz=false,aG=false,bT,bE={},b4={},a8={},be=200,cD={},cJ={},bS=[],bX=false,ci=false,ai=false,cK=false,cr=false,aE=false,a0=s(),cB=null,bU,aH,bl,bP=af,ba,aB,ca=0,bf=["id","ses","cvar","ref"];
-try{a5=D.title}catch(cg){a5=""}function cO(cZ,cW,cV,cY,cU,cX){if(a7){return}var cT;if(cV){cT=new Date();cT.setTime(cT.getTime()+cV)}D.cookie=cZ+"="+r(cW)+(cV?";expires="+cT.toGMTString():"")+";path="+(cY||"/")+(cU?";domain="+cU:"")+(cX?";secure":"")}function au(cV){if(a7){return 0}var cT=new RegExp("(^|;)[ ]*"+cV+"=([^;]*)"),cU=cT.exec(D.cookie);return cU?P(cU[2]):0}function bK(cT){var cU;cT=j(cT,an);if(by){cU=new RegExp("#.*");return cT.replace(cU,"")}return cT}function bD(cV,cT){var cW=q(cT),cU;if(cW){return cT}if(cT.slice(0,1)==="/"){return q(cV)+"://"+c(cV)+cT}cV=bK(cV);cU=cV.indexOf("?");if(cU>=0){cV=cV.slice(0,cU)}cU=cV.lastIndexOf("/");if(cU!==cV.length-1){cV=cV.slice(0,cU+1)}return cV+cT}function cn(cV,cT){var cU;cV=String(cV).toLowerCase();cT=String(cT).toLowerCase();if(cV===cT){return true}if(cT.slice(0,1)==="."){if(cV===cT.slice(1)){return true}cU=cV.length-cT.length;if((cU>0)&&(cV.slice(cU)===cT)){return true}}return false}function b3(cT){var cU=document.createElement("a");if(cT.indexOf("//")!==0&&cT.indexOf("http")!==0){if(cT.indexOf("*")===0){cT=cT.substr(1)
-}if(cT.indexOf(".")===0){cT=cT.substr(1)}cT="http://"+cT}cU.href=t.toAbsoluteUrl(cT);if(cU.pathname){return cU.pathname}return""}function aS(cU,cT){if(!ac(cT,"/")){cT="/"+cT}if(!ac(cU,"/")){cU="/"+cU}var cV=(cT==="/"||cT==="/*");if(cV){return true}if(cU===cT){return true}cT=String(cT).toLowerCase();cU=String(cU).toLowerCase();if(O(cT,"*")){cT=cT.slice(0,-1);cV=(!cT||cT==="/");if(cV){return true}if(cU===cT){return true}return cU.indexOf(cT)===0}if(!O(cU,"/")){cU+="/"}if(!O(cT,"/")){cT+="/"}return cU.indexOf(cT)===0}function ak(cX,cZ){var cU,cT,cV,cW,cY;for(cU=0;cU<ao.length;cU++){cW=I(ao[cU]);cY=b3(ao[cU]);if(cn(cX,cW)&&aS(cZ,cY)){return true}}return false}function aL(cW){var cU,cT,cV;for(cU=0;cU<ao.length;cU++){cT=I(ao[cU].toLowerCase());if(cW===cT){return true}if(cT.slice(0,1)==="."){if(cW===cT.slice(1)){return true}cV=cW.length-cT.length;if((cV>0)&&(cW.slice(cV)===cT)){return true}}}return false}function b8(cT,cV){var cU=new Image(1,1);cU.onload=function(){B=0;if(typeof cV==="function"){cV()
-}};cT=cT.replace("send_image=0","send_image=1");cU.src=av+(av.indexOf("?")<0?"?":"&")+cT}function cG(cU,cX,cT){if(!G(cT)||null===cT){cT=true}try{var cW=Q.XMLHttpRequest?new Q.XMLHttpRequest():Q.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):null;cW.open("POST",av,true);cW.onreadystatechange=function(){if(this.readyState===4&&!(this.status>=200&&this.status<300)&&cT){b8(cU,cX)}else{if(this.readyState===4&&(typeof cX==="function")){cX()}}};cW.setRequestHeader("Content-Type",cf);cW.send(cU)}catch(cV){if(cT){b8(cU,cX)}}}function bY(cU){var cT=new Date();var cV=cT.getTime()+cU;if(!p||cV>p){p=cV}}function b6(cT){if(bU||!aU){return}bU=setTimeout(function cU(){bU=null;if(!a0){a0=(!D.hasFocus||D.hasFocus())}if(!a0){b6(aU);return}if(bA()){return}var cV=new Date(),cW=aU-(cV.getTime()-cB);cW=Math.min(aU,cW);b6(cW)},cT||aU)}function bt(){if(!bU){return}clearTimeout(bU);bU=null}function aY(){a0=true;if(bA()){return}b6()}function ap(){bt()}function cQ(){if(aE||!aU){return}aE=true;ag(Q,"focus",aY);
-ag(Q,"blur",ap);b6()}function cj(cX){var cU=new Date();var cT=cU.getTime();cB=cT;if(ci&&cT<ci){var cV=ci-cT;setTimeout(cX,cV);bY(cV+50);ci+=50;return}if(ci===false){var cW=800;ci=cT+cW}cX()}function bo(cU,cT,cV){if(!cu&&cU){cj(function(){if(cL==="POST"||String(cU).length>2000){cG(cU,cV)}else{b8(cU,cV)}bY(cT)})}if(!aE){cQ()}else{b6()}}function b2(cT){if(cu){return false}return(cT&&cT.length)}function cP(cV,cT){if(!b2(cV)){return}var cU='{"requests":["?'+cV.join('","?')+'"]}';cj(function(){cG(cU,null,false);bY(cT)})}function aJ(cT){return a6+cT+"."+bO+"."+ba}function bN(){if(a7){return"0"}if(!G(g.cookieEnabled)){var cT=aJ("testcookie");cO(cT,"1");return au(cT)==="1"?"1":"0"}return g.cookieEnabled?"1":"0"}function a4(){ba=bP((cA||cy)+(bb||"/")).slice(0,4)}function bF(){var cU=aJ("cvar"),cT=au(cU);if(cT.length){cT=JSON_PIWIK.parse(cT);if(S(cT)){return cT}}return{}}function ck(){if(aG===false){aG=bF()}}function cv(){return bP((g.userAgent||"")+(g.platform||"")+JSON_PIWIK.stringify(cJ)+(new Date()).getTime()+Math.random()).slice(0,16)
-}function aq(){return bP((g.userAgent||"")+(g.platform||"")+JSON_PIWIK.stringify(cJ)).slice(0,6)}function a2(){return Math.floor((new Date()).getTime()/1000)}function aA(){var cU=a2();var cV=aq();var cT=String(cU)+cV;return cT}function cF(cW){cW=String(cW);var cZ=aq();var cX=cZ.length;var cY=cW.substr(-1*cX,cX);var cV=parseInt(cW.substr(0,cW.length-cX),10);if(cV&&cY&&cY===cZ){var cT=a2();var cU=45;if(cT>=cV&&cT<=(cV+cU)){return true}}return false}function cR(cT){if(!cr){return""}var cX=e(cT,an);if(!cX){return""}cX=String(cX);var cV=new RegExp("^[a-zA-Z0-9]+$");if(cX.length===32&&cV.test(cX)){var cU=cX.substr(16,32);if(cF(cU)){var cW=cX.substr(0,16);return cW}}return""}function cs(){if(!bx){bx=cR(bw)}var cV=new Date(),cT=Math.round(cV.getTime()/1000),cU=aJ("id"),cY=au(cU),cX,cW;if(cY){cX=cY.split(".");cX.unshift("0");if(bx.length){cX[1]=bx}return cX}if(bx.length){cW=bx}else{if("0"===bN()){cW=""}else{cW=cv()}}cX=["1",cW,cT,0,cT,"",""];return cX}function aN(){var c0=cs(),cW=c0[0],cX=c0[1],cU=c0[2],cT=c0[3],cY=c0[4],cV=c0[5];
-if(!G(c0[6])){c0[6]=""}var cZ=c0[6];return{newVisitor:cW,uuid:cX,createTs:cU,visitCount:cT,currentVisitTs:cY,lastVisitTs:cV,lastEcommerceOrderTs:cZ}}function ay(){var cW=new Date(),cU=cW.getTime(),cX=aN().createTs;var cT=parseInt(cX,10);var cV=(cT*1000)+cp-cU;return cV}function aC(cT){if(!bO){return}var cV=new Date(),cU=Math.round(cV.getTime()/1000);if(!G(cT)){cT=aN()}var cW=cT.uuid+"."+cT.createTs+"."+cT.visitCount+"."+cU+"."+cT.lastVisitTs+"."+cT.lastEcommerceOrderTs;cO(aJ("id"),cW,ay(),bb,cA)}function bv(){var cT=au(aJ("ref"));if(cT.length){try{cT=JSON_PIWIK.parse(cT);if(S(cT)){return cT}}catch(cU){}}return["","",0,""]}function bG(cV,cU,cT){cO(cV,"",-86400,cU,cT)}function bh(cU){var cT="testvalue";cO("test",cT,10000,null,cU);if(au("test")===cT){bG("test",null,cU);return true}return false}function aw(){var cU=a7;a7=false;var cT,cV;for(cT=0;cT<bf.length;cT++){cV=aJ(bf[cT]);if(0!==au(cV)){bG(cV,bb,cA)}}a7=cU}function bL(cT){bO=cT;aC()}function cS(cX){if(!cX||!S(cX)){return}var cW=[];var cV;
-for(cV in cX){if(Object.prototype.hasOwnProperty.call(cX,cV)){cW.push(cV)}}var cY={};cW.sort();var cT=cW.length;var cU;for(cU=0;cU<cT;cU++){cY[cW[cU]]=cX[cW[cU]]}return cY}function bV(){cO(aJ("ses"),"*",b7,bb,cA)}function a3(){var cW="";var cU="abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";var cV=cU.length;var cT;for(cT=0;cT<6;cT++){cW+=cU.charAt(Math.floor(Math.random()*cV))}return cW}function cb(cV,dg,dh,cW){var df,cU=new Date(),c3=Math.round(cU.getTime()/1000),c0,de,cX=1024,dm,c4,dc=aG,cY=aJ("ses"),da=aJ("ref"),c7=aJ("cvar"),c8=au(cY),dd=bv(),dj=aT||bw,c1,cT;if(a7){aw()}if(cu){return""}var c9=aN();if(!G(cW)){cW=""}var c6=D.characterSet||D.charset;if(!c6||c6.toLowerCase()==="utf-8"){c6=null}c1=dd[0];cT=dd[1];c0=dd[2];de=dd[3];if(!c8){var di=b7/1000;if(!c9.lastVisitTs||(c3-c9.lastVisitTs)>di){c9.visitCount++;c9.lastVisitTs=c9.currentVisitTs}if(!bg||!c1.length){for(df in b9){if(Object.prototype.hasOwnProperty.call(b9,df)){c1=e(dj,b9[df]);if(c1.length){break}}}for(df in bp){if(Object.prototype.hasOwnProperty.call(bp,df)){cT=e(dj,bp[df]);
-if(cT.length){break}}}}dm=c(a9);c4=de.length?c(de):"";if(dm.length&&!aL(dm)&&(!bg||!c4.length||aL(c4))){de=a9}if(de.length||c1.length){c0=c3;dd=[c1,cT,c0,bK(de.slice(0,cX))];cO(da,JSON_PIWIK.stringify(dd),cH,bb,cA)}}cV+="&idsite="+bO+"&rec=1&r="+String(Math.random()).slice(2,8)+"&h="+cU.getHours()+"&m="+cU.getMinutes()+"&s="+cU.getSeconds()+"&url="+r(bK(dj))+(a9.length?"&urlref="+r(bK(a9)):"")+((bj&&bj.length)?"&uid="+r(bj):"")+"&_id="+c9.uuid+"&_idts="+c9.createTs+"&_idvc="+c9.visitCount+"&_idn="+c9.newVisitor+(c1.length?"&_rcn="+r(c1):"")+(cT.length?"&_rck="+r(cT):"")+"&_refts="+c0+"&_viewts="+c9.lastVisitTs+(String(c9.lastEcommerceOrderTs).length?"&_ects="+c9.lastEcommerceOrderTs:"")+(String(de).length?"&_ref="+r(bK(de.slice(0,cX))):"")+(c6?"&cs="+r(c6):"")+"&send_image=0";for(df in cJ){if(Object.prototype.hasOwnProperty.call(cJ,df)){cV+="&"+df+"="+cJ[df]}}var dl=[];if(dg){for(df in dg){if(Object.prototype.hasOwnProperty.call(dg,df)&&/^dimension\d+$/.test(df)){var cZ=df.replace("dimension","");
-dl.push(parseInt(cZ,10));dl.push(String(cZ));cV+="&"+df+"="+dg[df];delete dg[df]}}}if(dg&&y(dg)){dg=null}for(df in a8){if(Object.prototype.hasOwnProperty.call(a8,df)){var c5=(-1===J(dl,df));if(c5){cV+="&dimension"+df+"="+a8[df]}}}if(dg){cV+="&data="+r(JSON_PIWIK.stringify(dg))}else{if(ah){cV+="&data="+r(JSON_PIWIK.stringify(ah))}}function c2(dn,dp){var dq=JSON_PIWIK.stringify(dn);if(dq.length>2){return"&"+dp+"="+r(dq)}return""}var dk=cS(bE);var db=cS(b4);cV+=c2(dk,"cvar");cV+=c2(db,"e_cvar");if(aG){cV+=c2(aG,"_cvar");for(df in dc){if(Object.prototype.hasOwnProperty.call(dc,df)){if(aG[df][0]===""||aG[df][1]===""){delete aG[df]}}}if(bz){cO(c7,JSON_PIWIK.stringify(aG),b7,bb,cA)}}if(aR){if(b5){cV+="&gt_ms="+b5}else{if(h&&h.timing&&h.timing.requestStart&&h.timing.responseEnd){cV+="&gt_ms="+(h.timing.responseEnd-h.timing.requestStart)}}}if(aB){cV+="&pv_id="+aB}c9.lastEcommerceOrderTs=G(cW)&&String(cW).length?cW:c9.lastEcommerceOrderTs;aC(c9);bV();cV+=W(dh,{tracker:br,request:cV});if(cC.length){cV+="&"+cC
-}if(x(bT)){cV=bT(cV)}return cV}bA=function aV(){var cT=new Date();if(cB+aU<=cT.getTime()){var cU=cb("ping=1",null,"ping");bo(cU,bs);return true}return false};function bc(cW,cV,c1,cX,cT,c4){var cZ="idgoal=0",c0,cU=new Date(),c2=[],c3,cY=String(cW).length;if(cY){cZ+="&ec_id="+r(cW);c0=Math.round(cU.getTime()/1000)}cZ+="&revenue="+cV;if(String(c1).length){cZ+="&ec_st="+c1}if(String(cX).length){cZ+="&ec_tx="+cX}if(String(cT).length){cZ+="&ec_sh="+cT}if(String(c4).length){cZ+="&ec_dt="+c4}if(cD){for(c3 in cD){if(Object.prototype.hasOwnProperty.call(cD,c3)){if(!G(cD[c3][1])){cD[c3][1]=""}if(!G(cD[c3][2])){cD[c3][2]=""}if(!G(cD[c3][3])||String(cD[c3][3]).length===0){cD[c3][3]=0}if(!G(cD[c3][4])||String(cD[c3][4]).length===0){cD[c3][4]=1}c2.push(cD[c3])}}cZ+="&ec_items="+r(JSON_PIWIK.stringify(c2))}cZ=cb(cZ,ah,"ecommerce",c0);bo(cZ,bs);if(cY){cD={}}}function bH(cT,cX,cW,cV,cU,cY){if(String(cT).length&&G(cX)){bc(cT,cX,cW,cV,cU,cY)}}function bd(cT){if(G(cT)){bc("",cT,"","","","")}}function bI(cU,cW,cV){aB=a3();
-var cT=cb("action_name="+r(ae(cU||a5)),cW,"log");bo(cT,bs,cV)}function aP(cV,cU){var cW,cT="(^| )(piwik[_-]"+cU;if(cV){for(cW=0;cW<cV.length;cW++){cT+="|"+cV[cW]}}cT+=")( |$)";return new RegExp(cT)}function aK(cT){return(av&&cT&&0===String(cT).indexOf(av))}function cc(cX,cT,cY,cU){if(aK(cT)){return 0}var cW=aP(bu,"download"),cV=aP(aW,"link"),cZ=new RegExp("\\.("+cI.join("|")+")([?&#]|$)","i");if(cV.test(cX)){return"link"}if(cU||cW.test(cX)||cZ.test(cT)){return"download"}if(cY){return 0}return"link"}function am(cU){var cT;cT=cU.parentNode;while(cT!==null&&G(cT)){if(Y.isLinkElement(cU)){break}cU=cT;cT=cU.parentNode}return cU}function cM(cY){cY=am(cY);if(!Y.hasNodeAttribute(cY,"href")){return}if(!G(cY.href)){return}var cX=Y.getAttributeValueFromNode(cY,"href");if(aK(cX)){return}var cU=cY.pathname||b3(cY.href);var cZ=cY.hostname||c(cY.href);var c0=cZ.toLowerCase();var cV=cY.href.replace(cZ,c0);var cW=new RegExp("^(javascript|vbscript|jscript|mocha|livescript|ecmascript|mailto|tel):","i");if(!cW.test(cV)){var cT=cc(cY.className,cV,ak(c0,cU),Y.hasNodeAttribute(cY,"download"));
-if(cT){return{type:cT,href:cV}}}}function aF(cT,cU,cV,cW){var cX=t.buildInteractionRequestParams(cT,cU,cV,cW);if(!cX){return}return cb(cX,null,"contentInteraction")}function cq(cV,cW,c0,cT,cU){if(!G(cV)){return}if(aK(cV)){return cV}var cY=t.toAbsoluteUrl(cV);var cX="redirecturl="+r(cY)+"&";cX+=aF(cW,c0,cT,(cU||cV));var cZ="&";if(av.indexOf("?")<0){cZ="?"}return av+cZ+cX}function aZ(cT,cU){if(!cT||!cU){return false}var cV=t.findTargetNode(cT);if(t.shouldIgnoreInteraction(cV)){return false}cV=t.findTargetNodeNoDefault(cT);if(cV&&!R(cV,cU)){return false}return true}function cd(cV,cU,cX){if(!cV){return}var cT=t.findParentContentNode(cV);if(!cT){return}if(!aZ(cT,cV)){return}var cW=t.buildContentBlock(cT);if(!cW){return}if(!cW.target&&cX){cW.target=cX}return t.buildInteractionRequestParams(cU,cW.name,cW.piece,cW.target)}function aM(cU){if(!bS||!bS.length){return false}var cT,cV;for(cT=0;cT<bS.length;cT++){cV=bS[cT];if(cV&&cV.name===cU.name&&cV.piece===cU.piece&&cV.target===cU.target){return true
-}}return false}function bn(cW){if(!cW){return false}var cZ=t.findTargetNode(cW);if(!cZ||t.shouldIgnoreInteraction(cZ)){return false}var c0=cM(cZ);if(cK&&c0&&c0.type){return false}if(Y.isLinkElement(cZ)&&Y.hasNodeAttributeWithValue(cZ,"href")){var cT=String(Y.getAttributeValueFromNode(cZ,"href"));if(0===cT.indexOf("#")){return false}if(aK(cT)){return true}if(!t.isUrlToCurrentDomain(cT)){return false}var cX=t.buildContentBlock(cW);if(!cX){return}var cV=cX.name;var c1=cX.piece;var cY=cX.target;if(!Y.hasNodeAttributeWithValue(cZ,t.CONTENT_TARGET_ATTR)||cZ.wasContentTargetAttrReplaced){cZ.wasContentTargetAttrReplaced=true;cY=t.toAbsoluteUrl(cT);Y.setAnyAttribute(cZ,t.CONTENT_TARGET_ATTR,cY)}var cU=cq(cT,"click",cV,c1,cY);t.setHrefAttribute(cZ,cU);return true}return false}function aD(cU){if(!cU||!cU.length){return}var cT;for(cT=0;cT<cU.length;cT++){bn(cU[cT])}}function aO(cT){return function(cU){if(!cT){return}var cX=t.findParentContentNode(cT);var cY;if(cU){cY=cU.target||cU.srcElement}if(!cY){cY=cT
-}if(!aZ(cX,cY)){return}bY(bs);if(Y.isLinkElement(cT)&&Y.hasNodeAttributeWithValue(cT,"href")&&Y.hasNodeAttributeWithValue(cT,t.CONTENT_TARGET_ATTR)){var cV=Y.getAttributeValueFromNode(cT,"href");if(!aK(cV)&&cT.wasContentTargetAttrReplaced){Y.setAnyAttribute(cT,t.CONTENT_TARGET_ATTR,"")}}var c2=cM(cT);if(ai&&c2&&c2.type){return c2.type}if(bn(cX)){return"href"}var cZ=t.buildContentBlock(cX);if(!cZ){return}var cW=cZ.name;var c3=cZ.piece;var c1=cZ.target;var c0=aF("click",cW,c3,c1);bo(c0,bs);return c0}}function bJ(cV){if(!cV||!cV.length){return}var cT,cU;for(cT=0;cT<cV.length;cT++){cU=t.findTargetNode(cV[cT]);if(cU&&!cU.contentInteractionTrackingSetupDone){cU.contentInteractionTrackingSetupDone=true;ag(cU,"click",aO(cU))}}}function bi(cV,cW){if(!cV||!cV.length){return[]}var cT,cU;for(cT=0;cT<cV.length;cT++){if(aM(cV[cT])){cV.splice(cT,1);cT--}else{bS.push(cV[cT])}}if(!cV||!cV.length){return[]}aD(cW);bJ(cW);var cX=[];for(cT=0;cT<cV.length;cT++){cU=cb(t.buildImpressionRequestParams(cV[cT].name,cV[cT].piece,cV[cT].target),undefined,"contentImpressions");
-if(cU){cX.push(cU)}}return cX}function ch(cU){var cT=t.collectContent(cU);return bi(cT,cU)}function aX(cU){if(!cU||!cU.length){return[]}var cT;for(cT=0;cT<cU.length;cT++){if(!t.isNodeVisible(cU[cT])){cU.splice(cT,1);cT--}}if(!cU||!cU.length){return[]}return ch(cU)}function ax(cV,cT,cU){var cW=t.buildImpressionRequestParams(cV,cT,cU);return cb(cW,null,"contentImpression")}function cN(cW,cU){if(!cW){return}var cT=t.findParentContentNode(cW);var cV=t.buildContentBlock(cT);if(!cV){return}if(!cU){cU="Unknown"}return aF(cU,cV.name,cV.piece,cV.target)}function ct(cU,cW,cT,cV){return"e_c="+r(cU)+"&e_a="+r(cW)+(G(cT)?"&e_n="+r(cT):"")+(G(cV)?"&e_v="+r(cV):"")}function al(cV,cX,cT,cW,cZ,cY){if(String(cV).length===0||String(cX).length===0){return false}var cU=cb(ct(cV,cX,cT,cW),cZ,"event");bo(cU,bs,cY)}function bQ(cT,cW,cU,cX){var cV=cb("search="+r(cT)+(cW?"&search_cat="+r(cW):"")+(G(cU)?"&search_count="+cU:""),cX,"sitesearch");bo(cV,bs)}function cx(cT,cW,cV){var cU=cb("idgoal="+cT+(cW?"&revenue="+cW:""),cV,"goal");
-bo(cU,bs)}function cE(cW,cT,c0,cZ,cV){var cY=cT+"="+r(bK(cW));var cU=cd(cV,"click",cW);if(cU){cY+="&"+cU}var cX=cb(cY,c0,"link");bo(cX,bs,cZ)}function bC(cU,cT){if(cU!==""){return cU+cT.charAt(0).toUpperCase()+cT.slice(1)}return cT}function bZ(cY){var cX,cT,cW=["","webkit","ms","moz"],cV;if(!a1){for(cT=0;cT<cW.length;cT++){cV=cW[cT];if(Object.prototype.hasOwnProperty.call(D,bC(cV,"hidden"))){if(D[bC(cV,"visibilityState")]==="prerender"){cX=true}break}}}if(cX){ag(D,cV+"visibilitychange",function cU(){D.removeEventListener(cV+"visibilitychange",cU,false);cY()});return}cY()}function b1(cT){if(!cT){return}if(!Y.hasNodeAttribute(cT,"href")){return}var cU=Y.getAttributeValueFromNode(cT,"href");if(!cU||aK(cU)){return}cU=j(cU,an);if(cU.indexOf("?")>0){cU+="&"}else{cU+="?"}var cW=aN().uuid;var cV=aA();cU=C(cU,an,cW+cV);Y.setAnyAttribute(cT,"href",cU)}function ar(cW){var cX=Y.getAttributeValueFromNode(cW,"href");if(!cX){return false}cX=String(cX);var cU=cX.indexOf("//")===0||cX.indexOf("http://")===0||cX.indexOf("https://")===0;
-if(!cU){return false}var cT=cW.pathname||b3(cW.href);var cV=(cW.hostname||c(cW.href)).toLowerCase();if(ak(cV,cT)){if(!cn(cy,I(cV))){return true}return false}return false}function cm(cT){var cU=cM(cT);if(cU&&cU.type){cU.href=n(cU.href);cE(cU.href,cU.type,undefined,null,cT);return}if(cr){cT=am(cT);if(ar(cT)){b1(cT)}}}function ce(){return D.all&&!D.addEventListener}function cz(cT){var cV=cT.which;var cU=(typeof cT.button);if(!cV&&cU!=="undefined"){if(ce()){if(cT.button&1){cV=1}else{if(cT.button&2){cV=3}else{if(cT.button&4){cV=2}}}}else{if(cT.button===0||cT.button==="0"){cV=1}else{if(cT.button&1){cV=2}else{if(cT.button&2){cV=3}}}}}return cV}function bB(cT){switch(cz(cT)){case 1:return"left";case 2:return"middle";case 3:return"right"}}function aQ(cT){return cT.target||cT.srcElement}function at(cT){return function(cW){cW=cW||Q.event;var cV=bB(cW);var cX=aQ(cW);if(cW.type==="click"){var cU=false;if(cT&&cV==="middle"){cU=true}if(cX&&!cU){cm(cX)}}else{if(cW.type==="mousedown"){if(cV==="middle"&&cX){aH=cV;
-bl=cX}else{aH=bl=null}}else{if(cW.type==="mouseup"){if(cV===aH&&cX===bl){cm(cX)}aH=bl=null}else{if(cW.type==="contextmenu"){cm(cX)}}}}}}function aj(cV,cU){var cT=typeof cU;if(cT==="undefined"){cU=true}ag(cV,"click",at(cU),false);if(cU){ag(cV,"mouseup",at(cU),false);ag(cV,"mousedown",at(cU),false);ag(cV,"contextmenu",at(cU),false)}}function bm(cV,cX){ai=true;var cW,cU=aP(bk,"ignore"),cY=D.links,cT=null,cZ=null;if(cY){for(cW=0;cW<cY.length;cW++){cT=cY[cW];if(!cU.test(cT.className)){cZ=typeof cT.piwikTrackers;if("undefined"===cZ){cT.piwikTrackers=[]}if(-1===J(cT.piwikTrackers,cX)){cT.piwikTrackers.push(cX);aj(cT,cV)}}}}}function aI(cV,cX,cY){if(bX){return true}bX=true;var cZ=false;var cW,cU;function cT(){cZ=true}l(function(){function c0(c2){setTimeout(function(){if(!bX){return}cZ=false;cY.trackVisibleContentImpressions();c0(c2)},c2)}function c1(c2){setTimeout(function(){if(!bX){return}if(cZ){cZ=false;cY.trackVisibleContentImpressions()}c1(c2)},c2)}if(cV){cW=["scroll","resize"];for(cU=0;cU<cW.length;
-cU++){if(D.addEventListener){D.addEventListener(cW[cU],cT)}else{Q.attachEvent("on"+cW[cU],cT)}}c1(100)}if(cX&&cX>0){cX=parseInt(cX,10);c0(cX)}})}function cl(){var cU,cW,cX={pdf:"application/pdf",qt:"video/quicktime",realp:"audio/x-pn-realaudio-plugin",wma:"application/x-mplayer2",dir:"application/x-director",fla:"application/x-shockwave-flash",java:"application/x-java-vm",gears:"application/x-googlegears",ag:"application/x-silverlight"};if(!((new RegExp("MSIE")).test(g.userAgent))){if(g.mimeTypes&&g.mimeTypes.length){for(cU in cX){if(Object.prototype.hasOwnProperty.call(cX,cU)){cW=g.mimeTypes[cX[cU]];cJ[cU]=(cW&&cW.enabledPlugin)?"1":"0"}}}if(typeof navigator.javaEnabled!=="unknown"&&G(g.javaEnabled)&&g.javaEnabled()){cJ.java="1"}if(x(Q.GearsFactory)){cJ.gears="1"}cJ.cookie=bN()}var cV=parseInt(T.width,10);var cT=parseInt(T.height,10);cJ.res=parseInt(cV,10)+"x"+parseInt(cT,10)}cl();a4();aC();this.getVisitorId=function(){return aN().uuid};this.getVisitorInfo=function(){return cs()};this.getAttributionInfo=function(){return bv()
-};this.getAttributionCampaignName=function(){return bv()[0]};this.getAttributionCampaignKeyword=function(){return bv()[1]};this.getAttributionReferrerTimestamp=function(){return bv()[2]};this.getAttributionReferrerUrl=function(){return bv()[3]};this.setTrackerUrl=function(cT){av=cT};this.getTrackerUrl=function(){return av};this.getPiwikUrl=function(){return L(this.getTrackerUrl(),bq)};this.addTracker=function(cT,cV){if(!cV){throw new Error("A siteId must be given to add a new tracker")}if(!G(cT)||null===cT){cT=this.getTrackerUrl()}var cU=new N(cT,cV);F.push(cU);return cU};this.getSiteId=function(){return bO};this.setSiteId=function(cT){bL(cT)};this.setUserId=function(cT){if(!G(cT)||!cT.length){return}bj=cT;bx=bP(bj).substr(0,16)};this.getUserId=function(){return bj};this.setCustomData=function(cT,cU){if(S(cT)){ah=cT}else{if(!ah){ah={}}ah[cT]=cU}};this.getCustomData=function(){return ah};this.setCustomRequestProcessing=function(cT){bT=cT};this.appendToTrackingUrl=function(cT){cC=cT};this.getRequest=function(cT){return cb(cT)
-};this.addPlugin=function(cT,cU){a[cT]=cU};this.setCustomDimension=function(cT,cU){cT=parseInt(cT,10);if(cT>0){if(!G(cU)){cU=""}if(!u(cU)){cU=String(cU)}a8[cT]=cU}};this.getCustomDimension=function(cT){cT=parseInt(cT,10);if(cT>0&&Object.prototype.hasOwnProperty.call(a8,cT)){return a8[cT]}};this.deleteCustomDimension=function(cT){cT=parseInt(cT,10);if(cT>0){delete a8[cT]}};this.setCustomVariable=function(cU,cT,cX,cV){var cW;if(!G(cV)){cV="visit"}if(!G(cT)){return}if(!G(cX)){cX=""}if(cU>0){cT=!u(cT)?String(cT):cT;cX=!u(cX)?String(cX):cX;cW=[cT.slice(0,be),cX.slice(0,be)];if(cV==="visit"||cV===2){ck();aG[cU]=cW}else{if(cV==="page"||cV===3){bE[cU]=cW}else{if(cV==="event"){b4[cU]=cW}}}}};this.getCustomVariable=function(cU,cV){var cT;if(!G(cV)){cV="visit"}if(cV==="page"||cV===3){cT=bE[cU]}else{if(cV==="event"){cT=b4[cU]}else{if(cV==="visit"||cV===2){ck();cT=aG[cU]}}}if(!G(cT)||(cT&&cT[0]==="")){return false}return cT};this.deleteCustomVariable=function(cT,cU){if(this.getCustomVariable(cT,cU)){this.setCustomVariable(cT,"","",cU)
-}};this.deleteCustomVariables=function(cT){if(cT==="page"||cT===3){bE={}}else{if(cT==="event"){b4={}}else{if(cT==="visit"||cT===2){aG={}}}}};this.storeCustomVariablesInCookie=function(){bz=true};this.setLinkTrackingTimer=function(cT){bs=cT};this.getLinkTrackingTimer=function(){return bs};this.setDownloadExtensions=function(cT){if(u(cT)){cT=cT.split("|")}cI=cT};this.addDownloadExtensions=function(cU){var cT;if(u(cU)){cU=cU.split("|")}for(cT=0;cT<cU.length;cT++){cI.push(cU[cT])}};this.removeDownloadExtensions=function(cV){var cU,cT=[];if(u(cV)){cV=cV.split("|")}for(cU=0;cU<cI.length;cU++){if(J(cV,cI[cU])===-1){cT.push(cI[cU])}}cI=cT};this.setDomains=function(cT){ao=u(cT)?[cT]:cT;var cX=false,cV=0,cU;for(cV;cV<ao.length;cV++){cU=String(ao[cV]);if(cn(cy,I(cU))){cX=true;break}var cW=b3(cU);if(cW&&cW!=="/"&&cW!=="/*"){cX=true;break}}if(!cX){ao.push(cy)}};this.enableCrossDomainLinking=function(){cr=true};this.disableCrossDomainLinking=function(){cr=false};this.isCrossDomainLinkingEnabled=function(){return cr
-};this.setIgnoreClasses=function(cT){bk=u(cT)?[cT]:cT};this.setRequestMethod=function(cT){cL=cT||b0};this.setRequestContentType=function(cT){cf=cT||az};this.setReferrerUrl=function(cT){a9=cT};this.setCustomUrl=function(cT){aT=bD(bw,cT)};this.getCurrentUrl=function(){return aT||bw};this.setDocumentTitle=function(cT){a5=cT};this.setAPIUrl=function(cT){bq=cT};this.setDownloadClasses=function(cT){bu=u(cT)?[cT]:cT};this.setLinkClasses=function(cT){aW=u(cT)?[cT]:cT};this.setCampaignNameKey=function(cT){b9=u(cT)?[cT]:cT};this.setCampaignKeywordKey=function(cT){bp=u(cT)?[cT]:cT};this.discardHashTag=function(cT){by=cT};this.setCookieNamePrefix=function(cT){a6=cT;aG=bF()};this.setCookieDomain=function(cT){var cU=I(cT);if(bh(cU)){cA=cU;a4()}};this.getCookieDomain=function(){return cA};this.hasCookies=function(){return"1"===bN()};this.setSessionCookie=function(cV,cU,cT){if(!cV){throw new Error("Missing cookie name")}if(!G(cT)){cT=b7}bf.push(cV);cO(aJ(cV),cU,cT,bb,cA)};this.getCookie=function(cU){var cT=au(aJ(cU));
-if(cT===0){return null}return cT};this.setCookiePath=function(cT){bb=cT;a4()};this.getCookiePath=function(cT){return bb};this.setVisitorCookieTimeout=function(cT){cp=cT*1000};this.setSessionCookieTimeout=function(cT){b7=cT*1000};this.getSessionCookieTimeout=function(){return b7};this.setReferralCookieTimeout=function(cT){cH=cT*1000};this.setConversionAttributionFirstReferrer=function(cT){bg=cT};this.disableCookies=function(){a7=true;cJ.cookie="0";if(bO){aw()}};this.deleteCookies=function(){aw()};this.setDoNotTrack=function(cU){var cT=g.doNotTrack||g.msDoNotTrack;cu=cU&&(cT==="yes"||cT==="1");if(cu){this.disableCookies()}};this.addListener=function(cU,cT){aj(cU,cT)};this.enableLinkTracking=function(cU){cK=true;var cT=this;bZ(function(){o(function(){bm(cU,cT)})})};this.enableJSErrorTracking=function(){if(cw){return}cw=true;var cT=Q.onerror;Q.onerror=function(cY,cW,cV,cX,cU){bZ(function(){var cZ="JavaScript Errors";var c0=cW+":"+cV;if(cX){c0+=":"+cX}al(cZ,c0,cY)});if(cT){return cT(cY,cW,cV,cX,cU)
-}return false}};this.disablePerformanceTracking=function(){aR=false};this.setGenerationTimeMs=function(cT){b5=parseInt(cT,10)};this.enableHeartBeatTimer=function(cT){cT=Math.max(cT,1);aU=(cT||15)*1000;if(cB!==null){cQ()}};this.disableHeartBeatTimer=function(){bt();if(aU||aE){if(Q.removeEventListener){Q.removeEventListener("focus",aY,true);Q.removeEventListener("blur",ap,true)}else{if(Q.detachEvent){Q.detachEvent("onfocus",aY);Q.detachEvent("onblur",ap)}}}aU=null;aE=false};this.killFrame=function(){if(Q.location!==Q.top.location){Q.top.location=Q.location}};this.redirectFile=function(cT){if(Q.location.protocol==="file:"){Q.location=cT}};this.setCountPreRendered=function(cT){a1=cT};this.trackGoal=function(cT,cV,cU){bZ(function(){cx(cT,cV,cU)})};this.trackLink=function(cU,cT,cW,cV){bZ(function(){cE(cU,cT,cW,cV)})};this.getNumTrackedPageViews=function(){return ca};this.trackPageView=function(cT,cV,cU){bS=[];if(K(bO)){bZ(function(){U(av,bq,bO)})}else{bZ(function(){ca++;bI(cT,cV,cU)})}};this.trackAllContentImpressions=function(){if(K(bO)){return
-}bZ(function(){o(function(){var cT=t.findContentNodes();var cU=ch(cT);cP(cU,bs)})})};this.trackVisibleContentImpressions=function(cT,cU){if(K(bO)){return}if(!G(cT)){cT=true}if(!G(cU)){cU=750}aI(cT,cU,this);bZ(function(){l(function(){var cV=t.findContentNodes();var cW=aX(cV);cP(cW,bs)})})};this.trackContentImpression=function(cV,cT,cU){if(K(bO)){return}if(!cV){return}cT=cT||"Unknown";bZ(function(){var cW=ax(cV,cT,cU);bo(cW,bs)})};this.trackContentImpressionsWithinNode=function(cT){if(K(bO)||!cT){return}bZ(function(){if(bX){l(function(){var cU=t.findContentNodesWithinNode(cT);var cV=aX(cU);cP(cV,bs)})}else{o(function(){var cU=t.findContentNodesWithinNode(cT);var cV=ch(cU);cP(cV,bs)})}})};this.trackContentInteraction=function(cV,cW,cT,cU){if(K(bO)){return}if(!cV||!cW){return}cT=cT||"Unknown";bZ(function(){var cX=aF(cV,cW,cT,cU);bo(cX,bs)})};this.trackContentInteractionNode=function(cU,cT){if(K(bO)||!cU){return}bZ(function(){var cV=cN(cU,cT);bo(cV,bs)})};this.logAllContentBlocksOnPage=function(){var cU=t.findContentNodes();
-var cT=t.collectContent(cU);if(console!==undefined&&console&&console.log){console.log(cT)}};this.trackEvent=function(cU,cW,cT,cV,cY,cX){bZ(function(){al(cU,cW,cT,cV,cY,cX)})};this.trackSiteSearch=function(cT,cV,cU,cW){bZ(function(){bQ(cT,cV,cU,cW)})};this.setEcommerceView=function(cW,cT,cV,cU){if(!G(cV)||!cV.length){cV=""}else{if(cV instanceof Array){cV=JSON_PIWIK.stringify(cV)}}bE[5]=["_pkc",cV];if(G(cU)&&String(cU).length){bE[2]=["_pkp",cU]}if((!G(cW)||!cW.length)&&(!G(cT)||!cT.length)){return}if(G(cW)&&cW.length){bE[3]=["_pks",cW]}if(!G(cT)||!cT.length){cT=""}bE[4]=["_pkn",cT]};this.addEcommerceItem=function(cX,cT,cV,cU,cW){if(cX.length){cD[cX]=[cX,cT,cV,cU,cW]}};this.trackEcommerceOrder=function(cT,cX,cW,cV,cU,cY){bH(cT,cX,cW,cV,cU,cY)};this.trackEcommerceCartUpdate=function(cT){bd(cT)};this.trackRequest=function(cU,cW,cV,cT){bZ(function(){var cX=cb(cU,cW,cT);bo(cX,bs,cV)})};d.trigger("TrackerSetup",[this])}function E(){return{push:Z}}function b(am,al){var an={};var aj,ak;for(aj=0;aj<al.length;
-aj++){var ah=al[aj];an[ah]=1;for(ak=0;ak<am.length;ak++){if(am[ak]&&am[ak][0]){var ai=am[ak][0];if(ah===ai){Z(am[ak]);delete am[ak];if(an[ai]>1){ad("The method "+ai+' 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')}an[ai]++}}}}return am}var z=["addTracker","disableCookies","setTrackerUrl","setAPIUrl","enableCrossDomainLinking","setCookiePath","setCookieDomain","setDomains","setUserId","setSiteId","enableLinkTracking"];function X(ah,aj){var ai=new N(ah,aj);F.push(ai);_paq=b(_paq,z);for(B=0;B<_paq.length;B++){if(_paq[B]){Z(_paq[B])}}_paq=new E();return ai}ag(Q,"beforeunload",ab,false);Date.prototype.getTimeAlias=Date.prototype.getTime;d={initialized:false,JSON:JSON_PIWIK,DOM:{addEventListener:function(ak,aj,ai,ah){var al=typeof ah;if(al==="undefined"){ah=false}ag(ak,aj,ai,ah)},onLoad:l,onReady:o,isNodeVisible:i,isOrWasNodeVisible:t.isNodeVisible},on:function(ai,ah){if(!v[ai]){v[ai]=[]
-}v[ai].push(ah)},off:function(aj,ai){if(!v[aj]){return}var ah=0;for(ah;ah<v[aj].length;ah++){if(v[aj][ah]===ai){v[aj].splice(ah,1)}}},trigger:function(aj,ak,ai){if(!v[aj]){return}var ah=0;for(ah;ah<v[aj].length;ah++){v[aj][ah].apply(ai||Q,ak)}},addPlugin:function(ah,ai){a[ah]=ai},getTracker:function(ah,ai){if(!G(ai)){ai=this.getAsyncTracker().getSiteId()}if(!G(ah)){ah=this.getAsyncTracker().getTrackerUrl()}return new N(ah,ai)},getAsyncTrackers:function(){return F},addTracker:function(ah,ai){if(!F.length){X(ah,ai)}else{F[0].addTracker(ah,ai)}},getAsyncTracker:function(ai,al){var ak;if(F&&F.length&&F[0]){ak=F[0]}else{return X(ai,al)}if(!al&&!ai){return ak}if((!G(al)||null===al)&&ak){al=ak.getSiteId()}if((!G(ai)||null===ai)&&ak){ai=ak.getTrackerUrl()}var aj,ah=0;for(ah;ah<F.length;ah++){aj=F[ah];if(aj&&String(aj.getSiteId())===String(al)&&aj.getTrackerUrl()===ai){return aj}}},retryMissedPluginCalls:function(){var ai=aa;aa=[];var ah=0;for(ah;ah<ai.length;ah++){Z(ai[ah])}}};if(typeof define==="function"&&define.amd){define("piwik",[],function(){return d
-})}return d}());
-/*!!! pluginTrackerHook */
-}(function(){function b(){if("object"!==typeof _paq){return false}var c=typeof _paq.length;if("undefined"===c){return false}return !!_paq.length}if(window&&"object"===typeof window.piwikPluginAsyncInit&&window.piwikPluginAsyncInit.length){var a=0;for(a;a<window.piwikPluginAsyncInit.length;a++){if(typeof window.piwikPluginAsyncInit[a]==="function"){window.piwikPluginAsyncInit[a]()}}}if(window&&window.piwikAsyncInit){window.piwikAsyncInit()}if(!window.Piwik.getAsyncTrackers().length){if(b()){window.Piwik.addTracker()}else{_paq={push:function(c){if(console!==undefined&&console&&console.error){console.error("_paq.push() was used but Piwik tracker was not initialized before the piwik.js file was loaded. Make sure to configure the tracker via _paq.push before loading piwik.js. Alternatively, you can create a tracker via Piwik.addTracker() manually and then use _paq.push but it may not fully work as tracker methods may not be executed in the correct order.",c)
-}}}}}window.Piwik.trigger("PiwikInitialized",[]);window.Piwik.initialized=true}());(function(){var a=(typeof AnalyticsTracker);if(a==="undefined"){AnalyticsTracker=window.Piwik}}());if(typeof piwik_log!=="function"){piwik_log=function(b,f,d,g){function a(h){try{if(window["piwik_"+h]){return window["piwik_"+h]}}catch(i){}return}var c,e=window.Piwik.getTracker(d,f);e.setDocumentTitle(b);e.setCustomData(g);c=a("tracker_pause");if(c){e.setLinkTrackingTimer(c)}c=a("download_extensions");if(c){e.setDownloadExtensions(c)}c=a("hosts_alias");if(c){e.setDomains(c)}c=a("ignore_classes");if(c){e.setIgnoreClasses(c)}e.trackPageView();if(a("install_tracker")){piwik_track=function(i,k,j,h){e.setSiteId(k);e.setTrackerUrl(j);e.trackLink(i,h)};e.enableLinkTracking()}};
-/*!! @license-end */
-};
diff --git a/js/piwik.js.orig b/js/piwik.js.orig
deleted file mode 100644
index c80d68a758..0000000000
--- a/js/piwik.js.orig
+++ /dev/null
@@ -1,6972 +0,0 @@
-/*!
- * Matomo - free/libre analytics platform
- *
- * JavaScript tracking client
- *
- * @link https://matomo.org
- * @source https://github.com/piwik/piwik/blob/master/js/piwik.js
- * @license http://piwik.org/free-software/bsd/ BSD-3 Clause (also in js/LICENSE.txt)
- * @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
- */
-// NOTE: if you change this above Piwik comment block, you must also change `$byteStart` in js/tracker.php
-
-// Refer to README.md for build instructions when minifying this file for distribution.
-
-/*
- * Browser [In]Compatibility
- * - minimum required ECMAScript: ECMA-262, edition 3
- *
- * Incompatible with these (and earlier) versions of:
- * - IE4 - try..catch and for..in introduced in IE5
- * - IE5 - named anonymous functions, array.push, encodeURIComponent, decodeURIComponent, and getElementsByTagName introduced in IE5.5
- * - Firefox 1.0 and Netscape 8.x - FF1.5 adds array.indexOf, among other things
- * - Mozilla 1.7 and Netscape 6.x-7.x
- * - Netscape 4.8
- * - Opera 6 - Error object (and Presto) introduced in Opera 7
- * - Opera 7
- */
-
-/*global JSON2:true */
-
-if (typeof JSON2 !== 'object' && typeof window.JSON === 'object' && window.JSON.stringify && window.JSON.parse) {
- JSON2 = window.JSON;
-} else {
- (function () {
- // we make sure to not break any site that uses JSON3 as well as we do not know if they run it in conflict mode
- // or not.
- var exports = {};
-
- // Create a JSON object only if one does not already exist. We create the
- // methods in a closure to avoid creating global variables.
-
- /*! JSON v3.3.2 | http://bestiejs.github.io/json3 | Copyright 2012-2014, Kit Cambridge | http://kit.mit-license.org */
- (function () {
- // Detect the `define` function exposed by asynchronous module loaders. The
- // strict `define` check is necessary for compatibility with `r.js`.
- var isLoader = typeof define === "function" && define.amd;
-
- // A set of types used to distinguish objects from primitives.
- var objectTypes = {
- "function": true,
- "object": true
- };
-
- // Detect the `exports` object exposed by CommonJS implementations.
- var freeExports = objectTypes[typeof exports] && exports && !exports.nodeType && exports;
-
- // Use the `global` object exposed by Node (including Browserify via
- // `insert-module-globals`), Narwhal, and Ringo as the default context,
- // and the `window` object in browsers. Rhino exports a `global` function
- // instead.
- var root = objectTypes[typeof window] && window || this,
- freeGlobal = freeExports && objectTypes[typeof module] && module && !module.nodeType && typeof global == "object" && global;
-
- if (freeGlobal && (freeGlobal["global"] === freeGlobal || freeGlobal["window"] === freeGlobal || freeGlobal["self"] === freeGlobal)) {
- root = freeGlobal;
- }
-
- // Public: Initializes JSON 3 using the given `context` object, attaching the
- // `stringify` and `parse` functions to the specified `exports` object.
- function runInContext(context, exports) {
- context || (context = root["Object"]());
- exports || (exports = root["Object"]());
-
- // Native constructor aliases.
- var Number = context["Number"] || root["Number"],
- String = context["String"] || root["String"],
- Object = context["Object"] || root["Object"],
- Date = context["Date"] || root["Date"],
- SyntaxError = context["SyntaxError"] || root["SyntaxError"],
- TypeError = context["TypeError"] || root["TypeError"],
- Math = context["Math"] || root["Math"],
- nativeJSON = context["JSON"] || root["JSON"];
-
- // Delegate to the native `stringify` and `parse` implementations.
- if (typeof nativeJSON == "object" && nativeJSON) {
- exports.stringify = nativeJSON.stringify;
- exports.parse = nativeJSON.parse;
- }
-
- // Convenience aliases.
- var objectProto = Object.prototype,
- getClass = objectProto.toString,
- isProperty, forEach, undef;
-
- // Test the `Date#getUTC*` methods. Based on work by @Yaffle.
- var isExtended = new Date(-3509827334573292);
- try {
- // The `getUTCFullYear`, `Month`, and `Date` methods return nonsensical
- // results for certain dates in Opera >= 10.53.
- isExtended = isExtended.getUTCFullYear() == -109252 && isExtended.getUTCMonth() === 0 && isExtended.getUTCDate() === 1 &&
- // Safari < 2.0.2 stores the internal millisecond time value correctly,
- // but clips the values returned by the date methods to the range of
- // signed 32-bit integers ([-2 ** 31, 2 ** 31 - 1]).
- isExtended.getUTCHours() == 10 && isExtended.getUTCMinutes() == 37 && isExtended.getUTCSeconds() == 6 && isExtended.getUTCMilliseconds() == 708;
- } catch (exception) {}
-
- // Internal: Determines whether the native `JSON.stringify` and `parse`
- // implementations are spec-compliant. Based on work by Ken Snyder.
- function has(name) {
- if (has[name] !== undef) {
- // Return cached feature test result.
- return has[name];
- }
- var isSupported;
- if (name == "bug-string-char-index") {
- // IE <= 7 doesn't support accessing string characters using square
- // bracket notation. IE 8 only supports this for primitives.
- isSupported = "a"[0] != "a";
- } else if (name == "json") {
- // Indicates whether both `JSON.stringify` and `JSON.parse` are
- // supported.
- isSupported = has("json-stringify") && has("json-parse");
- } else {
- var value, serialized = '{"a":[1,true,false,null,"\\u0000\\b\\n\\f\\r\\t"]}';
- // Test `JSON.stringify`.
- if (name == "json-stringify") {
- var stringify = exports.stringify, stringifySupported = typeof stringify == "function" && isExtended;
- if (stringifySupported) {
- // A test function object with a custom `toJSON` method.
- (value = function () {
- return 1;
- }).toJSON = value;
- try {
- stringifySupported =
- // Firefox 3.1b1 and b2 serialize string, number, and boolean
- // primitives as object literals.
- stringify(0) === "0" &&
- // FF 3.1b1, b2, and JSON 2 serialize wrapped primitives as object
- // literals.
- stringify(new Number()) === "0" &&
- stringify(new String()) == '""' &&
- // FF 3.1b1, 2 throw an error if the value is `null`, `undefined`, or
- // does not define a canonical JSON representation (this applies to
- // objects with `toJSON` properties as well, *unless* they are nested
- // within an object or array).
- stringify(getClass) === undef &&
- // IE 8 serializes `undefined` as `"undefined"`. Safari <= 5.1.7 and
- // FF 3.1b3 pass this test.
- stringify(undef) === undef &&
- // Safari <= 5.1.7 and FF 3.1b3 throw `Error`s and `TypeError`s,
- // respectively, if the value is omitted entirely.
- stringify() === undef &&
- // FF 3.1b1, 2 throw an error if the given value is not a number,
- // string, array, object, Boolean, or `null` literal. This applies to
- // objects with custom `toJSON` methods as well, unless they are nested
- // inside object or array literals. YUI 3.0.0b1 ignores custom `toJSON`
- // methods entirely.
- stringify(value) === "1" &&
- stringify([value]) == "[1]" &&
- // Prototype <= 1.6.1 serializes `[undefined]` as `"[]"` instead of
- // `"[null]"`.
- stringify([undef]) == "[null]" &&
- // YUI 3.0.0b1 fails to serialize `null` literals.
- stringify(null) == "null" &&
- // FF 3.1b1, 2 halts serialization if an array contains a function:
- // `[1, true, getClass, 1]` serializes as "[1,true,],". FF 3.1b3
- // elides non-JSON values from objects and arrays, unless they
- // define custom `toJSON` methods.
- stringify([undef, getClass, null]) == "[null,null,null]" &&
- // Simple serialization test. FF 3.1b1 uses Unicode escape sequences
- // where character escape codes are expected (e.g., `\b` => `\u0008`).
- stringify({ "a": [value, true, false, null, "\x00\b\n\f\r\t"] }) == serialized &&
- // FF 3.1b1 and b2 ignore the `filter` and `width` arguments.
- stringify(null, value) === "1" &&
- stringify([1, 2], null, 1) == "[\n 1,\n 2\n]" &&
- // JSON 2, Prototype <= 1.7, and older WebKit builds incorrectly
- // serialize extended years.
- stringify(new Date(-8.64e15)) == '"-271821-04-20T00:00:00.000Z"' &&
- // The milliseconds are optional in ES 5, but required in 5.1.
- stringify(new Date(8.64e15)) == '"+275760-09-13T00:00:00.000Z"' &&
- // Firefox <= 11.0 incorrectly serializes years prior to 0 as negative
- // four-digit years instead of six-digit years. Credits: @Yaffle.
- stringify(new Date(-621987552e5)) == '"-000001-01-01T00:00:00.000Z"' &&
- // Safari <= 5.1.5 and Opera >= 10.53 incorrectly serialize millisecond
- // values less than 1000. Credits: @Yaffle.
- stringify(new Date(-1)) == '"1969-12-31T23:59:59.999Z"';
- } catch (exception) {
- stringifySupported = false;
- }
- }
- isSupported = stringifySupported;
- }
- // Test `JSON.parse`.
- if (name == "json-parse") {
- var parse = exports.parse;
- if (typeof parse == "function") {
- try {
- // FF 3.1b1, b2 will throw an exception if a bare literal is provided.
- // Conforming implementations should also coerce the initial argument to
- // a string prior to parsing.
- if (parse("0") === 0 && !parse(false)) {
- // Simple parsing test.
- value = parse(serialized);
- var parseSupported = value["a"].length == 5 && value["a"][0] === 1;
- if (parseSupported) {
- try {
- // Safari <= 5.1.2 and FF 3.1b1 allow unescaped tabs in strings.
- parseSupported = !parse('"\t"');
- } catch (exception) {}
- if (parseSupported) {
- try {
- // FF 4.0 and 4.0.1 allow leading `+` signs and leading
- // decimal points. FF 4.0, 4.0.1, and IE 9-10 also allow
- // certain octal literals.
- parseSupported = parse("01") !== 1;
- } catch (exception) {}
- }
- if (parseSupported) {
- try {
- // FF 4.0, 4.0.1, and Rhino 1.7R3-R4 allow trailing decimal
- // points. These environments, along with FF 3.1b1 and 2,
- // also allow trailing commas in JSON objects and arrays.
- parseSupported = parse("1.") !== 1;
- } catch (exception) {}
- }
- }
- }
- } catch (exception) {
- parseSupported = false;
- }
- }
- isSupported = parseSupported;
- }
- }
- return has[name] = !!isSupported;
- }
-
- if (!has("json")) {
- // Common `[[Class]]` name aliases.
- var functionClass = "[object Function]",
- dateClass = "[object Date]",
- numberClass = "[object Number]",
- stringClass = "[object String]",
- arrayClass = "[object Array]",
- booleanClass = "[object Boolean]";
-
- // Detect incomplete support for accessing string characters by index.
- var charIndexBuggy = has("bug-string-char-index");
-
- // Define additional utility methods if the `Date` methods are buggy.
- if (!isExtended) {
- var floor = Math.floor;
- // A mapping between the months of the year and the number of days between
- // January 1st and the first of the respective month.
- var Months = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
- // Internal: Calculates the number of days between the Unix epoch and the
- // first day of the given month.
- var getDay = function (year, month) {
- return Months[month] + 365 * (year - 1970) + floor((year - 1969 + (month = +(month > 1))) / 4) - floor((year - 1901 + month) / 100) + floor((year - 1601 + month) / 400);
- };
- }
-
- // Internal: Determines if a property is a direct property of the given
- // object. Delegates to the native `Object#hasOwnProperty` method.
- if (!(isProperty = objectProto.hasOwnProperty)) {
- isProperty = function (property) {
- var members = {}, constructor;
- if ((members.__proto__ = null, members.__proto__ = {
- // The *proto* property cannot be set multiple times in recent
- // versions of Firefox and SeaMonkey.
- "toString": 1
- }, members).toString != getClass) {
- // Safari <= 2.0.3 doesn't implement `Object#hasOwnProperty`, but
- // supports the mutable *proto* property.
- isProperty = function (property) {
- // Capture and break the object's prototype chain (see section 8.6.2
- // of the ES 5.1 spec). The parenthesized expression prevents an
- // unsafe transformation by the Closure Compiler.
- var original = this.__proto__, result = property in (this.__proto__ = null, this);
- // Restore the original prototype chain.
- this.__proto__ = original;
- return result;
- };
- } else {
- // Capture a reference to the top-level `Object` constructor.
- constructor = members.constructor;
- // Use the `constructor` property to simulate `Object#hasOwnProperty` in
- // other environments.
- isProperty = function (property) {
- var parent = (this.constructor || constructor).prototype;
- return property in this && !(property in parent && this[property] === parent[property]);
- };
- }
- members = null;
- return isProperty.call(this, property);
- };
- }
-
- // Internal: Normalizes the `for...in` iteration algorithm across
- // environments. Each enumerated key is yielded to a `callback` function.
- forEach = function (object, callback) {
- var size = 0, Properties, members, property;
-
- // Tests for bugs in the current environment's `for...in` algorithm. The
- // `valueOf` property inherits the non-enumerable flag from
- // `Object.prototype` in older versions of IE, Netscape, and Mozilla.
- (Properties = function () {
- this.valueOf = 0;
- }).prototype.valueOf = 0;
-
- // Iterate over a new instance of the `Properties` class.
- members = new Properties();
- for (property in members) {
- // Ignore all properties inherited from `Object.prototype`.
- if (isProperty.call(members, property)) {
- size++;
- }
- }
- Properties = members = null;
-
- // Normalize the iteration algorithm.
- if (!size) {
- // A list of non-enumerable properties inherited from `Object.prototype`.
- members = ["valueOf", "toString", "toLocaleString", "propertyIsEnumerable", "isPrototypeOf", "hasOwnProperty", "constructor"];
- // IE <= 8, Mozilla 1.0, and Netscape 6.2 ignore shadowed non-enumerable
- // properties.
- forEach = function (object, callback) {
- var isFunction = getClass.call(object) == functionClass, property, length;
- var hasProperty = !isFunction && typeof object.constructor != "function" && objectTypes[typeof object.hasOwnProperty] && object.hasOwnProperty || isProperty;
- for (property in object) {
- // Gecko <= 1.0 enumerates the `prototype` property of functions under
- // certain conditions; IE does not.
- if (!(isFunction && property == "prototype") && hasProperty.call(object, property)) {
- callback(property);
- }
- }
- // Manually invoke the callback for each non-enumerable property.
- for (length = members.length; property = members[--length]; hasProperty.call(object, property) && callback(property));
- };
- } else if (size == 2) {
- // Safari <= 2.0.4 enumerates shadowed properties twice.
- forEach = function (object, callback) {
- // Create a set of iterated properties.
- var members = {}, isFunction = getClass.call(object) == functionClass, property;
- for (property in object) {
- // Store each property name to prevent double enumeration. The
- // `prototype` property of functions is not enumerated due to cross-
- // environment inconsistencies.
- if (!(isFunction && property == "prototype") && !isProperty.call(members, property) && (members[property] = 1) && isProperty.call(object, property)) {
- callback(property);
- }
- }
- };
- } else {
- // No bugs detected; use the standard `for...in` algorithm.
- forEach = function (object, callback) {
- var isFunction = getClass.call(object) == functionClass, property, isConstructor;
- for (property in object) {
- if (!(isFunction && property == "prototype") && isProperty.call(object, property) && !(isConstructor = property === "constructor")) {
- callback(property);
- }
- }
- // Manually invoke the callback for the `constructor` property due to
- // cross-environment inconsistencies.
- if (isConstructor || isProperty.call(object, (property = "constructor"))) {
- callback(property);
- }
- };
- }
- return forEach(object, callback);
- };
-
- // Public: Serializes a JavaScript `value` as a JSON string. The optional
- // `filter` argument may specify either a function that alters how object and
- // array members are serialized, or an array of strings and numbers that
- // indicates which properties should be serialized. The optional `width`
- // argument may be either a string or number that specifies the indentation
- // level of the output.
- if (!has("json-stringify")) {
- // Internal: A map of control characters and their escaped equivalents.
- var Escapes = {
- 92: "\\\\",
- 34: '\\"',
- 8: "\\b",
- 12: "\\f",
- 10: "\\n",
- 13: "\\r",
- 9: "\\t"
- };
-
- // Internal: Converts `value` into a zero-padded string such that its
- // length is at least equal to `width`. The `width` must be <= 6.
- var leadingZeroes = "000000";
- var toPaddedString = function (width, value) {
- // The `|| 0` expression is necessary to work around a bug in
- // Opera <= 7.54u2 where `0 == -0`, but `String(-0) !== "0"`.
- return (leadingZeroes + (value || 0)).slice(-width);
- };
-
- // Internal: Double-quotes a string `value`, replacing all ASCII control
- // characters (characters with code unit values between 0 and 31) with
- // their escaped equivalents. This is an implementation of the
- // `Quote(value)` operation defined in ES 5.1 section 15.12.3.
- var unicodePrefix = "\\u00";
- var quote = function (value) {
- var result = '"', index = 0, length = value.length, useCharIndex = !charIndexBuggy || length > 10;
- var symbols = useCharIndex && (charIndexBuggy ? value.split("") : value);
- for (; index < length; index++) {
- var charCode = value.charCodeAt(index);
- // If the character is a control character, append its Unicode or
- // shorthand escape sequence; otherwise, append the character as-is.
- switch (charCode) {
- case 8: case 9: case 10: case 12: case 13: case 34: case 92:
- result += Escapes[charCode];
- break;
- default:
- if (charCode < 32) {
- result += unicodePrefix + toPaddedString(2, charCode.toString(16));
- break;
- }
- result += useCharIndex ? symbols[index] : value.charAt(index);
- }
- }
- return result + '"';
- };
-
- // Internal: Recursively serializes an object. Implements the
- // `Str(key, holder)`, `JO(value)`, and `JA(value)` operations.
- var serialize = function (property, object, callback, properties, whitespace, indentation, stack) {
- var value, className, year, month, date, time, hours, minutes, seconds, milliseconds, results, element, index, length, prefix, result;
- try {
- // Necessary for host object support.
- value = object[property];
- } catch (exception) {}
- if (typeof value == "object" && value) {
- className = getClass.call(value);
- if (className == dateClass && !isProperty.call(value, "toJSON")) {
- if (value > -1 / 0 && value < 1 / 0) {
- // Dates are serialized according to the `Date#toJSON` method
- // specified in ES 5.1 section 15.9.5.44. See section 15.9.1.15
- // for the ISO 8601 date time string format.
- if (getDay) {
- // Manually compute the year, month, date, hours, minutes,
- // seconds, and milliseconds if the `getUTC*` methods are
- // buggy. Adapted from @Yaffle's `date-shim` project.
- date = floor(value / 864e5);
- for (year = floor(date / 365.2425) + 1970 - 1; getDay(year + 1, 0) <= date; year++);
- for (month = floor((date - getDay(year, 0)) / 30.42); getDay(year, month + 1) <= date; month++);
- date = 1 + date - getDay(year, month);
- // The `time` value specifies the time within the day (see ES
- // 5.1 section 15.9.1.2). The formula `(A % B + B) % B` is used
- // to compute `A modulo B`, as the `%` operator does not
- // correspond to the `modulo` operation for negative numbers.
- time = (value % 864e5 + 864e5) % 864e5;
- // The hours, minutes, seconds, and milliseconds are obtained by
- // decomposing the time within the day. See section 15.9.1.10.
- hours = floor(time / 36e5) % 24;
- minutes = floor(time / 6e4) % 60;
- seconds = floor(time / 1e3) % 60;
- milliseconds = time % 1e3;
- } else {
- year = value.getUTCFullYear();
- month = value.getUTCMonth();
- date = value.getUTCDate();
- hours = value.getUTCHours();
- minutes = value.getUTCMinutes();
- seconds = value.getUTCSeconds();
- milliseconds = value.getUTCMilliseconds();
- }
- // Serialize extended years correctly.
- value = (year <= 0 || year >= 1e4 ? (year < 0 ? "-" : "+") + toPaddedString(6, year < 0 ? -year : year) : toPaddedString(4, year)) +
- "-" + toPaddedString(2, month + 1) + "-" + toPaddedString(2, date) +
- // Months, dates, hours, minutes, and seconds should have two
- // digits; milliseconds should have three.
- "T" + toPaddedString(2, hours) + ":" + toPaddedString(2, minutes) + ":" + toPaddedString(2, seconds) +
- // Milliseconds are optional in ES 5.0, but required in 5.1.
- "." + toPaddedString(3, milliseconds) + "Z";
- } else {
- value = null;
- }
- } else if (typeof value.toJSON == "function" && ((className != numberClass && className != stringClass && className != arrayClass) || isProperty.call(value, "toJSON"))) {
- // Prototype <= 1.6.1 adds non-standard `toJSON` methods to the
- // `Number`, `String`, `Date`, and `Array` prototypes. JSON 3
- // ignores all `toJSON` methods on these objects unless they are
- // defined directly on an instance.
- value = value.toJSON(property);
- }
- }
- if (callback) {
- // If a replacement function was provided, call it to obtain the value
- // for serialization.
- value = callback.call(object, property, value);
- }
- if (value === null) {
- return "null";
- }
- className = getClass.call(value);
- if (className == booleanClass) {
- // Booleans are represented literally.
- return "" + value;
- } else if (className == numberClass) {
- // JSON numbers must be finite. `Infinity` and `NaN` are serialized as
- // `"null"`.
- return value > -1 / 0 && value < 1 / 0 ? "" + value : "null";
- } else if (className == stringClass) {
- // Strings are double-quoted and escaped.
- return quote("" + value);
- }
- // Recursively serialize objects and arrays.
- if (typeof value == "object") {
- // Check for cyclic structures. This is a linear search; performance
- // is inversely proportional to the number of unique nested objects.
- for (length = stack.length; length--;) {
- if (stack[length] === value) {
- // Cyclic structures cannot be serialized by `JSON.stringify`.
- throw TypeError();
- }
- }
- // Add the object to the stack of traversed objects.
- stack.push(value);
- results = [];
- // Save the current indentation level and indent one additional level.
- prefix = indentation;
- indentation += whitespace;
- if (className == arrayClass) {
- // Recursively serialize array elements.
- for (index = 0, length = value.length; index < length; index++) {
- element = serialize(index, value, callback, properties, whitespace, indentation, stack);
- results.push(element === undef ? "null" : element);
- }
- result = results.length ? (whitespace ? "[\n" + indentation + results.join(",\n" + indentation) + "\n" + prefix + "]" : ("[" + results.join(",") + "]")) : "[]";
- } else {
- // Recursively serialize object members. Members are selected from
- // either a user-specified list of property names, or the object
- // itself.
- forEach(properties || value, function (property) {
- var element = serialize(property, value, callback, properties, whitespace, indentation, stack);
- if (element !== undef) {
- // According to ES 5.1 section 15.12.3: "If `gap` {whitespace}
- // is not the empty string, let `member` {quote(property) + ":"}
- // be the concatenation of `member` and the `space` character."
- // The "`space` character" refers to the literal space
- // character, not the `space` {width} argument provided to
- // `JSON.stringify`.
- results.push(quote(property) + ":" + (whitespace ? " " : "") + element);
- }
- });
- result = results.length ? (whitespace ? "{\n" + indentation + results.join(",\n" + indentation) + "\n" + prefix + "}" : ("{" + results.join(",") + "}")) : "{}";
- }
- // Remove the object from the traversed object stack.
- stack.pop();
- return result;
- }
- };
-
- // Public: `JSON.stringify`. See ES 5.1 section 15.12.3.
- exports.stringify = function (source, filter, width) {
- var whitespace, callback, properties, className;
- if (objectTypes[typeof filter] && filter) {
- if ((className = getClass.call(filter)) == functionClass) {
- callback = filter;
- } else if (className == arrayClass) {
- // Convert the property names array into a makeshift set.
- properties = {};
- for (var index = 0, length = filter.length, value; index < length; value = filter[index++], ((className = getClass.call(value)), className == stringClass || className == numberClass) && (properties[value] = 1));
- }
- }
- if (width) {
- if ((className = getClass.call(width)) == numberClass) {
- // Convert the `width` to an integer and create a string containing
- // `width` number of space characters.
- if ((width -= width % 1) > 0) {
- for (whitespace = "", width > 10 && (width = 10); whitespace.length < width; whitespace += " ");
- }
- } else if (className == stringClass) {
- whitespace = width.length <= 10 ? width : width.slice(0, 10);
- }
- }
- // Opera <= 7.54u2 discards the values associated with empty string keys
- // (`""`) only if they are used directly within an object member list
- // (e.g., `!("" in { "": 1})`).
- return serialize("", (value = {}, value[""] = source, value), callback, properties, whitespace, "", []);
- };
- }
-
- // Public: Parses a JSON source string.
- if (!has("json-parse")) {
- var fromCharCode = String.fromCharCode;
-
- // Internal: A map of escaped control characters and their unescaped
- // equivalents.
- var Unescapes = {
- 92: "\\",
- 34: '"',
- 47: "/",
- 98: "\b",
- 116: "\t",
- 110: "\n",
- 102: "\f",
- 114: "\r"
- };
-
- // Internal: Stores the parser state.
- var Index, Source;
-
- // Internal: Resets the parser state and throws a `SyntaxError`.
- var abort = function () {
- Index = Source = null;
- throw SyntaxError();
- };
-
- // Internal: Returns the next token, or `"$"` if the parser has reached
- // the end of the source string. A token may be a string, number, `null`
- // literal, or Boolean literal.
- var lex = function () {
- var source = Source, length = source.length, value, begin, position, isSigned, charCode;
- while (Index < length) {
- charCode = source.charCodeAt(Index);
- switch (charCode) {
- case 9: case 10: case 13: case 32:
- // Skip whitespace tokens, including tabs, carriage returns, line
- // feeds, and space characters.
- Index++;
- break;
- case 123: case 125: case 91: case 93: case 58: case 44:
- // Parse a punctuator token (`{`, `}`, `[`, `]`, `:`, or `,`) at
- // the current position.
- value = charIndexBuggy ? source.charAt(Index) : source[Index];
- Index++;
- return value;
- case 34:
- // `"` delimits a JSON string; advance to the next character and
- // begin parsing the string. String tokens are prefixed with the
- // sentinel `@` character to distinguish them from punctuators and
- // end-of-string tokens.
- for (value = "@", Index++; Index < length;) {
- charCode = source.charCodeAt(Index);
- if (charCode < 32) {
- // Unescaped ASCII control characters (those with a code unit
- // less than the space character) are not permitted.
- abort();
- } else if (charCode == 92) {
- // A reverse solidus (`\`) marks the beginning of an escaped
- // control character (including `"`, `\`, and `/`) or Unicode
- // escape sequence.
- charCode = source.charCodeAt(++Index);
- switch (charCode) {
- case 92: case 34: case 47: case 98: case 116: case 110: case 102: case 114:
- // Revive escaped control characters.
- value += Unescapes[charCode];
- Index++;
- break;
- case 117:
- // `\u` marks the beginning of a Unicode escape sequence.
- // Advance to the first character and validate the
- // four-digit code point.
- begin = ++Index;
- for (position = Index + 4; Index < position; Index++) {
- charCode = source.charCodeAt(Index);
- // A valid sequence comprises four hexdigits (case-
- // insensitive) that form a single hexadecimal value.
- if (!(charCode >= 48 && charCode <= 57 || charCode >= 97 && charCode <= 102 || charCode >= 65 && charCode <= 70)) {
- // Invalid Unicode escape sequence.
- abort();
- }
- }
- // Revive the escaped character.
- value += fromCharCode("0x" + source.slice(begin, Index));
- break;
- default:
- // Invalid escape sequence.
- abort();
- }
- } else {
- if (charCode == 34) {
- // An unescaped double-quote character marks the end of the
- // string.
- break;
- }
- charCode = source.charCodeAt(Index);
- begin = Index;
- // Optimize for the common case where a string is valid.
- while (charCode >= 32 && charCode != 92 && charCode != 34) {
- charCode = source.charCodeAt(++Index);
- }
- // Append the string as-is.
- value += source.slice(begin, Index);
- }
- }
- if (source.charCodeAt(Index) == 34) {
- // Advance to the next character and return the revived string.
- Index++;
- return value;
- }
- // Unterminated string.
- abort();
- default:
- // Parse numbers and literals.
- begin = Index;
- // Advance past the negative sign, if one is specified.
- if (charCode == 45) {
- isSigned = true;
- charCode = source.charCodeAt(++Index);
- }
- // Parse an integer or floating-point value.
- if (charCode >= 48 && charCode <= 57) {
- // Leading zeroes are interpreted as octal literals.
- if (charCode == 48 && ((charCode = source.charCodeAt(Index + 1)), charCode >= 48 && charCode <= 57)) {
- // Illegal octal literal.
- abort();
- }
- isSigned = false;
- // Parse the integer component.
- for (; Index < length && ((charCode = source.charCodeAt(Index)), charCode >= 48 && charCode <= 57); Index++);
- // Floats cannot contain a leading decimal point; however, this
- // case is already accounted for by the parser.
- if (source.charCodeAt(Index) == 46) {
- position = ++Index;
- // Parse the decimal component.
- for (; position < length && ((charCode = source.charCodeAt(position)), charCode >= 48 && charCode <= 57); position++);
- if (position == Index) {
- // Illegal trailing decimal.
- abort();
- }
- Index = position;
- }
- // Parse exponents. The `e` denoting the exponent is
- // case-insensitive.
- charCode = source.charCodeAt(Index);
- if (charCode == 101 || charCode == 69) {
- charCode = source.charCodeAt(++Index);
- // Skip past the sign following the exponent, if one is
- // specified.
- if (charCode == 43 || charCode == 45) {
- Index++;
- }
- // Parse the exponential component.
- for (position = Index; position < length && ((charCode = source.charCodeAt(position)), charCode >= 48 && charCode <= 57); position++);
- if (position == Index) {
- // Illegal empty exponent.
- abort();
- }
- Index = position;
- }
- // Coerce the parsed value to a JavaScript number.
- return +source.slice(begin, Index);
- }
- // A negative sign may only precede numbers.
- if (isSigned) {
- abort();
- }
- // `true`, `false`, and `null` literals.
- if (source.slice(Index, Index + 4) == "true") {
- Index += 4;
- return true;
- } else if (source.slice(Index, Index + 5) == "false") {
- Index += 5;
- return false;
- } else if (source.slice(Index, Index + 4) == "null") {
- Index += 4;
- return null;
- }
- // Unrecognized token.
- abort();
- }
- }
- // Return the sentinel `$` character if the parser has reached the end
- // of the source string.
- return "$";
- };
-
- // Internal: Parses a JSON `value` token.
- var get = function (value) {
- var results, hasMembers;
- if (value == "$") {
- // Unexpected end of input.
- abort();
- }
- if (typeof value == "string") {
- if ((charIndexBuggy ? value.charAt(0) : value[0]) == "@") {
- // Remove the sentinel `@` character.
- return value.slice(1);
- }
- // Parse object and array literals.
- if (value == "[") {
- // Parses a JSON array, returning a new JavaScript array.
- results = [];
- for (;; hasMembers || (hasMembers = true)) {
- value = lex();
- // A closing square bracket marks the end of the array literal.
- if (value == "]") {
- break;
- }
- // If the array literal contains elements, the current token
- // should be a comma separating the previous element from the
- // next.
- if (hasMembers) {
- if (value == ",") {
- value = lex();
- if (value == "]") {
- // Unexpected trailing `,` in array literal.
- abort();
- }
- } else {
- // A `,` must separate each array element.
- abort();
- }
- }
- // Elisions and leading commas are not permitted.
- if (value == ",") {
- abort();
- }
- results.push(get(value));
- }
- return results;
- } else if (value == "{") {
- // Parses a JSON object, returning a new JavaScript object.
- results = {};
- for (;; hasMembers || (hasMembers = true)) {
- value = lex();
- // A closing curly brace marks the end of the object literal.
- if (value == "}") {
- break;
- }
- // If the object literal contains members, the current token
- // should be a comma separator.
- if (hasMembers) {
- if (value == ",") {
- value = lex();
- if (value == "}") {
- // Unexpected trailing `,` in object literal.
- abort();
- }
- } else {
- // A `,` must separate each object member.
- abort();
- }
- }
- // Leading commas are not permitted, object property names must be
- // double-quoted strings, and a `:` must separate each property
- // name and value.
- if (value == "," || typeof value != "string" || (charIndexBuggy ? value.charAt(0) : value[0]) != "@" || lex() != ":") {
- abort();
- }
- results[value.slice(1)] = get(lex());
- }
- return results;
- }
- // Unexpected token encountered.
- abort();
- }
- return value;
- };
-
- // Internal: Updates a traversed object member.
- var update = function (source, property, callback) {
- var element = walk(source, property, callback);
- if (element === undef) {
- delete source[property];
- } else {
- source[property] = element;
- }
- };
-
- // Internal: Recursively traverses a parsed JSON object, invoking the
- // `callback` function for each value. This is an implementation of the
- // `Walk(holder, name)` operation defined in ES 5.1 section 15.12.2.
- var walk = function (source, property, callback) {
- var value = source[property], length;
- if (typeof value == "object" && value) {
- // `forEach` can't be used to traverse an array in Opera <= 8.54
- // because its `Object#hasOwnProperty` implementation returns `false`
- // for array indices (e.g., `![1, 2, 3].hasOwnProperty("0")`).
- if (getClass.call(value) == arrayClass) {
- for (length = value.length; length--;) {
- update(value, length, callback);
- }
- } else {
- forEach(value, function (property) {
- update(value, property, callback);
- });
- }
- }
- return callback.call(source, property, value);
- };
-
- // Public: `JSON.parse`. See ES 5.1 section 15.12.2.
- exports.parse = function (source, callback) {
- var result, value;
- Index = 0;
- Source = "" + source;
- result = get(lex());
- // If a JSON string contains multiple tokens, it is invalid.
- if (lex() != "$") {
- abort();
- }
- // Reset the parser state.
- Index = Source = null;
- return callback && getClass.call(callback) == functionClass ? walk((value = {}, value[""] = result, value), "", callback) : result;
- };
- }
- }
-
- exports["runInContext"] = runInContext;
- return exports;
- }
-
- if (freeExports && !isLoader) {
- // Export for CommonJS environments.
- runInContext(root, freeExports);
- } else {
- // Export for web browsers and JavaScript engines.
- var nativeJSON = root.JSON,
- previousJSON = root["JSON3"],
- isRestored = false;
-
- var JSON3 = runInContext(root, (root["JSON3"] = {
- // Public: Restores the original value of the global `JSON` object and
- // returns a reference to the `JSON3` object.
- "noConflict": function () {
- if (!isRestored) {
- isRestored = true;
- root.JSON = nativeJSON;
- root["JSON3"] = previousJSON;
- nativeJSON = previousJSON = null;
- }
- return JSON3;
- }
- }));
-
- root.JSON = {
- "parse": JSON3.parse,
- "stringify": JSON3.stringify
- };
- }
-
- // Export for asynchronous module loaders.
- if (isLoader) {
- define(function () {
- return JSON3;
- });
- }
- }).call(this);
- /************************************************************
- * end JSON
- ************************************************************/
-
- JSON2 = exports;
-
- })();
-}
-
-/* startjslint */
-/*jslint browser:true, plusplus:true, vars:true, nomen:true, evil:true, regexp: false, bitwise: true, white: true */
-/*global JSON2 */
-/*global window */
-/*global unescape */
-/*global ActiveXObject */
-/*members Piwik, encodeURIComponent, decodeURIComponent, getElementsByTagName,
- shift, unshift, piwikAsyncInit, piwikPluginAsyncInit, frameElement, self, hasFocus,
- createElement, appendChild, characterSet, charset, all,
- addEventListener, attachEvent, removeEventListener, detachEvent, disableCookies,
- cookie, domain, readyState, documentElement, doScroll, title, text,
- location, top, onerror, document, referrer, parent, links, href, protocol, name, GearsFactory,
- performance, mozPerformance, msPerformance, webkitPerformance, timing, requestStart,
- responseEnd, event, which, button, srcElement, type, target,
- parentNode, tagName, hostname, className,
- userAgent, cookieEnabled, platform, mimeTypes, enabledPlugin, javaEnabled,
- XMLHttpRequest, ActiveXObject, open, setRequestHeader, onreadystatechange, send, readyState, status,
- getTime, getTimeAlias, setTime, toGMTString, getHours, getMinutes, getSeconds,
- toLowerCase, toUpperCase, charAt, indexOf, lastIndexOf, split, slice,
- onload, src,
- min, round, random, floor,
- exec,
- res, width, height,
- pdf, qt, realp, wma, dir, fla, java, gears, ag,
- initialized, hook, getHook, getVisitorId, getVisitorInfo, setUserId, getUserId, setSiteId, getSiteId, setTrackerUrl, getTrackerUrl, appendToTrackingUrl, getRequest, addPlugin,
- getAttributionInfo, getAttributionCampaignName, getAttributionCampaignKeyword,
- getAttributionReferrerTimestamp, getAttributionReferrerUrl,
- setCustomData, getCustomData,
- setCustomRequestProcessing,
- setCustomVariable, getCustomVariable, deleteCustomVariable, storeCustomVariablesInCookie, setCustomDimension, getCustomDimension,
- deleteCustomDimension, setDownloadExtensions, addDownloadExtensions, removeDownloadExtensions,
- setDomains, setIgnoreClasses, setRequestMethod, setRequestContentType,
- setReferrerUrl, setCustomUrl, setAPIUrl, setDocumentTitle,
- setDownloadClasses, setLinkClasses,
- setCampaignNameKey, setCampaignKeywordKey,
- discardHashTag,
- setCookieNamePrefix, setCookieDomain, setCookiePath, setVisitorIdCookie,
- setVisitorCookieTimeout, setSessionCookieTimeout, setReferralCookieTimeout,
- setConversionAttributionFirstReferrer,
- disablePerformanceTracking, setGenerationTimeMs,
- doNotTrack, setDoNotTrack, msDoNotTrack, getValuesFromVisitorIdCookie,
- addListener, enableLinkTracking, enableJSErrorTracking, setLinkTrackingTimer,
- enableHeartBeatTimer, disableHeartBeatTimer, killFrame, redirectFile, setCountPreRendered,
- 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,
- getAttribute, hasAttribute, attributes, nodeName, findContentNodes, findContentNodes, findContentNodesWithinNode,
- findPieceNode, findTargetNodeNoDefault, findTargetNode, findContentPiece, children, hasNodeCssClass,
- getAttributeValueFromNode, hasNodeAttributeWithValue, hasNodeAttribute, findNodesByTagName, findMultiple,
- makeNodesUnique, concat, find, htmlCollectionToArray, offsetParent, value, nodeValue, findNodesHavingAttribute,
- findFirstNodeHavingAttribute, findFirstNodeHavingAttributeWithValue, getElementsByClassName,
- findNodesHavingCssClass, findFirstNodeHavingClass, isLinkElement, findParentContentNode, removeDomainIfIsInLink,
- findContentName, findMediaUrlInNode, toAbsoluteUrl, findContentTarget, getLocation, origin, host, isSameDomain,
- search, trim, getBoundingClientRect, bottom, right, left, innerWidth, innerHeight, clientWidth, clientHeight,
- isOrWasNodeInViewport, isNodeVisible, buildInteractionRequestParams, buildImpressionRequestParams,
- shouldIgnoreInteraction, setHrefAttribute, setAttribute, buildContentBlock, collectContent, setLocation,
- CONTENT_ATTR, CONTENT_CLASS, CONTENT_NAME_ATTR, CONTENT_PIECE_ATTR, CONTENT_PIECE_CLASS,
- CONTENT_TARGET_ATTR, CONTENT_TARGET_CLASS, CONTENT_IGNOREINTERACTION_ATTR, CONTENT_IGNOREINTERACTION_CLASS,
- trackCallbackOnLoad, trackCallbackOnReady, buildContentImpressionsRequests, wasContentImpressionAlreadyTracked,
- getQuery, getContent, getContentImpressionsRequestsFromNodes, buildContentInteractionTrackingRedirectUrl,
- buildContentInteractionRequestNode, buildContentInteractionRequest, buildContentImpressionRequest,
- appendContentInteractionToRequestIfPossible, setupInteractionsTracking, trackContentImpressionClickInteraction,
- internalIsNodeVisible, clearTrackedContentImpressions, getTrackerUrl, trackAllContentImpressions,
- getTrackedContentImpressions, getCurrentlyVisibleContentImpressionsRequestsIfNotTrackedYet,
- contentInteractionTrackingSetupDone, contains, match, pathname, piece, trackContentInteractionNode,
- trackContentInteractionNode, trackContentImpressionsWithinNode, trackContentImpression,
- enableTrackOnlyVisibleContent, trackContentInteraction, clearEnableTrackOnlyVisibleContent, logAllContentBlocksOnPage,
- trackVisibleContentImpressions, isTrackOnlyVisibleContentEnabled, port, isUrlToCurrentDomain,
- isNodeAuthorizedToTriggerInteraction, replaceHrefIfInternalLink, getConfigDownloadExtensions, disableLinkTracking,
- substr, setAnyAttribute, wasContentTargetAttrReplaced, max, abs, childNodes, compareDocumentPosition, body,
- getConfigVisitorCookieTimeout, getRemainingVisitorCookieTimeout, getDomains, getConfigCookiePath,
- getConfigIdPageView, 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, addTracker, removeAllAsyncTrackersButFirst
- */
-/*global _paq:true */
-/*members push */
-/*global Piwik:true */
-/*members addPlugin, getTracker, getAsyncTracker, getAsyncTrackers, addTracker, trigger, on, off, retryMissedPluginCalls,
- DOM, onLoad, onReady*/
-/*global Piwik_Overlay_Client */
-/*global AnalyticsTracker:true */
-/*members initialize */
-/*global define */
-/*members amd */
-/*global console:true */
-/*members error */
-/*members log */
-
-// asynchronous tracker (or proxy)
-if (typeof _paq !== 'object') {
- _paq = [];
-}
-
-// Piwik singleton and namespace
-if (typeof window.Piwik !== 'object') {
- window.Piwik = (function () {
- 'use strict';
-
- /************************************************************
- * Private data
- ************************************************************/
-
- var expireDateTime,
-
- /* plugins */
- plugins = {},
-
- eventHandlers = {},
-
- /* alias frequently used globals for added minification */
- documentAlias = document,
- navigatorAlias = navigator,
- screenAlias = screen,
- windowAlias = window,
-
- /* performance timing */
- performanceAlias = windowAlias.performance || windowAlias.mozPerformance || windowAlias.msPerformance || windowAlias.webkitPerformance,
-
- /* encode */
- encodeWrapper = windowAlias.encodeURIComponent,
-
- /* decode */
- decodeWrapper = windowAlias.decodeURIComponent,
-
- /* urldecode */
- urldecode = unescape,
-
- /* asynchronous tracker */
- asyncTrackers = [],
-
- /* iterator */
- iterator,
-
- /* local Piwik */
- Piwik,
-
- missedPluginTrackerCalls = [];
-
- /************************************************************
- * Private methods
- ************************************************************/
-
- /**
- * See https://github.com/piwik/piwik/issues/8413
- * To prevent Javascript Error: Uncaught URIError: URI malformed when encoding is not UTF-8. Use this method
- * instead of decodeWrapper if a text could contain any non UTF-8 encoded characters eg
- * a URL like http://apache.piwik/test.html?%F6%E4%FC or a link like
- * <a href="test-with-%F6%E4%FC/story/0">(encoded iso-8859-1 URL)</a>
- */
- function safeDecodeWrapper(url)
- {
- try {
- return decodeWrapper(url);
- } catch (e) {
- return unescape(url);
- }
- }
-
- /*
- * Is property defined?
- */
- function isDefined(property) {
- // workaround https://github.com/douglascrockford/JSLint/commit/24f63ada2f9d7ad65afc90e6d949f631935c2480
- var propertyType = typeof property;
-
- return propertyType !== 'undefined';
- }
-
- /*
- * Is property a function?
- */
- function isFunction(property) {
- return typeof property === 'function';
- }
-
- /*
- * Is property an object?
- *
- * @return bool Returns true if property is null, an Object, or subclass of Object (i.e., an instanceof String, Date, etc.)
- */
- function isObject(property) {
- return typeof property === 'object';
- }
-
- /*
- * Is property a string?
- */
- function isString(property) {
- return typeof property === 'string' || property instanceof String;
- }
-
- function isObjectEmpty(property)
- {
- if (!property) {
- return true;
- }
-
- var i;
- var isEmpty = true;
- for (i in property) {
- if (Object.prototype.hasOwnProperty.call(property, i)) {
- isEmpty = false;
- }
- }
-
- 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
- *
- * @param array parameterArray An array comprising either:
- * [ 'methodName', optional_parameters ]
- * or:
- * [ functionObject, optional_parameters ]
- */
- function apply() {
- var i, j, f, parameterArray, trackerCall;
-
- for (i = 0; i < arguments.length; i += 1) {
- trackerCall = null;
- if (arguments[i] && arguments[i].slice) {
- trackerCall = arguments[i].slice();
- }
- parameterArray = arguments[i];
- f = parameterArray.shift();
-
- var fParts, context;
-
- var isStaticPluginCall = isString(f) && f.indexOf('::') > 0;
- if (isStaticPluginCall) {
- // a static method will not be called on a tracker and is not dependent on the existance of a
- // tracker etc
- 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);
- } else if (trackerCall) {
- // we try to call that method again later as the plugin might not be loaded yet
- // a plugin can call "Piwik.retryMissedPluginCalls();" once it has been loaded and then the
- // method call to "Piwik[context][f]" may be executed
- missedPluginTrackerCalls.push(trackerCall);
- }
-
- } else {
- for (j = 0; j < asyncTrackers.length; j++) {
- if (isString(f)) {
- context = asyncTrackers[j];
-
- var isPluginTrackerCall = f.indexOf('.') > 0;
-
- if (isPluginTrackerCall) {
- fParts = f.split('.');
- if (context && 'object' === typeof context[fParts[0]]) {
- context = context[fParts[0]];
- f = fParts[1];
- } else if (trackerCall) {
- // we try to call that method again later as the plugin might not be loaded yet
- missedPluginTrackerCalls.push(trackerCall);
- break;
- }
- }
-
- 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) {
- // do not trigger an error if it is a call to a plugin as the plugin may just not be
- // loaded yet etc
- 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);
- }
- }
- }
- }
- }
-
- /*
- * Cross-browser helper function to add event handler
- */
- function addEventListener(element, eventType, eventHandler, useCapture) {
- if (element.addEventListener) {
- element.addEventListener(eventType, eventHandler, useCapture);
-
- return true;
- }
-
- if (element.attachEvent) {
- return element.attachEvent('on' + eventType, eventHandler);
- }
-
- element['on' + eventType] = eventHandler;
- }
-
- function trackCallbackOnLoad(callback)
- {
- if (documentAlias.readyState === 'complete') {
- callback();
- } else if (windowAlias.addEventListener) {
- windowAlias.addEventListener('load', callback);
- } else if (windowAlias.attachEvent) {
- windowAlias.attachEvent('onload', callback);
- }
- }
-
- function trackCallbackOnReady(callback)
- {
- var loaded = false;
-
- if (documentAlias.attachEvent) {
- loaded = documentAlias.readyState === 'complete';
- } else {
- loaded = documentAlias.readyState !== 'loading';
- }
-
- if (loaded) {
- callback();
- return;
- }
-
- var _timer;
-
- if (documentAlias.addEventListener) {
- 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();
- }
- }
- });
-
- if (documentAlias.documentElement.doScroll && windowAlias === windowAlias.top) {
- (function ready() {
- if (!loaded) {
- try {
- documentAlias.documentElement.doScroll('left');
- } catch (error) {
- setTimeout(ready, 0);
-
- return;
- }
- loaded = true;
- callback();
- }
- }());
- }
- }
-
- // fallback
- addEventListener(windowAlias, 'load', function () {
- if (!loaded) {
- loaded = true;
- callback();
- }
- }, false);
- }
-
- /*
- * Call plugin hook methods
- */
- function executePluginMethod(methodName, callback) {
- var result = '',
- i,
- pluginMethod, value;
-
- for (i in plugins) {
- if (Object.prototype.hasOwnProperty.call(plugins, i)) {
- pluginMethod = plugins[i][methodName];
-
- if (isFunction(pluginMethod)) {
- value = pluginMethod(callback);
- if (value) {
- result += value;
- }
- }
- }
- }
-
- return result;
- }
-
- /*
- * Handle beforeunload event
- *
- * Subject to Safari's "Runaway JavaScript Timer" and
- * Chrome V8 extension that terminates JS that exhibits
- * "slow unload", i.e., calling getTime() > 1000 times
- */
- function beforeUnloadHandler() {
- var now;
-
- executePluginMethod('unload');
- /*
- * Delay/pause (blocks UI)
- */
- if (expireDateTime) {
- // the things we do for backwards compatibility...
- // in ECMA-262 5th ed., we could simply use:
- // while (Date.now() < expireDateTime) { }
- do {
- now = new Date();
- } while (now.getTimeAlias() < expireDateTime);
- }
- }
-
- /*
- * Load JavaScript file (asynchronously)
- */
- function loadScript(src, onLoad) {
- var script = documentAlias.createElement('script');
-
- script.type = 'text/javascript';
- script.src = src;
-
- if (script.readyState) {
- script.onreadystatechange = function () {
- var state = this.readyState;
-
- if (state === 'loaded' || state === 'complete') {
- script.onreadystatechange = null;
- onLoad();
- }
- };
- } else {
- script.onload = onLoad;
- }
-
- documentAlias.getElementsByTagName('head')[0].appendChild(script);
- }
-
- /*
- * Get page referrer
- */
- function getReferrer() {
- var referrer = '';
-
- try {
- referrer = windowAlias.top.document.referrer;
- } catch (e) {
- if (windowAlias.parent) {
- try {
- referrer = windowAlias.parent.document.referrer;
- } catch (e2) {
- referrer = '';
- }
- }
- }
-
- if (referrer === '') {
- referrer = documentAlias.referrer;
- }
-
- return referrer;
- }
-
- /*
- * Extract scheme/protocol from URL
- */
- function getProtocolScheme(url) {
- var e = new RegExp('^([a-z]+):'),
- matches = e.exec(url);
-
- return matches ? matches[1] : null;
- }
-
- /*
- * Extract hostname from URL
- */
- function getHostName(url) {
- // scheme : // [username [: password] @] hostame [: port] [/ [path] [? query] [# fragment]]
- var e = new RegExp('^(?:(?:https?|ftp):)/*(?:[^@]+@)?([^:/#]+)'),
- matches = e.exec(url);
-
- return matches ? matches[1] : url;
- }
-
- /*
- * Extract parameter from URL
- */
- function getParameter(url, name) {
- var regexSearch = "[\\?&#]" + name + "=([^&#]*)";
- var regex = new RegExp(regexSearch);
- var results = regex.exec(url);
- return results ? decodeWrapper(results[1]) : '';
- }
-
- /*
- * UTF-8 encoding
- */
- function utf8_encode(argString) {
- return unescape(encodeWrapper(argString));
- }
-
- /************************************************************
- * sha1
- * - based on sha1 from http://phpjs.org/functions/sha1:512 (MIT / GPL v2)
- ************************************************************/
-
- function sha1(str) {
- // + original by: Webtoolkit.info (http://www.webtoolkit.info/)
- // + namespaced by: Michael White (http://getsprink.com)
- // + input by: Brett Zamir (http://brett-zamir.me)
- // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
- // + jslinted by: Anthon Pang (http://piwik.org)
-
- var
- rotate_left = function (n, s) {
- return (n << s) | (n >>> (32 - s));
- },
-
- cvt_hex = function (val) {
- var strout = '',
- i,
- v;
-
- for (i = 7; i >= 0; i--) {
- v = (val >>> (i * 4)) & 0x0f;
- strout += v.toString(16);
- }
-
- return strout;
- },
-
- blockstart,
- i,
- j,
- W = [],
- H0 = 0x67452301,
- H1 = 0xEFCDAB89,
- H2 = 0x98BADCFE,
- H3 = 0x10325476,
- H4 = 0xC3D2E1F0,
- A,
- B,
- C,
- D,
- E,
- temp,
- str_len,
- word_array = [];
-
- str = utf8_encode(str);
- str_len = str.length;
-
- for (i = 0; i < str_len - 3; i += 4) {
- j = str.charCodeAt(i) << 24 | str.charCodeAt(i + 1) << 16 |
- str.charCodeAt(i + 2) << 8 | str.charCodeAt(i + 3);
- word_array.push(j);
- }
-
- switch (str_len & 3) {
- case 0:
- i = 0x080000000;
- break;
- case 1:
- i = str.charCodeAt(str_len - 1) << 24 | 0x0800000;
- break;
- case 2:
- i = str.charCodeAt(str_len - 2) << 24 | str.charCodeAt(str_len - 1) << 16 | 0x08000;
- break;
- case 3:
- i = str.charCodeAt(str_len - 3) << 24 | str.charCodeAt(str_len - 2) << 16 | str.charCodeAt(str_len - 1) << 8 | 0x80;
- break;
- }
-
- word_array.push(i);
-
- while ((word_array.length & 15) !== 14) {
- word_array.push(0);
- }
-
- word_array.push(str_len >>> 29);
- word_array.push((str_len << 3) & 0x0ffffffff);
-
- for (blockstart = 0; blockstart < word_array.length; blockstart += 16) {
- for (i = 0; i < 16; i++) {
- W[i] = word_array[blockstart + i];
- }
-
- for (i = 16; i <= 79; i++) {
- W[i] = rotate_left(W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16], 1);
- }
-
- A = H0;
- B = H1;
- C = H2;
- D = H3;
- E = H4;
-
- for (i = 0; i <= 19; i++) {
- temp = (rotate_left(A, 5) + ((B & C) | (~B & D)) + E + W[i] + 0x5A827999) & 0x0ffffffff;
- E = D;
- D = C;
- C = rotate_left(B, 30);
- B = A;
- A = temp;
- }
-
- for (i = 20; i <= 39; i++) {
- temp = (rotate_left(A, 5) + (B ^ C ^ D) + E + W[i] + 0x6ED9EBA1) & 0x0ffffffff;
- E = D;
- D = C;
- C = rotate_left(B, 30);
- B = A;
- A = temp;
- }
-
- for (i = 40; i <= 59; i++) {
- temp = (rotate_left(A, 5) + ((B & C) | (B & D) | (C & D)) + E + W[i] + 0x8F1BBCDC) & 0x0ffffffff;
- E = D;
- D = C;
- C = rotate_left(B, 30);
- B = A;
- A = temp;
- }
-
- for (i = 60; i <= 79; i++) {
- temp = (rotate_left(A, 5) + (B ^ C ^ D) + E + W[i] + 0xCA62C1D6) & 0x0ffffffff;
- E = D;
- D = C;
- C = rotate_left(B, 30);
- B = A;
- A = temp;
- }
-
- H0 = (H0 + A) & 0x0ffffffff;
- H1 = (H1 + B) & 0x0ffffffff;
- H2 = (H2 + C) & 0x0ffffffff;
- H3 = (H3 + D) & 0x0ffffffff;
- H4 = (H4 + E) & 0x0ffffffff;
- }
-
- temp = cvt_hex(H0) + cvt_hex(H1) + cvt_hex(H2) + cvt_hex(H3) + cvt_hex(H4);
-
- return temp.toLowerCase();
- }
-
- /************************************************************
- * end sha1
- ************************************************************/
-
- /*
- * Fix-up URL when page rendered from search engine cache or translated page
- */
- function urlFixup(hostName, href, referrer) {
- if (!hostName) {
- hostName = '';
- }
-
- if (!href) {
- href = '';
- }
-
- if (hostName === 'translate.googleusercontent.com') { // Google
- if (referrer === '') {
- referrer = href;
- }
-
- href = getParameter(href, 'u');
- hostName = getHostName(href);
- } else if (hostName === 'cc.bingj.com' || // Bing
- hostName === 'webcache.googleusercontent.com' || // Google
- hostName.slice(0, 5) === '74.6.') { // Yahoo (via Inktomi 74.6.0.0/16)
- href = documentAlias.links[0].href;
- hostName = getHostName(href);
- }
-
- return [hostName, href, referrer];
- }
-
- /*
- * Fix-up domain
- */
- function domainFixup(domain) {
- var dl = domain.length;
-
- // remove trailing '.'
- if (domain.charAt(--dl) === '.') {
- domain = domain.slice(0, dl);
- }
-
- // remove leading '*'
- if (domain.slice(0, 2) === '*.') {
- domain = domain.slice(1);
- }
-
- if (domain.indexOf('/') !== -1) {
- domain = domain.substr(0, domain.indexOf('/'));
- }
-
- return domain;
- }
-
- /*
- * Title fixup
- */
- function titleFixup(title) {
- title = title && title.text ? title.text : title;
-
- if (!isString(title)) {
- var tmp = documentAlias.getElementsByTagName('title');
-
- if (tmp && isDefined(tmp[0])) {
- title = tmp[0].text;
- }
- }
-
- return title;
- }
-
- function getChildrenFromNode(node)
- {
- if (!node) {
- return [];
- }
-
- if (!isDefined(node.children) && isDefined(node.childNodes)) {
- return node.children;
- }
-
- if (isDefined(node.children)) {
- return node.children;
- }
-
- return [];
- }
-
- function containsNodeElement(node, containedNode)
- {
- if (!node || !containedNode) {
- return false;
- }
-
- if (node.contains) {
- return node.contains(containedNode);
- }
-
- if (node === containedNode) {
- return true;
- }
-
- if (node.compareDocumentPosition) {
- return !!(node.compareDocumentPosition(containedNode) & 16);
- }
-
- return false;
- }
-
- // Polyfill for IndexOf for IE6-IE8
- function indexOfArray(theArray, searchElement)
- {
- if (theArray && theArray.indexOf) {
- return theArray.indexOf(searchElement);
- }
-
- // 1. Let O be the result of calling ToObject passing
- // the this value as the argument.
- if (!isDefined(theArray) || theArray === null) {
- return -1;
- }
-
- if (!theArray.length) {
- return -1;
- }
-
- var len = theArray.length;
-
- if (len === 0) {
- return -1;
- }
-
- var k = 0;
-
- // 9. Repeat, while k < len
- while (k < len) {
- // a. Let Pk be ToString(k).
- // This is implicit for LHS operands of the in operator
- // b. Let kPresent be the result of calling the
- // HasProperty internal method of O with argument Pk.
- // This step can be combined with c
- // c. If kPresent is true, then
- // i. Let elementK be the result of calling the Get
- // internal method of O with the argument ToString(k).
- // ii. Let same be the result of applying the
- // Strict Equality Comparison Algorithm to
- // searchElement and elementK.
- // iii. If same is true, return k.
- if (theArray[k] === searchElement) {
- return k;
- }
- k++;
- }
- 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
- ************************************************************/
-
- /**
- * Author: Jason Farrell
- * Author URI: http://useallfive.com/
- *
- * Description: Checks if a DOM element is truly visible.
- * Package URL: https://github.com/UseAllFive/true-visibility
- * License: MIT (https://github.com/UseAllFive/true-visibility/blob/master/LICENSE.txt)
- */
- function isVisible(node) {
-
- if (!node) {
- return false;
- }
-
- //-- Cross browser method to get style properties:
- function _getStyle(el, property) {
- if (windowAlias.getComputedStyle) {
- return documentAlias.defaultView.getComputedStyle(el,null)[property];
- }
- if (el.currentStyle) {
- return el.currentStyle[property];
- }
- }
-
- function _elementInDocument(element) {
- element = element.parentNode;
-
- while (element) {
- if (element === documentAlias) {
- return true;
- }
- element = element.parentNode;
- }
- return false;
- }
-
- /**
- * Checks if a DOM element is visible. Takes into
- * consideration its parents and overflow.
- *
- * @param (el) the DOM element to check if is visible
- *
- * These params are optional that are sent in recursively,
- * you typically won't use these:
- *
- * @param (t) Top corner position number
- * @param (r) Right corner position number
- * @param (b) Bottom corner position number
- * @param (l) Left corner position number
- * @param (w) Element width number
- * @param (h) Element height number
- */
- function _isVisible(el, t, r, b, l, w, h) {
- var p = el.parentNode,
- VISIBLE_PADDING = 1; // has to be visible at least one px of the element
-
- if (!_elementInDocument(el)) {
- return false;
- }
-
- //-- Return true for document node
- if (9 === p.nodeType) {
- return true;
- }
-
- //-- Return false if our element is invisible
- if (
- '0' === _getStyle(el, 'opacity') ||
- 'none' === _getStyle(el, 'display') ||
- 'hidden' === _getStyle(el, 'visibility')
- ) {
- return false;
- }
-
- if (!isDefined(t) ||
- !isDefined(r) ||
- !isDefined(b) ||
- !isDefined(l) ||
- !isDefined(w) ||
- !isDefined(h)) {
- t = el.offsetTop;
- l = el.offsetLeft;
- b = t + el.offsetHeight;
- r = l + el.offsetWidth;
- w = el.offsetWidth;
- h = el.offsetHeight;
- }
-
- if (node === el && (0 === h || 0 === w) && 'hidden' === _getStyle(el, 'overflow')) {
- return false;
- }
-
- //-- If we have a parent, let's continue:
- if (p) {
- //-- Check if the parent can hide its children.
- if (('hidden' === _getStyle(p, 'overflow') || 'scroll' === _getStyle(p, 'overflow'))) {
- //-- Only check if the offset is different for the parent
- if (
- //-- If the target element is to the right of the parent elm
- l + VISIBLE_PADDING > p.offsetWidth + p.scrollLeft ||
- //-- If the target element is to the left of the parent elm
- l + w - VISIBLE_PADDING < p.scrollLeft ||
- //-- If the target element is under the parent elm
- t + VISIBLE_PADDING > p.offsetHeight + p.scrollTop ||
- //-- If the target element is above the parent elm
- t + h - VISIBLE_PADDING < p.scrollTop
- ) {
- //-- Our target element is out of bounds:
- return false;
- }
- }
- //-- Add the offset parent's left/top coords to our element's offset:
- if (el.offsetParent === p) {
- l += p.offsetLeft;
- t += p.offsetTop;
- }
- //-- Let's recursively check upwards:
- return _isVisible(p, t, r, b, l, w, h);
- }
- return true;
- }
-
- return _isVisible(node);
- }
-
- /************************************************************
- * Query
- ************************************************************/
-
- var query = {
- htmlCollectionToArray: function (foundNodes)
- {
- var nodes = [], index;
-
- if (!foundNodes || !foundNodes.length) {
- return nodes;
- }
-
- for (index = 0; index < foundNodes.length; index++) {
- nodes.push(foundNodes[index]);
- }
-
- return nodes;
- },
- find: function (selector)
- {
- // we use querySelectorAll only on document, not on nodes because of its unexpected behavior. See for
- // instance http://stackoverflow.com/questions/11503534/jquery-vs-document-queryselectorall and
- // http://jsfiddle.net/QdMc5/ and http://ejohn.org/blog/thoughts-on-queryselectorall
- if (!document.querySelectorAll || !selector) {
- return []; // we do not support all browsers
- }
-
- var foundNodes = document.querySelectorAll(selector);
-
- return this.htmlCollectionToArray(foundNodes);
- },
- findMultiple: function (selectors)
- {
- if (!selectors || !selectors.length) {
- return [];
- }
-
- var index, foundNodes;
- var nodes = [];
- for (index = 0; index < selectors.length; index++) {
- foundNodes = this.find(selectors[index]);
- nodes = nodes.concat(foundNodes);
- }
-
- nodes = this.makeNodesUnique(nodes);
-
- return nodes;
- },
- findNodesByTagName: function (node, tagName)
- {
- if (!node || !tagName || !node.getElementsByTagName) {
- return [];
- }
-
- var foundNodes = node.getElementsByTagName(tagName);
-
- return this.htmlCollectionToArray(foundNodes);
- },
- makeNodesUnique: function (nodes)
- {
- var copy = [].concat(nodes);
- nodes.sort(function(n1, n2){
- if (n1 === n2) {
- return 0;
- }
-
- var index1 = indexOfArray(copy, n1);
- var index2 = indexOfArray(copy, n2);
-
- if (index1 === index2) {
- return 0;
- }
-
- return index1 > index2 ? -1 : 1;
- });
-
- if (nodes.length <= 1) {
- return nodes;
- }
-
- var index = 0;
- var numDuplicates = 0;
- var duplicates = [];
- var node;
-
- node = nodes[index++];
-
- while (node) {
- if (node === nodes[index]) {
- numDuplicates = duplicates.push(index);
- }
-
- node = nodes[index++] || null;
- }
-
- while (numDuplicates--) {
- nodes.splice(duplicates[numDuplicates], 1);
- }
-
- return nodes;
- },
- getAttributeValueFromNode: function (node, attributeName)
- {
- if (!this.hasNodeAttribute(node, attributeName)) {
- return;
- }
-
- if (node && node.getAttribute) {
- return node.getAttribute(attributeName);
- }
-
- if (!node || !node.attributes) {
- return;
- }
-
- var typeOfAttr = (typeof node.attributes[attributeName]);
- if ('undefined' === typeOfAttr) {
- return;
- }
-
- 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;
- }
-
- var index;
- var attrs = node.attributes;
-
- if (!attrs) {
- return;
- }
-
- for (index = 0; index < attrs.length; index++) {
- if (attrs[index].nodeName === attributeName) {
- return attrs[index].nodeValue;
- }
- }
-
- return null;
- },
- hasNodeAttributeWithValue: function (node, attributeName)
- {
- var value = this.getAttributeValueFromNode(node, attributeName);
-
- return !!value;
- },
- hasNodeAttribute: function (node, attributeName)
- {
- if (node && node.hasAttribute) {
- return node.hasAttribute(attributeName);
- }
-
- if (node && node.attributes) {
- var typeOfAttr = (typeof node.attributes[attributeName]);
- return 'undefined' !== typeOfAttr;
- }
-
- return false;
- },
- hasNodeCssClass: function (node, klassName)
- {
- if (node && klassName && node.className) {
- var classes = typeof node.className === "string" ? node.className.split(' ') : [];
- if (-1 !== indexOfArray(classes, klassName)) {
- return true;
- }
- }
-
- return false;
- },
- findNodesHavingAttribute: function (nodeToSearch, attributeName, nodes)
- {
- if (!nodes) {
- nodes = [];
- }
-
- if (!nodeToSearch || !attributeName) {
- return nodes;
- }
-
- var children = getChildrenFromNode(nodeToSearch);
-
- if (!children || !children.length) {
- return nodes;
- }
-
- var index, child;
- for (index = 0; index < children.length; index++) {
- child = children[index];
- if (this.hasNodeAttribute(child, attributeName)) {
- nodes.push(child);
- }
-
- nodes = this.findNodesHavingAttribute(child, attributeName, nodes);
- }
-
- return nodes;
- },
- findFirstNodeHavingAttribute: function (node, attributeName)
- {
- if (!node || !attributeName) {
- return;
- }
-
- if (this.hasNodeAttribute(node, attributeName)) {
- return node;
- }
-
- var nodes = this.findNodesHavingAttribute(node, attributeName);
-
- if (nodes && nodes.length) {
- return nodes[0];
- }
- },
- findFirstNodeHavingAttributeWithValue: function (node, attributeName)
- {
- if (!node || !attributeName) {
- return;
- }
-
- if (this.hasNodeAttributeWithValue(node, attributeName)) {
- return node;
- }
-
- var nodes = this.findNodesHavingAttribute(node, attributeName);
-
- if (!nodes || !nodes.length) {
- return;
- }
-
- var index;
- for (index = 0; index < nodes.length; index++) {
- if (this.getAttributeValueFromNode(nodes[index], attributeName)) {
- return nodes[index];
- }
- }
- },
- findNodesHavingCssClass: function (nodeToSearch, className, nodes)
- {
- if (!nodes) {
- nodes = [];
- }
-
- if (!nodeToSearch || !className) {
- return nodes;
- }
-
- if (nodeToSearch.getElementsByClassName) {
- var foundNodes = nodeToSearch.getElementsByClassName(className);
- return this.htmlCollectionToArray(foundNodes);
- }
-
- var children = getChildrenFromNode(nodeToSearch);
-
- if (!children || !children.length) {
- return [];
- }
-
- var index, child;
- for (index = 0; index < children.length; index++) {
- child = children[index];
- if (this.hasNodeCssClass(child, className)) {
- nodes.push(child);
- }
-
- nodes = this.findNodesHavingCssClass(child, className, nodes);
- }
-
- return nodes;
- },
- findFirstNodeHavingClass: function (node, className)
- {
- if (!node || !className) {
- return;
- }
-
- if (this.hasNodeCssClass(node, className)) {
- return node;
- }
-
- var nodes = this.findNodesHavingCssClass(node, className);
-
- if (nodes && nodes.length) {
- return nodes[0];
- }
- },
- isLinkElement: function (node)
- {
- if (!node) {
- return false;
- }
-
- var elementName = String(node.nodeName).toLowerCase();
- var linkElementNames = ['a', 'area'];
- var pos = indexOfArray(linkElementNames, elementName);
-
- return pos !== -1;
- },
- setAnyAttribute: function (node, attrName, attrValue)
- {
- if (!node || !attrName) {
- return;
- }
-
- if (node.setAttribute) {
- node.setAttribute(attrName, attrValue);
- } else {
- node[attrName] = attrValue;
- }
- }
- };
-
- /************************************************************
- * Content Tracking
- ************************************************************/
-
- var content = {
- CONTENT_ATTR: 'data-track-content',
- CONTENT_CLASS: 'piwikTrackContent',
- CONTENT_NAME_ATTR: 'data-content-name',
- CONTENT_PIECE_ATTR: 'data-content-piece',
- CONTENT_PIECE_CLASS: 'piwikContentPiece',
- CONTENT_TARGET_ATTR: 'data-content-target',
- CONTENT_TARGET_CLASS: 'piwikContentTarget',
- CONTENT_IGNOREINTERACTION_ATTR: 'data-content-ignoreinteraction',
- CONTENT_IGNOREINTERACTION_CLASS: 'piwikContentIgnoreInteraction',
- location: undefined,
-
- findContentNodes: function ()
- {
-
- var cssSelector = '.' + this.CONTENT_CLASS;
- var attrSelector = '[' + this.CONTENT_ATTR + ']';
- var contentNodes = query.findMultiple([cssSelector, attrSelector]);
-
- return contentNodes;
- },
- findContentNodesWithinNode: function (node)
- {
- if (!node) {
- return [];
- }
-
- // NOTE: we do not use query.findMultiple here as querySelectorAll would most likely not deliver the result we want
-
- var nodes1 = query.findNodesHavingCssClass(node, this.CONTENT_CLASS);
- var nodes2 = query.findNodesHavingAttribute(node, this.CONTENT_ATTR);
-
- if (nodes2 && nodes2.length) {
- var index;
- for (index = 0; index < nodes2.length; index++) {
- nodes1.push(nodes2[index]);
- }
- }
-
- if (query.hasNodeAttribute(node, this.CONTENT_ATTR)) {
- nodes1.push(node);
- } else if (query.hasNodeCssClass(node, this.CONTENT_CLASS)) {
- nodes1.push(node);
- }
-
- nodes1 = query.makeNodesUnique(nodes1);
-
- return nodes1;
- },
- findParentContentNode: function (anyNode)
- {
- if (!anyNode) {
- return;
- }
-
- var node = anyNode;
- var counter = 0;
-
- while (node && node !== documentAlias && node.parentNode) {
- if (query.hasNodeAttribute(node, this.CONTENT_ATTR)) {
- return node;
- }
- if (query.hasNodeCssClass(node, this.CONTENT_CLASS)) {
- return node;
- }
-
- node = node.parentNode;
-
- if (counter > 1000) {
- break; // prevent loop, should not happen anyway but better we do this
- }
- counter++;
- }
- },
- findPieceNode: function (node)
- {
- var contentPiece;
-
- contentPiece = query.findFirstNodeHavingAttribute(node, this.CONTENT_PIECE_ATTR);
-
- if (!contentPiece) {
- contentPiece = query.findFirstNodeHavingClass(node, this.CONTENT_PIECE_CLASS);
- }
-
- if (contentPiece) {
- return contentPiece;
- }
-
- return node;
- },
- findTargetNodeNoDefault: function (node)
- {
- if (!node) {
- return;
- }
-
- var target = query.findFirstNodeHavingAttributeWithValue(node, this.CONTENT_TARGET_ATTR);
- if (target) {
- return target;
- }
-
- target = query.findFirstNodeHavingAttribute(node, this.CONTENT_TARGET_ATTR);
- if (target) {
- return target;
- }
-
- target = query.findFirstNodeHavingClass(node, this.CONTENT_TARGET_CLASS);
- if (target) {
- return target;
- }
- },
- findTargetNode: function (node)
- {
- var target = this.findTargetNodeNoDefault(node);
- if (target) {
- return target;
- }
-
- return node;
- },
- findContentName: function (node)
- {
- if (!node) {
- return;
- }
-
- var nameNode = query.findFirstNodeHavingAttributeWithValue(node, this.CONTENT_NAME_ATTR);
-
- if (nameNode) {
- return query.getAttributeValueFromNode(nameNode, this.CONTENT_NAME_ATTR);
- }
-
- var contentPiece = this.findContentPiece(node);
- if (contentPiece) {
- return this.removeDomainIfIsInLink(contentPiece);
- }
-
- if (query.hasNodeAttributeWithValue(node, 'title')) {
- return query.getAttributeValueFromNode(node, 'title');
- }
-
- var clickUrlNode = this.findPieceNode(node);
-
- if (query.hasNodeAttributeWithValue(clickUrlNode, 'title')) {
- return query.getAttributeValueFromNode(clickUrlNode, 'title');
- }
-
- var targetNode = this.findTargetNode(node);
-
- if (query.hasNodeAttributeWithValue(targetNode, 'title')) {
- return query.getAttributeValueFromNode(targetNode, 'title');
- }
- },
- findContentPiece: function (node)
- {
- if (!node) {
- return;
- }
-
- var nameNode = query.findFirstNodeHavingAttributeWithValue(node, this.CONTENT_PIECE_ATTR);
-
- if (nameNode) {
- return query.getAttributeValueFromNode(nameNode, this.CONTENT_PIECE_ATTR);
- }
-
- var contentNode = this.findPieceNode(node);
-
- var media = this.findMediaUrlInNode(contentNode);
- if (media) {
- return this.toAbsoluteUrl(media);
- }
- },
- findContentTarget: function (node)
- {
- if (!node) {
- return;
- }
-
- var targetNode = this.findTargetNode(node);
-
- if (query.hasNodeAttributeWithValue(targetNode, this.CONTENT_TARGET_ATTR)) {
- return query.getAttributeValueFromNode(targetNode, this.CONTENT_TARGET_ATTR);
- }
-
- var href;
- if (query.hasNodeAttributeWithValue(targetNode, 'href')) {
- href = query.getAttributeValueFromNode(targetNode, 'href');
- return this.toAbsoluteUrl(href);
- }
-
- var contentNode = this.findPieceNode(node);
-
- if (query.hasNodeAttributeWithValue(contentNode, 'href')) {
- href = query.getAttributeValueFromNode(contentNode, 'href');
- return this.toAbsoluteUrl(href);
- }
- },
- isSameDomain: function (url)
- {
- if (!url || !url.indexOf) {
- return false;
- }
-
- if (0 === url.indexOf(this.getLocation().origin)) {
- return true;
- }
-
- var posHost = url.indexOf(this.getLocation().host);
- if (8 >= posHost && 0 <= posHost) {
- return true;
- }
-
- return false;
- },
- removeDomainIfIsInLink: function (text)
- {
- // we will only remove if domain === location.origin meaning is not an outlink
- var regexContainsProtocol = '^https?:\/\/[^\/]+';
- var regexReplaceDomain = '^.*\/\/[^\/]+';
-
- if (text &&
- text.search &&
- -1 !== text.search(new RegExp(regexContainsProtocol))
- && this.isSameDomain(text)) {
-
- text = text.replace(new RegExp(regexReplaceDomain), '');
- if (!text) {
- text = '/';
- }
- }
-
- return text;
- },
- findMediaUrlInNode: function (node)
- {
- if (!node) {
- return;
- }
-
- var mediaElements = ['img', 'embed', 'video', 'audio'];
- var elementName = node.nodeName.toLowerCase();
-
- if (-1 !== indexOfArray(mediaElements, elementName) &&
- query.findFirstNodeHavingAttributeWithValue(node, 'src')) {
-
- var sourceNode = query.findFirstNodeHavingAttributeWithValue(node, 'src');
-
- return query.getAttributeValueFromNode(sourceNode, 'src');
- }
-
- if (elementName === 'object' &&
- query.hasNodeAttributeWithValue(node, 'data')) {
-
- return query.getAttributeValueFromNode(node, 'data');
- }
-
- if (elementName === 'object') {
- var params = query.findNodesByTagName(node, 'param');
- if (params && params.length) {
- var index;
- for (index = 0; index < params.length; index++) {
- if ('movie' === query.getAttributeValueFromNode(params[index], 'name') &&
- query.hasNodeAttributeWithValue(params[index], 'value')) {
-
- return query.getAttributeValueFromNode(params[index], 'value');
- }
- }
- }
-
- var embed = query.findNodesByTagName(node, 'embed');
- if (embed && embed.length) {
- return this.findMediaUrlInNode(embed[0]);
- }
- }
- },
- trim: function (text)
- {
- if (text && String(text) === text) {
- return text.replace(/^\s+|\s+$/g, '');
- }
-
- return text;
- },
- isOrWasNodeInViewport: function (node)
- {
- if (!node || !node.getBoundingClientRect || node.nodeType !== 1) {
- return true;
- }
-
- var rect = node.getBoundingClientRect();
- var html = documentAlias.documentElement || {};
-
- var wasVisible = rect.top < 0;
- if (wasVisible && node.offsetTop) {
- wasVisible = (node.offsetTop + rect.height) > 0;
- }
-
- var docWidth = html.clientWidth; // The clientWidth attribute returns the viewport width excluding the size of a rendered scroll bar
-
- if (windowAlias.innerWidth && docWidth > windowAlias.innerWidth) {
- docWidth = windowAlias.innerWidth; // The innerWidth attribute must return the viewport width including the size of a rendered scroll bar
- }
-
- var docHeight = html.clientHeight; // The clientWidth attribute returns the viewport width excluding the size of a rendered scroll bar
-
- if (windowAlias.innerHeight && docHeight > windowAlias.innerHeight) {
- docHeight = windowAlias.innerHeight; // The innerWidth attribute must return the viewport width including the size of a rendered scroll bar
- }
-
- return (
- (rect.bottom > 0 || wasVisible) &&
- rect.right > 0 &&
- rect.left < docWidth &&
- ((rect.top < docHeight) || wasVisible) // rect.top < 0 we assume user has seen all the ones that are above the current viewport
- );
- },
- isNodeVisible: function (node)
- {
- var isItVisible = isVisible(node);
- var isInViewport = this.isOrWasNodeInViewport(node);
- return isItVisible && isInViewport;
- },
- buildInteractionRequestParams: function (interaction, name, piece, target)
- {
- var params = '';
-
- if (interaction) {
- params += 'c_i='+ encodeWrapper(interaction);
- }
- if (name) {
- if (params) {
- params += '&';
- }
- params += 'c_n='+ encodeWrapper(name);
- }
- if (piece) {
- if (params) {
- params += '&';
- }
- params += 'c_p='+ encodeWrapper(piece);
- }
- if (target) {
- if (params) {
- params += '&';
- }
- params += 'c_t='+ encodeWrapper(target);
- }
-
- return params;
- },
- buildImpressionRequestParams: function (name, piece, target)
- {
- var params = 'c_n=' + encodeWrapper(name) +
- '&c_p=' + encodeWrapper(piece);
-
- if (target) {
- params += '&c_t=' + encodeWrapper(target);
- }
-
- return params;
- },
- buildContentBlock: function (node)
- {
- if (!node) {
- return;
- }
-
- var name = this.findContentName(node);
- var piece = this.findContentPiece(node);
- var target = this.findContentTarget(node);
-
- name = this.trim(name);
- piece = this.trim(piece);
- target = this.trim(target);
-
- return {
- name: name || 'Unknown',
- piece: piece || 'Unknown',
- target: target || ''
- };
- },
- collectContent: function (contentNodes)
- {
- if (!contentNodes || !contentNodes.length) {
- return [];
- }
-
- var contents = [];
-
- var index, contentBlock;
- for (index = 0; index < contentNodes.length; index++) {
- contentBlock = this.buildContentBlock(contentNodes[index]);
- if (isDefined(contentBlock)) {
- contents.push(contentBlock);
- }
- }
-
- return contents;
- },
- setLocation: function (location)
- {
- this.location = location;
- },
- getLocation: function ()
- {
- var locationAlias = this.location || windowAlias.location;
-
- if (!locationAlias.origin) {
- locationAlias.origin = locationAlias.protocol + "//" + locationAlias.hostname + (locationAlias.port ? ':' + locationAlias.port: '');
- }
-
- return locationAlias;
- },
- toAbsoluteUrl: function (url)
- {
- if ((!url || String(url) !== url) && url !== '') {
- // we only handle strings
- return url;
- }
-
- if ('' === url) {
- return this.getLocation().href;
- }
-
- // Eg //example.com/test.jpg
- if (url.search(/^\/\//) !== -1) {
- return this.getLocation().protocol + url;
- }
-
- // Eg http://example.com/test.jpg
- if (url.search(/:\/\//) !== -1) {
- return url;
- }
-
- // Eg #test.jpg
- if (0 === url.indexOf('#')) {
- return this.getLocation().origin + this.getLocation().pathname + url;
- }
-
- // Eg ?x=5
- if (0 === url.indexOf('?')) {
- return this.getLocation().origin + this.getLocation().pathname + url;
- }
-
- // Eg mailto:x@y.z tel:012345, ... market:... sms:..., javasript:... ecmascript: ... and many more
- if (0 === url.search('^[a-zA-Z]{2,11}:')) {
- return url;
- }
-
- // Eg /test.jpg
- if (url.search(/^\//) !== -1) {
- return this.getLocation().origin + url;
- }
-
- // Eg test.jpg
- var regexMatchDir = '(.*\/)';
- var base = this.getLocation().origin + this.getLocation().pathname.match(new RegExp(regexMatchDir))[0];
- return base + url;
- },
- isUrlToCurrentDomain: function (url) {
-
- var absoluteUrl = this.toAbsoluteUrl(url);
-
- if (!absoluteUrl) {
- return false;
- }
-
- var origin = this.getLocation().origin;
- if (origin === absoluteUrl) {
- return true;
- }
-
- if (0 === String(absoluteUrl).indexOf(origin)) {
- if (':' === String(absoluteUrl).substr(origin.length, 1)) {
- return false; // url has port whereas origin has not => different URL
- }
-
- return true;
- }
-
- return false;
- },
- setHrefAttribute: function (node, url)
- {
- if (!node || !url) {
- return;
- }
-
- query.setAnyAttribute(node, 'href', url);
- },
- shouldIgnoreInteraction: function (targetNode)
- {
- var hasAttr = query.hasNodeAttribute(targetNode, this.CONTENT_IGNOREINTERACTION_ATTR);
- var hasClass = query.hasNodeCssClass(targetNode, this.CONTENT_IGNOREINTERACTION_CLASS);
- return hasAttr || hasClass;
- }
- };
-
- /************************************************************
- * Page Overlay
- ************************************************************/
-
- function getPiwikUrlForOverlay(trackerUrl, apiUrl) {
- if (apiUrl) {
- return apiUrl;
- }
-
- // if eg http://www.example.com/js/tracker.php?version=232323 => http://www.example.com/js/tracker.php
- if (stringContains(trackerUrl, '?')) {
- var posQuery = trackerUrl.indexOf('?');
- trackerUrl = trackerUrl.slice(0, posQuery);
- }
-
- if (stringEndsWith(trackerUrl, 'piwik.php')) {
- // if eg without domain or path "piwik.php" => ''
- trackerUrl = removeCharactersFromEndOfString(trackerUrl, 'piwik.php'.length);
- } else if (stringEndsWith(trackerUrl, '.php')) {
- // if eg http://www.example.com/js/piwik.php => http://www.example.com/js/
- // or if eg http://www.example.com/tracker.php => http://www.example.com/
- var lastSlash = trackerUrl.lastIndexOf('/');
- var includeLastSlash = 1;
- trackerUrl = trackerUrl.slice(0, lastSlash + includeLastSlash);
- }
-
- // if eg http://www.example.com/js/ => http://www.example.com/ (when not minified Piwik JS loaded)
- if (stringEndsWith(trackerUrl, '/js/')) {
- trackerUrl = removeCharactersFromEndOfString(trackerUrl, 'js/'.length);
- }
-
- // http://www.example.com/
- return trackerUrl;
- }
-
- /*
- * Check whether this is a page overlay session
- *
- * @return boolean
- *
- * {@internal side-effect: modifies window.name }}
- */
- function isOverlaySession(configTrackerSiteId) {
- var windowName = 'Piwik_Overlay';
-
- // check whether we were redirected from the piwik overlay plugin
- var referrerRegExp = new RegExp('index\\.php\\?module=Overlay&action=startOverlaySession'
- + '&idSite=([0-9]+)&period=([^&]+)&date=([^&]+)(&segment=.*)?$');
-
- var match = referrerRegExp.exec(documentAlias.referrer);
-
- if (match) {
- // check idsite
- var idsite = match[1];
-
- if (idsite !== String(configTrackerSiteId)) {
- return false;
- }
-
- // store overlay session info in window name
- var period = match[2],
- date = match[3],
- segment = match[4];
-
- if (!segment) {
- segment = '';
- } else if (segment.indexOf('&segment=') === 0) {
- segment = segment.substr('&segment='.length);
- }
-
- windowAlias.name = windowName + '###' + period + '###' + date + '###' + segment;
- }
-
- // retrieve and check data from window name
- var windowNameParts = windowAlias.name.split('###');
-
- return windowNameParts.length === 4 && windowNameParts[0] === windowName;
- }
-
- /*
- * Inject the script needed for page overlay
- */
- function injectOverlayScripts(configTrackerUrl, configApiUrl, configTrackerSiteId) {
- var windowNameParts = windowAlias.name.split('###'),
- period = windowNameParts[1],
- date = windowNameParts[2],
- segment = windowNameParts[3],
- piwikUrl = getPiwikUrlForOverlay(configTrackerUrl, configApiUrl);
-
- loadScript(
- piwikUrl + 'plugins/Overlay/client/client.js?v=1',
- function () {
- Piwik_Overlay_Client.initialize(piwikUrl, configTrackerSiteId, period, date, segment);
- }
- );
- }
-
- function isInsideAnIframe () {
- 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 (e2) {
- return true;
- }
- }
-
- /************************************************************
- * End Page Overlay
- ************************************************************/
-
- /*
- * Piwik Tracker class
- *
- * trackerUrl and trackerSiteId are optional arguments to the constructor
- *
- * See: Tracker.setTrackerUrl() and Tracker.setSiteId()
- */
- function Tracker(trackerUrl, siteId) {
-
- /************************************************************
- * Private members
- ************************************************************/
-
- var
-/*<DEBUG>*/
- /*
- * registered test hooks
- */
- registeredHooks = {},
-/*</DEBUG>*/
-
- // Current URL and Referrer URL
- locationArray = urlFixup(documentAlias.domain, windowAlias.location.href, getReferrer()),
- domainAlias = domainFixup(locationArray[0]),
- locationHrefAlias = safeDecodeWrapper(locationArray[1]),
- configReferrerUrl = safeDecodeWrapper(locationArray[2]),
-
- enableJSErrorTracking = false,
-
- defaultRequestMethod = 'GET',
-
- // Request method (GET or POST)
- configRequestMethod = defaultRequestMethod,
-
- defaultRequestContentType = 'application/x-www-form-urlencoded; charset=UTF-8',
-
- // Request Content-Type header value; applicable when POST request method is used for submitting tracking events
- configRequestContentType = defaultRequestContentType,
-
- // Tracker URL
- configTrackerUrl = trackerUrl || '',
-
- // API URL (only set if it differs from the Tracker URL)
- configApiUrl = '',
-
- // This string is appended to the Tracker URL Request (eg. to send data that is not handled by the existing setters/getters)
- configAppendToTrackingUrl = '',
-
- // Site ID
- configTrackerSiteId = siteId || '',
-
- // User ID
- configUserId = '',
-
- // Visitor UUID
- visitorUUID = '',
-
- // Document URL
- configCustomUrl,
-
- // Document 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'],
-
- // Hosts or alias(es) to not treat as outlinks
- configHostsAlias = [domainAlias],
-
- // HTML anchor element classes to not track
- configIgnoreClasses = [],
-
- // HTML anchor element classes to treat as downloads
- configDownloadClasses = [],
-
- // HTML anchor element classes to treat at outlinks
- configLinkClasses = [],
-
- // Maximum delay to wait for web bug image to be fetched (in milliseconds)
- configTrackerPause = 500,
-
- // Minimum visit time after initial page view (in milliseconds)
- configMinimumVisitTime,
-
- // Recurring heart beat after initial ping (in milliseconds)
- configHeartBeatDelay,
-
- // alias to circumvent circular function dependency (JSLint requires this)
- heartBeatPingIfActivityAlias,
-
- // Disallow hash tags in URL
- configDiscardHashTag,
-
- // Custom data
- configCustomData,
-
- // Campaign names
- configCampaignNameParameters = [ 'pk_campaign', 'piwik_campaign', 'utm_campaign', 'utm_source', 'utm_medium' ],
-
- // Campaign keywords
- configCampaignKeywordParameters = [ 'pk_kwd', 'piwik_kwd', 'utm_term' ],
-
- // First-party cookie name prefix
- configCookieNamePrefix = '_pk_',
-
- // First-party cookie domain
- // User agent defaults to origin hostname
- configCookieDomain,
-
- // First-party cookie path
- // Default is user agent defined.
- configCookiePath,
-
- // First-party cookies are disabled
- configCookiesDisabled = false,
-
- // Do Not Track
- configDoNotTrack,
-
- // Count sites which are pre-rendered
- configCountPreRendered,
-
- // Do we attribute the conversion to the first referrer or the most recent referrer?
- configConversionAttributionFirstReferrer,
-
- // Life of the visitor cookie (in milliseconds)
- configVisitorCookieTimeout = 33955200000, // 13 months (365 days + 28days)
-
- // Life of the session cookie (in milliseconds)
- configSessionCookieTimeout = 1800000, // 30 minutes
-
- // Life of the referral cookie (in milliseconds)
- configReferralCookieTimeout = 15768000000, // 6 months
-
- // Is performance tracking enabled
- configPerformanceTrackingEnabled = true,
-
- // Generation time set from the server
- configPerformanceGenerationTime = 0,
-
- // Whether Custom Variables scope "visit" should be stored in a cookie during the time of the visit
- configStoreCustomVariablesInCookie = false,
-
- // Custom Variables read from cookie, scope "visit"
- customVariables = false,
-
- configCustomRequestContentProcessing,
-
- // Custom Variables, scope "page"
- customVariablesPage = {},
-
- // Custom Variables, scope "event"
- customVariablesEvent = {},
-
- // Custom Dimensions (can be any scope)
- customDimensions = {},
-
- // Custom Variables names and values are each truncated before being sent in the request or recorded in the cookie
- customVariableMaximumLength = 200,
-
- // Ecommerce items
- ecommerceItems = {},
-
- // Browser features via client-side data collection
- browserFeatures = {},
-
- // Keeps track of previously tracked content impressions
- trackedContentImpressions = [],
- isTrackOnlyVisibleContentEnabled = false,
-
- // Guard to prevent empty visits see #6415. If there is a new visitor and the first 2 (or 3 or 4)
- // tracking requests are at nearly same time (eg trackPageView and trackContentImpression) 2 or more
- // visits will be created
- timeNextTrackingRequestCanBeExecutedImmediately = false,
-
- // Guard against installing the link tracker more than once per Tracker instance
- linkTrackingInstalled = false,
- linkTrackingEnabled = false,
-
- // Guard against installing the activity tracker more than once per Tracker instance
- heartBeatSetUp = false,
-
- // bool used to detect whether this browser window had focus at least once. So far we cannot really
- // detect this 100% correct for an iframe so whenever Piwik is loaded inside an iframe we presume
- // the window had focus at least once.
- hadWindowFocusAtLeastOnce = isInsideAnIframe(),
-
- // Timestamp of last tracker request sent to Piwik
- lastTrackerRequestTime = null,
-
- // Handle to the current heart beat timeout
- heartBeatTimeout,
-
- // Internal state of the pseudo click handler
- lastButton,
- lastTarget,
-
- // Hash function
- hash = sha1,
-
- // Domain hash value
- domainHash,
-
- configIdPageView;
-
- // Document title
- try {
- configTitle = documentAlias.title;
- } catch(e) {
- configTitle = '';
- }
-
- /*
- * Set cookie value
- */
- function setCookie(cookieName, value, msToExpire, path, domain, secure) {
- if (configCookiesDisabled) {
- return;
- }
-
- var expiryDate;
-
- // relative time to expire in milliseconds
- if (msToExpire) {
- expiryDate = new Date();
- expiryDate.setTime(expiryDate.getTime() + msToExpire);
- }
-
- documentAlias.cookie = cookieName + '=' + encodeWrapper(value) +
- (msToExpire ? ';expires=' + expiryDate.toGMTString() : '') +
- ';path=' + (path || '/') +
- (domain ? ';domain=' + domain : '') +
- (secure ? ';secure' : '');
- }
-
- /*
- * Get cookie value
- */
- function getCookie(cookieName) {
- if (configCookiesDisabled) {
- return 0;
- }
-
- var cookiePattern = new RegExp('(^|;)[ ]*' + cookieName + '=([^;]*)'),
- cookieMatch = cookiePattern.exec(documentAlias.cookie);
-
- return cookieMatch ? decodeWrapper(cookieMatch[2]) : 0;
- }
-
- /*
- * Removes hash tag from the URL
- *
- * URLs are purified before being recorded in the cookie,
- * or before being sent as GET parameters
- */
- function purify(url) {
- var targetPattern;
-
- if (configDiscardHashTag) {
- targetPattern = new RegExp('#.*');
-
- return url.replace(targetPattern, '');
- }
-
- return url;
- }
-
- /*
- * Resolve relative reference
- *
- * Note: not as described in rfc3986 section 5.2
- */
- function resolveRelativeReference(baseUrl, url) {
- var protocol = getProtocolScheme(url),
- i;
-
- if (protocol) {
- return url;
- }
-
- if (url.slice(0, 1) === '/') {
- return getProtocolScheme(baseUrl) + '://' + getHostName(baseUrl) + url;
- }
-
- baseUrl = purify(baseUrl);
-
- i = baseUrl.indexOf('?');
- if (i >= 0) {
- baseUrl = baseUrl.slice(0, i);
- }
-
- i = baseUrl.lastIndexOf('/');
- if (i !== baseUrl.length - 1) {
- baseUrl = baseUrl.slice(0, i + 1);
- }
-
- return baseUrl + url;
- }
-
- function isSameHost (hostName, alias) {
- var offset;
-
- hostName = String(hostName).toLowerCase();
- alias = String(alias).toLowerCase();
-
- if (hostName === alias) {
- return true;
- }
-
- if (alias.slice(0, 1) === '.') {
- if (hostName === alias.slice(1)) {
- return true;
- }
-
- offset = hostName.length - alias.length;
-
- if ((offset > 0) && (hostName.slice(offset) === alias)) {
- return true;
- }
- }
-
- return false;
- }
-
- /*
- * Extract pathname from URL. element.pathname is actually supported by pretty much all browsers including
- * IE6 apart from some rare very old ones
- */
- 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;
- }
-
- return '';
- }
-
- function isSitePath (path, pathAlias)
- {
- if(!stringStartsWith(pathAlias, '/')) {
- pathAlias = '/' + pathAlias;
- }
-
- if(!stringStartsWith(path, '/')) {
- path = '/' + path;
- }
-
- var matchesAnyPath = (pathAlias === '/' || pathAlias === '/*');
-
- if (matchesAnyPath) {
- return true;
- }
-
- if (path === pathAlias) {
- return true;
- }
-
- pathAlias = String(pathAlias).toLowerCase();
- path = String(path).toLowerCase();
-
- // wildcard path support
- if(stringEndsWith(pathAlias, '*')) {
- // remove the final '*' before comparing
- pathAlias = pathAlias.slice(0, -1);
-
- // Note: this is almost duplicated from just few lines above
- matchesAnyPath = (!pathAlias || pathAlias === '/');
-
- if (matchesAnyPath) {
- return true;
- }
-
- if (path === pathAlias) {
- return true;
- }
-
- // wildcard match
- return path.indexOf(pathAlias) === 0;
- }
-
- // we need to append slashes so /foobarbaz won't match a site /foobar
- if (!stringEndsWith(path, '/')) {
- path += '/';
- }
-
- if (!stringEndsWith(pathAlias, '/')) {
- pathAlias += '/';
- }
-
- return path.indexOf(pathAlias) === 0;
- }
-
- /**
- * Whether the specified domain name and path belong to any of the alias domains (eg. set via setDomains).
- *
- * Note: this function is used to determine whether a click on a URL will be considered an "Outlink".
- *
- * @param host
- * @param path
- * @returns {boolean}
- */
- function isSiteHostPath(host, path)
- {
- var i,
- alias,
- configAlias,
- aliasHost,
- aliasPath;
-
- for (i = 0; i < configHostsAlias.length; i++) {
- aliasHost = domainFixup(configHostsAlias[i]);
- aliasPath = getPathName(configHostsAlias[i]);
-
- if (isSameHost(host, aliasHost) && isSitePath(path, aliasPath)) {
- return true;
- }
- }
-
- return false;
- }
-
- /*
- * Is the host local? (i.e., not an outlink)
- */
- function isSiteHostName(hostName) {
-
- var i,
- alias,
- offset;
-
- for (i = 0; i < configHostsAlias.length; i++) {
- alias = domainFixup(configHostsAlias[i].toLowerCase());
-
- if (hostName === alias) {
- return true;
- }
-
- if (alias.slice(0, 1) === '.') {
- if (hostName === alias.slice(1)) {
- return true;
- }
-
- offset = hostName.length - alias.length;
-
- if ((offset > 0) && (hostName.slice(offset) === alias)) {
- return true;
- }
- }
- }
-
- return false;
- }
-
- /*
- * Send image request to Piwik server using GET.
- * The infamous web bug (or beacon) is a transparent, single pixel (1x1) image
- */
- function getImage(request, callback) {
- var image = new Image(1, 1);
-
- image.onload = function () {
- 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;
- }
-
- /*
- * POST request to Piwik server using XMLHttpRequest.
- */
- function sendXmlHttpRequest(request, callback, fallbackToGet) {
- if (!isDefined(fallbackToGet) || null === fallbackToGet) {
- fallbackToGet = true;
- }
-
- try {
- // we use the progid Microsoft.XMLHTTP because
- // IE5.5 included MSXML 2.5; the progid MSXML2.XMLHTTP
- // is pinned to MSXML2.XMLHTTP.3.0
- var xhr = windowAlias.XMLHttpRequest
- ? new windowAlias.XMLHttpRequest()
- : windowAlias.ActiveXObject
- ? new ActiveXObject('Microsoft.XMLHTTP')
- : null;
-
- xhr.open('POST', configTrackerUrl, true);
-
- // fallback on error
- xhr.onreadystatechange = function () {
- if (this.readyState === 4 && !(this.status >= 200 && this.status < 300) && fallbackToGet) {
- getImage(request, callback);
- } else {
- if (this.readyState === 4 && (typeof callback === 'function')) { callback(); }
- }
- };
-
- xhr.setRequestHeader('Content-Type', configRequestContentType);
-
- xhr.send(request);
- } catch (e) {
- if (fallbackToGet) {
- // fallback
- getImage(request, callback);
- }
- }
- }
-
- function setExpireDateTime(delay) {
-
- var now = new Date();
- var time = now.getTime() + delay;
-
- if (!expireDateTime || time > expireDateTime) {
- expireDateTime = time;
- }
- }
-
- /*
- * Sets up the heart beat timeout.
- */
- function heartBeatUp(delay) {
- if (heartBeatTimeout
- || !configHeartBeatDelay
- ) {
- return;
- }
-
- heartBeatTimeout = setTimeout(function heartBeat() {
- heartBeatTimeout = null;
-
- if (!hadWindowFocusAtLeastOnce) {
- // if browser does not support .hasFocus (eg IE5), we assume that the window has focus.
- hadWindowFocusAtLeastOnce = (!documentAlias.hasFocus || documentAlias.hasFocus());
- }
-
- if (!hadWindowFocusAtLeastOnce) {
- // only send a ping if the tab actually had focus at least once. For example do not send a ping
- // if window was opened via "right click => open in new window" and never had focus see #9504
- heartBeatUp(configHeartBeatDelay);
- return;
- }
-
- if (heartBeatPingIfActivityAlias()) {
- return;
- }
-
- var now = new Date(),
- heartBeatDelay = configHeartBeatDelay - (now.getTime() - lastTrackerRequestTime);
- // sanity check
- heartBeatDelay = Math.min(configHeartBeatDelay, heartBeatDelay);
- heartBeatUp(heartBeatDelay);
- }, delay || configHeartBeatDelay);
- }
-
- /*
- * Removes the heart beat timeout.
- */
- function heartBeatDown() {
- if (!heartBeatTimeout) {
- return;
- }
-
- clearTimeout(heartBeatTimeout);
- heartBeatTimeout = null;
- }
-
- function heartBeatOnFocus() {
- hadWindowFocusAtLeastOnce = true;
-
- // since it's possible for a user to come back to a tab after several hours or more, we try to send
- // a ping if the page is active. (after the ping is sent, the heart beat timeout will be set)
- if (heartBeatPingIfActivityAlias()) {
- return;
- }
-
- heartBeatUp();
- }
-
- function heartBeatOnBlur() {
- heartBeatDown();
- }
-
- /*
- * Setup event handlers and timeout for initial heart beat.
- */
- function setUpHeartBeat() {
- if (heartBeatSetUp
- || !configHeartBeatDelay
- ) {
- return;
- }
-
- heartBeatSetUp = true;
-
- addEventListener(windowAlias, 'focus', heartBeatOnFocus);
- addEventListener(windowAlias, 'blur', heartBeatOnBlur);
-
- heartBeatUp();
- }
-
- function makeSureThereIsAGapAfterFirstTrackingRequestToPreventMultipleVisitorCreation(callback)
- {
- var now = new Date();
- var timeNow = now.getTime();
-
- lastTrackerRequestTime = timeNow;
-
- if (timeNextTrackingRequestCanBeExecutedImmediately && timeNow < timeNextTrackingRequestCanBeExecutedImmediately) {
- // we are in the time frame shortly after the first request. we have to delay this request a bit to make sure
- // a visitor has been created meanwhile.
-
- var timeToWait = timeNextTrackingRequestCanBeExecutedImmediately - timeNow;
-
- setTimeout(callback, timeToWait);
- setExpireDateTime(timeToWait + 50); // set timeout is not necessarily executed at timeToWait so delay a bit more
- timeNextTrackingRequestCanBeExecutedImmediately += 50; // delay next tracking request by further 50ms to next execute them at same time
-
- return;
- }
-
- if (timeNextTrackingRequestCanBeExecutedImmediately === false) {
- // it is the first request, we want to execute this one directly and delay all the next one(s) within a delay.
- // All requests after this delay can be executed as usual again
- var delayInMs = 800;
- timeNextTrackingRequestCanBeExecutedImmediately = timeNow + delayInMs;
- }
-
- callback();
- }
-
- /*
- * Send request
- */
- function sendRequest(request, delay, callback) {
- if (!configDoNotTrack && request) {
- makeSureThereIsAGapAfterFirstTrackingRequestToPreventMultipleVisitorCreation(function () {
- if (configRequestMethod === 'POST') {
- sendXmlHttpRequest(request, callback);
- } else {
- getImage(request, callback);
- }
-
- setExpireDateTime(delay);
- });
- }
-
- if (!heartBeatSetUp) {
- setUpHeartBeat(); // setup window events too, but only once
- } else {
- heartBeatUp();
- }
- }
-
- function canSendBulkRequest(requests)
- {
- if (configDoNotTrack) {
- return false;
- }
-
- return (requests && requests.length);
- }
-
- /*
- * Send requests using bulk
- */
- function sendBulkRequest(requests, delay)
- {
- if (!canSendBulkRequest(requests)) {
- return;
- }
-
- var bulk = '{"requests":["?' + requests.join('","?') + '"]}';
-
- makeSureThereIsAGapAfterFirstTrackingRequestToPreventMultipleVisitorCreation(function () {
- sendXmlHttpRequest(bulk, null, false);
- setExpireDateTime(delay);
- });
- }
-
- /*
- * Get cookie name with prefix and domain hash
- */
- function getCookieName(baseName) {
- // NOTE: If the cookie name is changed, we must also update the PiwikTracker.php which
- // will attempt to discover first party cookies. eg. See the PHP Client method getVisitorId()
- return configCookieNamePrefix + baseName + '.' + configTrackerSiteId + '.' + domainHash;
- }
-
- /*
- * Does browser have cookies enabled (for this site)?
- */
- function hasCookies() {
- if (configCookiesDisabled) {
- return '0';
- }
-
- if (!isDefined(navigatorAlias.cookieEnabled)) {
- var testCookieName = getCookieName('testcookie');
- setCookie(testCookieName, '1');
-
- return getCookie(testCookieName) === '1' ? '1' : '0';
- }
-
- return navigatorAlias.cookieEnabled ? '1' : '0';
- }
-
- /*
- * Update domain hash
- */
- function updateDomainHash() {
- domainHash = hash((configCookieDomain || domainAlias) + (configCookiePath || '/')).slice(0, 4); // 4 hexits = 16 bits
- }
-
- /*
- * Inits the custom variables object
- */
- function getCustomVariablesFromCookie() {
- var cookieName = getCookieName('cvar'),
- cookie = getCookie(cookieName);
-
- if (cookie.length) {
- cookie = JSON2.parse(cookie);
-
- if (isObject(cookie)) {
- return cookie;
- }
- }
-
- return {};
- }
-
- /*
- * Lazy loads the custom variables from the cookie, only once during this page view
- */
- function loadCustomVariables() {
- if (customVariables === false) {
- customVariables = getCustomVariablesFromCookie();
- }
- }
-
- /*
- * Generate a pseudo-unique ID to fingerprint this user
- * 16 hexits = 64 bits
- * note: this isn't a RFC4122-compliant UUID
- */
- function generateRandomUuid() {
- return hash(
- (navigatorAlias.userAgent || '') +
- (navigatorAlias.platform || '') +
- JSON2.stringify(browserFeatures) +
- (new Date()).getTime() +
- Math.random()
- ).slice(0, 16);
- }
-
- /*
- * Load visitor ID cookie
- */
- function loadVisitorIdCookie() {
- var now = new Date(),
- nowTs = Math.round(now.getTime() / 1000),
- visitorIdCookieName = getCookieName('id'),
- id = getCookie(visitorIdCookieName),
- cookieValue,
- uuid;
-
- // Visitor ID cookie found
- if (id) {
- cookieValue = id.split('.');
-
- // returning visitor flag
- cookieValue.unshift('0');
-
- if(visitorUUID.length) {
- cookieValue[1] = visitorUUID;
- }
- return cookieValue;
- }
-
- if(visitorUUID.length) {
- uuid = visitorUUID;
- } else if ('0' === hasCookies()){
- uuid = '';
- } else {
- uuid = generateRandomUuid();
- }
-
- // No visitor ID cookie, let's create a new one
- cookieValue = [
- // new visitor
- '1',
-
- // uuid
- uuid,
-
- // creation timestamp - seconds since Unix epoch
- nowTs,
-
- // visitCount - 0 = no previous visit
- 0,
-
- // current visit timestamp
- nowTs,
-
- // last visit timestamp - blank = no previous visit
- '',
-
- // last ecommerce order timestamp
- ''
- ];
-
- return cookieValue;
- }
-
-
- /**
- * Loads the Visitor ID cookie and returns a named array of values
- */
- function getValuesFromVisitorIdCookie() {
- var cookieVisitorIdValue = loadVisitorIdCookie(),
- newVisitor = cookieVisitorIdValue[0],
- uuid = cookieVisitorIdValue[1],
- createTs = cookieVisitorIdValue[2],
- visitCount = cookieVisitorIdValue[3],
- currentVisitTs = cookieVisitorIdValue[4],
- lastVisitTs = cookieVisitorIdValue[5];
-
- // case migrating from pre-1.5 cookies
- if (!isDefined(cookieVisitorIdValue[6])) {
- cookieVisitorIdValue[6] = "";
- }
-
- var lastEcommerceOrderTs = cookieVisitorIdValue[6];
-
- return {
- newVisitor: newVisitor,
- uuid: uuid,
- createTs: createTs,
- visitCount: visitCount,
- currentVisitTs: currentVisitTs,
- lastVisitTs: lastVisitTs,
- lastEcommerceOrderTs: lastEcommerceOrderTs
- };
- }
-
-
- function getRemainingVisitorCookieTimeout() {
- var now = new Date(),
- nowTs = now.getTime(),
- cookieCreatedTs = getValuesFromVisitorIdCookie().createTs;
-
- var createTs = parseInt(cookieCreatedTs, 10);
- var originalTimeout = (createTs * 1000) + configVisitorCookieTimeout - nowTs;
- return originalTimeout;
- }
-
- /*
- * Sets the Visitor ID cookie
- */
- function setVisitorIdCookie(visitorIdCookieValues) {
-
- if(!configTrackerSiteId) {
- // when called before Site ID was set
- return;
- }
-
- var now = new Date(),
- nowTs = Math.round(now.getTime() / 1000);
-
- if(!isDefined(visitorIdCookieValues)) {
- visitorIdCookieValues = getValuesFromVisitorIdCookie();
- }
-
- var cookieValue = visitorIdCookieValues.uuid + '.' +
- visitorIdCookieValues.createTs + '.' +
- visitorIdCookieValues.visitCount + '.' +
- nowTs + '.' +
- visitorIdCookieValues.lastVisitTs + '.' +
- visitorIdCookieValues.lastEcommerceOrderTs;
-
- setCookie(getCookieName('id'), cookieValue, getRemainingVisitorCookieTimeout(), configCookiePath, configCookieDomain);
- }
-
- /*
- * Loads the referrer attribution information
- *
- * @returns array
- * 0: campaign name
- * 1: campaign keyword
- * 2: timestamp
- * 3: raw URL
- */
- function loadReferrerAttributionCookie() {
- // NOTE: if the format of the cookie changes,
- // we must also update JS tests, PHP tracker, System tests,
- // and notify other tracking clients (eg. Java) of the changes
- var cookie = getCookie(getCookieName('ref'));
-
- if (cookie.length) {
- try {
- cookie = JSON2.parse(cookie);
- if (isObject(cookie)) {
- return cookie;
- }
- } catch (ignore) {
- // Pre 1.3, this cookie was not JSON encoded
- }
- }
-
- return [
- '',
- '',
- 0,
- ''
- ];
- }
-
- function deleteCookie(cookieName, path, domain) {
- setCookie(cookieName, '', -86400, path, domain);
- }
-
- function isPossibleToSetCookieOnDomain(domainToTest)
- {
- var valueToSet = 'testvalue';
- setCookie('test', valueToSet, 10000, null, domainToTest);
-
- if (getCookie('test') === valueToSet) {
- deleteCookie('test', null, domainToTest);
-
- return true;
- }
-
- return false;
- }
-
- function deleteCookies() {
- var savedConfigCookiesDisabled = configCookiesDisabled;
-
- // Temporarily allow cookies just to delete the existing ones
- configCookiesDisabled = false;
-
- var cookiesToDelete = ['id', 'ses', 'cvar', 'ref'];
- var index, cookieName;
-
- for (index = 0; index < cookiesToDelete.length; index++) {
- cookieName = getCookieName(cookiesToDelete[index]);
- if (0 !== getCookie(cookieName)) {
- deleteCookie(cookieName, configCookiePath, configCookieDomain);
- }
- }
-
- configCookiesDisabled = savedConfigCookiesDisabled;
- }
-
- function setSiteId(siteId) {
- configTrackerSiteId = siteId;
- setVisitorIdCookie();
- }
-
- function sortObjectByKeys(value) {
- if (!value || !isObject(value)) {
- return;
- }
-
- // Object.keys(value) is not supported by all browsers, we get the keys manually
- var keys = [];
- var key;
-
- for (key in value) {
- if (Object.prototype.hasOwnProperty.call(value, key)) {
- keys.push(key);
- }
- }
-
- var normalized = {};
- keys.sort();
- var len = keys.length;
- var i;
-
- for (i = 0; i < len; i++) {
- normalized[keys[i]] = value[keys[i]];
- }
-
- return normalized;
- }
-
- /**
- * Creates the session cookie
- */
- function setSessionCookie() {
- setCookie(getCookieName('ses'), '*', configSessionCookieTimeout, configCookiePath, configCookieDomain);
- }
-
- function generateUniqueId() {
- var id = '';
- var chars = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
- var charLen = chars.length;
- var i;
-
- for (i = 0; i < 6; i++) {
- id += chars.charAt(Math.floor(Math.random() * charLen));
- }
-
- return id;
- }
-
- /**
- * Returns the URL to call piwik.php,
- * with the standard parameters (plugins, resolution, url, referrer, etc.).
- * Sends the pageview and browser settings with every request in case of race conditions.
- */
- function getRequest(request, customData, pluginMethod, currentEcommerceOrderTs) {
- var i,
- now = new Date(),
- nowTs = Math.round(now.getTime() / 1000),
- referralTs,
- referralUrl,
- referralUrlMaxLength = 1024,
- currentReferrerHostName,
- originalReferrerHostName,
- customVariablesCopy = customVariables,
- cookieSessionName = getCookieName('ses'),
- cookieReferrerName = getCookieName('ref'),
- cookieCustomVariablesName = getCookieName('cvar'),
- cookieSessionValue = getCookie(cookieSessionName),
- attributionCookie = loadReferrerAttributionCookie(),
- currentUrl = configCustomUrl || locationHrefAlias,
- campaignNameDetected,
- campaignKeywordDetected;
-
- if (configCookiesDisabled) {
- deleteCookies();
- }
-
- if (configDoNotTrack) {
- return '';
- }
-
- var cookieVisitorIdValues = getValuesFromVisitorIdCookie();
- if (!isDefined(currentEcommerceOrderTs)) {
- currentEcommerceOrderTs = "";
- }
-
- // send charset if document charset is not utf-8. sometimes encoding
- // of urls will be the same as this and not utf-8, which will cause problems
- // do not send charset if it is utf8 since it's assumed by default in Piwik
- var charSet = documentAlias.characterSet || documentAlias.charset;
-
- if (!charSet || charSet.toLowerCase() === 'utf-8') {
- charSet = null;
- }
-
- campaignNameDetected = attributionCookie[0];
- campaignKeywordDetected = attributionCookie[1];
- referralTs = attributionCookie[2];
- referralUrl = attributionCookie[3];
-
- if (!cookieSessionValue) {
- // cookie 'ses' was not found: we consider this the start of a 'session'
-
- // here we make sure that if 'ses' cookie is deleted few times within the visit
- // and so this code path is triggered many times for one visit,
- // we only increase visitCount once per Visit window (default 30min)
- var visitDuration = configSessionCookieTimeout / 1000;
- if (!cookieVisitorIdValues.lastVisitTs
- || (nowTs - cookieVisitorIdValues.lastVisitTs) > visitDuration) {
- cookieVisitorIdValues.visitCount++;
- cookieVisitorIdValues.lastVisitTs = cookieVisitorIdValues.currentVisitTs;
- }
-
-
- // Detect the campaign information from the current URL
- // Only if campaign wasn't previously set
- // Or if it was set but we must attribute to the most recent one
- // Note: we are working on the currentUrl before purify() since we can parse the campaign parameters in the hash tag
- if (!configConversionAttributionFirstReferrer
- || !campaignNameDetected.length) {
- for (i in configCampaignNameParameters) {
- if (Object.prototype.hasOwnProperty.call(configCampaignNameParameters, i)) {
- campaignNameDetected = getParameter(currentUrl, configCampaignNameParameters[i]);
-
- if (campaignNameDetected.length) {
- break;
- }
- }
- }
-
- for (i in configCampaignKeywordParameters) {
- if (Object.prototype.hasOwnProperty.call(configCampaignKeywordParameters, i)) {
- campaignKeywordDetected = getParameter(currentUrl, configCampaignKeywordParameters[i]);
-
- if (campaignKeywordDetected.length) {
- break;
- }
- }
- }
- }
-
- // Store the referrer URL and time in the cookie;
- // referral URL depends on the first or last referrer attribution
- currentReferrerHostName = getHostName(configReferrerUrl);
- originalReferrerHostName = referralUrl.length ? getHostName(referralUrl) : '';
-
- if (currentReferrerHostName.length && // there is a referrer
- !isSiteHostName(currentReferrerHostName) && // domain is not the current domain
- (!configConversionAttributionFirstReferrer || // attribute to last known referrer
- !originalReferrerHostName.length || // previously empty
- isSiteHostName(originalReferrerHostName))) { // previously set but in current domain
- referralUrl = configReferrerUrl;
- }
-
- // Set the referral cookie if we have either a Referrer URL, or detected a Campaign (or both)
- if (referralUrl.length
- || campaignNameDetected.length) {
- referralTs = nowTs;
- attributionCookie = [
- campaignNameDetected,
- campaignKeywordDetected,
- referralTs,
- purify(referralUrl.slice(0, referralUrlMaxLength))
- ];
-
- setCookie(cookieReferrerName, JSON2.stringify(attributionCookie), configReferralCookieTimeout, configCookiePath, configCookieDomain);
- }
- }
-
- // build out the rest of the request
- request += '&idsite=' + configTrackerSiteId +
- '&rec=1' +
- '&r=' + String(Math.random()).slice(2, 8) + // keep the string to a minimum
- '&h=' + now.getHours() + '&m=' + now.getMinutes() + '&s=' + now.getSeconds() +
- '&url=' + encodeWrapper(purify(currentUrl)) +
- (configReferrerUrl.length ? '&urlref=' + encodeWrapper(purify(configReferrerUrl)) : '') +
- ((configUserId && configUserId.length) ? '&uid=' + encodeWrapper(configUserId) : '') +
- '&_id=' + cookieVisitorIdValues.uuid + '&_idts=' + cookieVisitorIdValues.createTs + '&_idvc=' + cookieVisitorIdValues.visitCount +
- '&_idn=' + cookieVisitorIdValues.newVisitor + // currently unused
- (campaignNameDetected.length ? '&_rcn=' + encodeWrapper(campaignNameDetected) : '') +
- (campaignKeywordDetected.length ? '&_rck=' + encodeWrapper(campaignKeywordDetected) : '') +
- '&_refts=' + referralTs +
- '&_viewts=' + cookieVisitorIdValues.lastVisitTs +
- (String(cookieVisitorIdValues.lastEcommerceOrderTs).length ? '&_ects=' + cookieVisitorIdValues.lastEcommerceOrderTs : '') +
- (String(referralUrl).length ? '&_ref=' + encodeWrapper(purify(referralUrl.slice(0, referralUrlMaxLength))) : '') +
- (charSet ? '&cs=' + encodeWrapper(charSet) : '') +
- '&send_image=0';
-
- // browser features
- for (i in browserFeatures) {
- if (Object.prototype.hasOwnProperty.call(browserFeatures, i)) {
- request += '&' + i + '=' + browserFeatures[i];
- }
- }
-
- var customDimensionIdsAlreadyHandled = [];
- if (customData) {
- for (i in customData) {
- if (Object.prototype.hasOwnProperty.call(customData, i) && /^dimension\d+$/.test(i)) {
- var index = i.replace('dimension', '');
- customDimensionIdsAlreadyHandled.push(parseInt(index, 10));
- customDimensionIdsAlreadyHandled.push(String(index));
- request += '&' + i + '=' + customData[i];
- delete customData[i];
- }
- }
- }
-
- if (customData && isObjectEmpty(customData)) {
- customData = null;
- // we deleted all keys from custom data
- }
-
- // custom dimensions
- for (i in customDimensions) {
- if (Object.prototype.hasOwnProperty.call(customDimensions, i)) {
- var isNotSetYet = (-1 === indexOfArray(customDimensionIdsAlreadyHandled, i));
- if (isNotSetYet) {
- request += '&dimension' + i + '=' + customDimensions[i];
- }
- }
- }
-
- // custom data
- if (customData) {
- request += '&data=' + encodeWrapper(JSON2.stringify(customData));
- } else if (configCustomData) {
- request += '&data=' + encodeWrapper(JSON2.stringify(configCustomData));
- }
-
- // Custom Variables, scope "page"
- function appendCustomVariablesToRequest(customVariables, parameterName) {
- var customVariablesStringified = JSON2.stringify(customVariables);
- if (customVariablesStringified.length > 2) {
- return '&' + parameterName + '=' + encodeWrapper(customVariablesStringified);
- }
- return '';
- }
-
- var sortedCustomVarPage = sortObjectByKeys(customVariablesPage);
- var sortedCustomVarEvent = sortObjectByKeys(customVariablesEvent);
-
- request += appendCustomVariablesToRequest(sortedCustomVarPage, 'cvar');
- request += appendCustomVariablesToRequest(sortedCustomVarEvent, 'e_cvar');
-
- // Custom Variables, scope "visit"
- if (customVariables) {
- request += appendCustomVariablesToRequest(customVariables, '_cvar');
-
- // Don't save deleted custom variables in the cookie
- for (i in customVariablesCopy) {
- if (Object.prototype.hasOwnProperty.call(customVariablesCopy, i)) {
- if (customVariables[i][0] === '' || customVariables[i][1] === '') {
- delete customVariables[i];
- }
- }
- }
-
- if (configStoreCustomVariablesInCookie) {
- setCookie(cookieCustomVariablesName, JSON2.stringify(customVariables), configSessionCookieTimeout, configCookiePath, configCookieDomain);
- }
- }
-
- // performance tracking
- if (configPerformanceTrackingEnabled) {
- if (configPerformanceGenerationTime) {
- request += '&gt_ms=' + configPerformanceGenerationTime;
- } else if (performanceAlias && performanceAlias.timing
- && performanceAlias.timing.requestStart && performanceAlias.timing.responseEnd) {
- request += '&gt_ms=' + (performanceAlias.timing.responseEnd - performanceAlias.timing.requestStart);
- }
- }
-
- if (configIdPageView) {
- request += '&pv_id=' + configIdPageView;
- }
-
- // update cookies
- cookieVisitorIdValues.lastEcommerceOrderTs = isDefined(currentEcommerceOrderTs) && String(currentEcommerceOrderTs).length ? currentEcommerceOrderTs : cookieVisitorIdValues.lastEcommerceOrderTs;
- setVisitorIdCookie(cookieVisitorIdValues);
- setSessionCookie();
-
- // tracker plugin hook
- request += executePluginMethod(pluginMethod);
-
- if (configAppendToTrackingUrl.length) {
- request += '&' + configAppendToTrackingUrl;
- }
-
- if (isFunction(configCustomRequestContentProcessing)) {
- request = configCustomRequestContentProcessing(request);
- }
-
- return request;
- }
-
- /*
- * If there was user activity since the last check, and it's been configHeartBeatDelay seconds
- * since the last tracker, send a ping request (the heartbeat timeout will be reset by sendRequest).
- */
- heartBeatPingIfActivityAlias = function heartBeatPingIfActivity() {
- var now = new Date();
- if (lastTrackerRequestTime + configHeartBeatDelay <= now.getTime()) {
- var requestPing = getRequest('ping=1', null, 'ping');
- sendRequest(requestPing, configTrackerPause);
-
- return true;
- }
-
- return false;
- };
-
- function logEcommerce(orderId, grandTotal, subTotal, tax, shipping, discount) {
- var request = 'idgoal=0',
- lastEcommerceOrderTs,
- now = new Date(),
- items = [],
- sku,
- isEcommerceOrder = String(orderId).length;
-
- if (isEcommerceOrder) {
- request += '&ec_id=' + encodeWrapper(orderId);
- // Record date of order in the visitor cookie
- lastEcommerceOrderTs = Math.round(now.getTime() / 1000);
- }
-
- request += '&revenue=' + grandTotal;
-
- if (String(subTotal).length) {
- request += '&ec_st=' + subTotal;
- }
-
- if (String(tax).length) {
- request += '&ec_tx=' + tax;
- }
-
- if (String(shipping).length) {
- request += '&ec_sh=' + shipping;
- }
-
- if (String(discount).length) {
- request += '&ec_dt=' + discount;
- }
-
- if (ecommerceItems) {
- // Removing the SKU index in the array before JSON encoding
- for (sku in ecommerceItems) {
- if (Object.prototype.hasOwnProperty.call(ecommerceItems, sku)) {
- // Ensure name and category default to healthy value
- if (!isDefined(ecommerceItems[sku][1])) {
- ecommerceItems[sku][1] = "";
- }
-
- if (!isDefined(ecommerceItems[sku][2])) {
- ecommerceItems[sku][2] = "";
- }
-
- // Set price to zero
- if (!isDefined(ecommerceItems[sku][3])
- || String(ecommerceItems[sku][3]).length === 0) {
- ecommerceItems[sku][3] = 0;
- }
-
- // Set quantity to 1
- if (!isDefined(ecommerceItems[sku][4])
- || String(ecommerceItems[sku][4]).length === 0) {
- ecommerceItems[sku][4] = 1;
- }
-
- items.push(ecommerceItems[sku]);
- }
- }
- request += '&ec_items=' + encodeWrapper(JSON2.stringify(items));
- }
- request = getRequest(request, configCustomData, 'ecommerce', lastEcommerceOrderTs);
- sendRequest(request, configTrackerPause);
-
- if (isEcommerceOrder) {
- ecommerceItems = {};
- }
- }
-
- function logEcommerceOrder(orderId, grandTotal, subTotal, tax, shipping, discount) {
- if (String(orderId).length
- && isDefined(grandTotal)) {
- logEcommerce(orderId, grandTotal, subTotal, tax, shipping, discount);
- }
- }
-
- function logEcommerceCartUpdate(grandTotal) {
- if (isDefined(grandTotal)) {
- logEcommerce("", grandTotal, "", "", "", "");
- }
- }
-
- /*
- * Log the page view / visit
- */
- function logPageView(customTitle, customData, callback) {
- configIdPageView = generateUniqueId();
-
- var request = getRequest('action_name=' + encodeWrapper(titleFixup(customTitle || configTitle)), customData, 'log');
-
- sendRequest(request, configTrackerPause, callback);
- }
-
- /*
- * Construct regular expression of classes
- */
- function getClassesRegExp(configClasses, defaultClass) {
- var i,
- classesRegExp = '(^| )(piwik[_-]' + defaultClass;
-
- if (configClasses) {
- for (i = 0; i < configClasses.length; i++) {
- classesRegExp += '|' + configClasses[i];
- }
- }
-
- classesRegExp += ')( |$)';
-
- return new RegExp(classesRegExp);
- }
-
- function startsUrlWithTrackerUrl(url) {
- return (configTrackerUrl && url && 0 === String(url).indexOf(configTrackerUrl));
- }
-
- /*
- * Link or Download?
- */
- function getLinkType(className, href, isInLink, hasDownloadAttribute) {
- if (startsUrlWithTrackerUrl(href)) {
- return 0;
- }
-
- // does class indicate whether it is an (explicit/forced) outlink or a download?
- var downloadPattern = getClassesRegExp(configDownloadClasses, 'download'),
- linkPattern = getClassesRegExp(configLinkClasses, 'link'),
-
- // does file extension indicate that it is a download?
- downloadExtensionsPattern = new RegExp('\\.(' + configDownloadExtensions.join('|') + ')([?&#]|$)', 'i');
-
- if (linkPattern.test(className)) {
- return 'link';
- }
-
- if (hasDownloadAttribute || downloadPattern.test(className) || downloadExtensionsPattern.test(href)) {
- return 'download';
- }
-
- if (isInLink) {
- return 0;
- }
-
- return 'link';
- }
-
- function getSourceElement(sourceElement)
- {
- var parentElement;
-
- parentElement = sourceElement.parentNode;
- while (parentElement !== null &&
- /* buggy IE5.5 */
- isDefined(parentElement)) {
-
- if (query.isLinkElement(sourceElement)) {
- break;
- }
- sourceElement = parentElement;
- parentElement = sourceElement.parentNode;
- }
-
- return sourceElement;
- }
-
- function getLinkIfShouldBeProcessed(sourceElement)
- {
- sourceElement = getSourceElement(sourceElement);
-
- if (!query.hasNodeAttribute(sourceElement, 'href')) {
- return;
- }
-
- if (!isDefined(sourceElement.href)) {
- return;
- }
-
- var href = query.getAttributeValueFromNode(sourceElement, 'href');
-
- if (startsUrlWithTrackerUrl(href)) {
- return;
- }
-
- var originalSourcePath = sourceElement.pathname || getPathName(sourceElement.href);
-
- // browsers, such as Safari, don't downcase hostname and href
- var originalSourceHostName = sourceElement.hostname || getHostName(sourceElement.href);
- var sourceHostName = originalSourceHostName.toLowerCase();
- var sourceHref = sourceElement.href.replace(originalSourceHostName, sourceHostName);
-
- // browsers, such as Safari, don't downcase hostname and href
- var scriptProtocol = new RegExp('^(javascript|vbscript|jscript|mocha|livescript|ecmascript|mailto|tel):', 'i');
-
- if (!scriptProtocol.test(sourceHref)) {
- // track outlinks and all downloads
- var linkType = getLinkType(sourceElement.className, sourceHref, isSiteHostPath(sourceHostName, originalSourcePath), query.hasNodeAttribute(sourceElement, 'download'));
-
- if (linkType) {
- return {
- type: linkType,
- href: sourceHref
- };
- }
- }
- }
-
- function buildContentInteractionRequest(interaction, name, piece, target)
- {
- var params = content.buildInteractionRequestParams(interaction, name, piece, target);
-
- if (!params) {
- return;
- }
-
- return getRequest(params, null, 'contentInteraction');
- }
-
- function buildContentInteractionTrackingRedirectUrl(url, contentInteraction, contentName, contentPiece, contentTarget)
- {
- if (!isDefined(url)) {
- return;
- }
-
- if (startsUrlWithTrackerUrl(url)) {
- return url;
- }
-
- var redirectUrl = content.toAbsoluteUrl(url);
- var request = 'redirecturl=' + encodeWrapper(redirectUrl) + '&';
- request += buildContentInteractionRequest(contentInteraction, contentName, contentPiece, (contentTarget || url));
-
- var separator = '&';
- if (configTrackerUrl.indexOf('?') < 0) {
- separator = '?';
- }
-
- return configTrackerUrl + separator + request;
- }
-
- function isNodeAuthorizedToTriggerInteraction(contentNode, interactedNode)
- {
- if (!contentNode || !interactedNode) {
- return false;
- }
-
- var targetNode = content.findTargetNode(contentNode);
-
- if (content.shouldIgnoreInteraction(targetNode)) {
- // interaction should be ignored
- return false;
- }
-
- targetNode = content.findTargetNodeNoDefault(contentNode);
- if (targetNode && !containsNodeElement(targetNode, interactedNode)) {
- /**
- * There is a target node defined but the clicked element is not within the target node. example:
- * <div data-track-content><a href="Y" data-content-target>Y</a><img src=""/><a href="Z">Z</a></div>
- *
- * The user clicked in this case on link Z and not on target Y
- */
- return false;
- }
-
- return true;
- }
-
- function getContentInteractionToRequestIfPossible (anyNode, interaction, fallbackTarget)
- {
- if (!anyNode) {
- return;
- }
-
- var contentNode = content.findParentContentNode(anyNode);
-
- if (!contentNode) {
- // we are not within a content block
- return;
- }
-
- if (!isNodeAuthorizedToTriggerInteraction(contentNode, anyNode)) {
- return;
- }
-
- var contentBlock = content.buildContentBlock(contentNode);
-
- if (!contentBlock) {
- return;
- }
-
- if (!contentBlock.target && fallbackTarget) {
- contentBlock.target = fallbackTarget;
- }
-
- return content.buildInteractionRequestParams(interaction, contentBlock.name, contentBlock.piece, contentBlock.target);
- }
-
- function wasContentImpressionAlreadyTracked(contentBlock)
- {
- if (!trackedContentImpressions || !trackedContentImpressions.length) {
- return false;
- }
-
- var index, trackedContent;
-
- for (index = 0; index < trackedContentImpressions.length; index++) {
- trackedContent = trackedContentImpressions[index];
-
- if (trackedContent &&
- trackedContent.name === contentBlock.name &&
- trackedContent.piece === contentBlock.piece &&
- trackedContent.target === contentBlock.target) {
- return true;
- }
- }
-
- return false;
- }
-
- function replaceHrefIfInternalLink(contentBlock)
- {
- if (!contentBlock) {
- return false;
- }
-
- var targetNode = content.findTargetNode(contentBlock);
-
- if (!targetNode || content.shouldIgnoreInteraction(targetNode)) {
- return false;
- }
-
- var link = getLinkIfShouldBeProcessed(targetNode);
- if (linkTrackingEnabled && link && link.type) {
-
- return false; // will be handled via outlink or download.
- }
-
- if (query.isLinkElement(targetNode) &&
- query.hasNodeAttributeWithValue(targetNode, 'href')) {
- var url = String(query.getAttributeValueFromNode(targetNode, 'href'));
-
- if (0 === url.indexOf('#')) {
- return false;
- }
-
- if (startsUrlWithTrackerUrl(url)) {
- return true;
- }
-
- if (!content.isUrlToCurrentDomain(url)) {
- return false;
- }
-
- var block = content.buildContentBlock(contentBlock);
-
- if (!block) {
- return;
- }
-
- var contentName = block.name;
- var contentPiece = block.piece;
- var contentTarget = block.target;
-
- if (!query.hasNodeAttributeWithValue(targetNode, content.CONTENT_TARGET_ATTR) || targetNode.wasContentTargetAttrReplaced) {
- // make sure we still track the correct content target when an interaction is happening
- targetNode.wasContentTargetAttrReplaced = true;
- contentTarget = content.toAbsoluteUrl(url);
- query.setAnyAttribute(targetNode, content.CONTENT_TARGET_ATTR, contentTarget);
- }
-
- var targetUrl = buildContentInteractionTrackingRedirectUrl(url, 'click', contentName, contentPiece, contentTarget);
-
- // location.href does not respect target=_blank so we prefer to use this
- content.setHrefAttribute(targetNode, targetUrl);
-
- return true;
- }
-
- return false;
- }
-
- function replaceHrefsIfInternalLink(contentNodes)
- {
- if (!contentNodes || !contentNodes.length) {
- return;
- }
-
- var index;
- for (index = 0; index < contentNodes.length; index++) {
- replaceHrefIfInternalLink(contentNodes[index]);
- }
- }
-
- function trackContentImpressionClickInteraction (targetNode)
- {
- return function (event) {
-
- if (!targetNode) {
- return;
- }
-
- var contentBlock = content.findParentContentNode(targetNode);
-
- var interactedElement;
- if (event) {
- interactedElement = event.target || event.srcElement;
- }
- if (!interactedElement) {
- interactedElement = targetNode;
- }
-
- if (!isNodeAuthorizedToTriggerInteraction(contentBlock, interactedElement)) {
- return;
- }
-
- setExpireDateTime(configTrackerPause);
-
- if (query.isLinkElement(targetNode) &&
- query.hasNodeAttributeWithValue(targetNode, 'href') &&
- query.hasNodeAttributeWithValue(targetNode, content.CONTENT_TARGET_ATTR)) {
- // there is a href attribute, the link was replaced with piwik.php but later the href was changed again by the application.
- var href = query.getAttributeValueFromNode(targetNode, 'href');
- if (!startsUrlWithTrackerUrl(href) && targetNode.wasContentTargetAttrReplaced) {
- query.setAnyAttribute(targetNode, content.CONTENT_TARGET_ATTR, '');
- }
- }
-
- var link = getLinkIfShouldBeProcessed(targetNode);
-
- if (linkTrackingInstalled && link && link.type) {
- // click ignore, will be tracked via processClick, we do not want to track it twice
-
- return link.type;
- }
-
- if (replaceHrefIfInternalLink(contentBlock)) {
- return 'href';
- }
-
- var block = content.buildContentBlock(contentBlock);
-
- if (!block) {
- return;
- }
-
- var contentName = block.name;
- var contentPiece = block.piece;
- var contentTarget = block.target;
-
- // click on any non link element, or on a link element that has not an href attribute or on an anchor
- var request = buildContentInteractionRequest('click', contentName, contentPiece, contentTarget);
- sendRequest(request, configTrackerPause);
-
- return request;
- };
- }
-
- function setupInteractionsTracking(contentNodes)
- {
- if (!contentNodes || !contentNodes.length) {
- return;
- }
-
- var index, targetNode;
- for (index = 0; index < contentNodes.length; index++) {
- targetNode = content.findTargetNode(contentNodes[index]);
-
- if (targetNode && !targetNode.contentInteractionTrackingSetupDone) {
- targetNode.contentInteractionTrackingSetupDone = true;
-
- addEventListener(targetNode, 'click', trackContentImpressionClickInteraction(targetNode));
- }
- }
- }
-
- /*
- * Log all content pieces
- */
- function buildContentImpressionsRequests(contents, contentNodes)
- {
- if (!contents || !contents.length) {
- return [];
- }
-
- var index, request;
-
- for (index = 0; index < contents.length; index++) {
-
- if (wasContentImpressionAlreadyTracked(contents[index])) {
- contents.splice(index, 1);
- index--;
- } else {
- trackedContentImpressions.push(contents[index]);
- }
- }
-
- if (!contents || !contents.length) {
- return [];
- }
-
- replaceHrefsIfInternalLink(contentNodes);
- setupInteractionsTracking(contentNodes);
-
- var requests = [];
-
- for (index = 0; index < contents.length; index++) {
-
- request = getRequest(
- content.buildImpressionRequestParams(contents[index].name, contents[index].piece, contents[index].target),
- undefined,
- 'contentImpressions'
- );
-
- if (request) {
- requests.push(request);
- }
- }
-
- return requests;
- }
-
- /*
- * Log all content pieces
- */
- function getContentImpressionsRequestsFromNodes(contentNodes)
- {
- var contents = content.collectContent(contentNodes);
-
- return buildContentImpressionsRequests(contents, contentNodes);
- }
-
- /*
- * Log currently visible content pieces
- */
- function getCurrentlyVisibleContentImpressionsRequestsIfNotTrackedYet(contentNodes)
- {
- if (!contentNodes || !contentNodes.length) {
- return [];
- }
-
- var index;
-
- for (index = 0; index < contentNodes.length; index++) {
- if (!content.isNodeVisible(contentNodes[index])) {
- contentNodes.splice(index, 1);
- index--;
- }
- }
-
- if (!contentNodes || !contentNodes.length) {
- return [];
- }
-
- return getContentImpressionsRequestsFromNodes(contentNodes);
- }
-
- function buildContentImpressionRequest(contentName, contentPiece, contentTarget)
- {
- var params = content.buildImpressionRequestParams(contentName, contentPiece, contentTarget);
-
- return getRequest(params, null, 'contentImpression');
- }
-
- function buildContentInteractionRequestNode(node, contentInteraction)
- {
- if (!node) {
- return;
- }
-
- var contentNode = content.findParentContentNode(node);
- var contentBlock = content.buildContentBlock(contentNode);
-
- if (!contentBlock) {
- return;
- }
-
- if (!contentInteraction) {
- contentInteraction = 'Unknown';
- }
-
- return buildContentInteractionRequest(contentInteraction, contentBlock.name, contentBlock.piece, contentBlock.target);
- }
-
- function buildEventRequest(category, action, name, value)
- {
- return 'e_c=' + encodeWrapper(category)
- + '&e_a=' + encodeWrapper(action)
- + (isDefined(name) ? '&e_n=' + encodeWrapper(name) : '')
- + (isDefined(value) ? '&e_v=' + encodeWrapper(value) : '');
- }
-
- /*
- * Log the event
- */
- function logEvent(category, action, name, value, customData, callback)
- {
- // Category and Action are required parameters
- if (String(category).length === 0 || String(action).length === 0) {
- return false;
- }
- var request = getRequest(
- buildEventRequest(category, action, name, value),
- customData,
- 'event'
- );
-
- sendRequest(request, configTrackerPause, callback);
- }
-
- /*
- * Log the site search request
- */
- function logSiteSearch(keyword, category, resultsCount, customData) {
- var request = getRequest('search=' + encodeWrapper(keyword)
- + (category ? '&search_cat=' + encodeWrapper(category) : '')
- + (isDefined(resultsCount) ? '&search_count=' + resultsCount : ''), customData, 'sitesearch');
-
- sendRequest(request, configTrackerPause);
- }
-
- /*
- * Log the goal with the server
- */
- function logGoal(idGoal, customRevenue, customData) {
- var request = getRequest('idgoal=' + idGoal + (customRevenue ? '&revenue=' + customRevenue : ''), customData, 'goal');
-
- sendRequest(request, configTrackerPause);
- }
-
- /*
- * Log the link or click with the server
- */
- function logLink(url, linkType, customData, callback, sourceElement) {
-
- var linkParams = linkType + '=' + encodeWrapper(purify(url));
-
- var interaction = getContentInteractionToRequestIfPossible(sourceElement, 'click', url);
-
- if (interaction) {
- linkParams += '&' + interaction;
- }
-
- var request = getRequest(linkParams, customData, 'link');
-
- sendRequest(request, configTrackerPause, callback);
- }
-
- /*
- * Browser prefix
- */
- function prefixPropertyName(prefix, propertyName) {
- if (prefix !== '') {
- return prefix + propertyName.charAt(0).toUpperCase() + propertyName.slice(1);
- }
-
- return propertyName;
- }
-
- /*
- * Check for pre-rendered web pages, and log the page view/link/goal
- * according to the configuration and/or visibility
- *
- * @see http://dvcs.w3.org/hg/webperf/raw-file/tip/specs/PageVisibility/Overview.html
- */
- function trackCallback(callback) {
- var isPreRendered,
- i,
- // Chrome 13, IE10, FF10
- prefixes = ['', 'webkit', 'ms', 'moz'],
- prefix;
-
- if (!configCountPreRendered) {
- for (i = 0; i < prefixes.length; i++) {
- prefix = prefixes[i];
-
- // does this browser support the page visibility API?
- if (Object.prototype.hasOwnProperty.call(documentAlias, prefixPropertyName(prefix, 'hidden'))) {
- // if pre-rendered, then defer callback until page visibility changes
- if (documentAlias[prefixPropertyName(prefix, 'visibilityState')] === 'prerender') {
- isPreRendered = true;
- }
- break;
- }
- }
- }
-
- if (isPreRendered) {
- // note: the event name doesn't follow the same naming convention as vendor properties
- addEventListener(documentAlias, prefix + 'visibilitychange', function ready() {
- documentAlias.removeEventListener(prefix + 'visibilitychange', ready, false);
- callback();
- });
-
- return;
- }
-
- // configCountPreRendered === true || isPreRendered === false
- callback();
- }
-
- /*
- * Process clicks
- */
- function processClick(sourceElement) {
- var link = getLinkIfShouldBeProcessed(sourceElement);
-
- if (link && link.type) {
- link.href = safeDecodeWrapper(link.href);
- logLink(link.href, link.type, undefined, null, sourceElement);
- }
- }
-
- function isIE8orOlder()
- {
- return documentAlias.all && !documentAlias.addEventListener;
- }
-
- function getKeyCodeFromEvent(event)
- {
- // event.which is deprecated https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/which
- var which = event.which;
-
- /**
- 1 : Left mouse button
- 2 : Wheel button or middle button
- 3 : Right mouse button
- */
-
- var typeOfEventButton = (typeof event.button);
-
- if (!which && typeOfEventButton !== 'undefined' ) {
- /**
- -1: No button pressed
- 0 : Main button pressed, usually the left button
- 1 : Auxiliary button pressed, usually the wheel button or themiddle button (if present)
- 2 : Secondary button pressed, usually the right button
- 3 : Fourth button, typically the Browser Back button
- 4 : Fifth button, typically the Browser Forward button
-
- IE8 and earlier has different values:
- 1 : Left mouse button
- 2 : Right mouse button
- 4 : Wheel button or middle button
-
- For a left-hand configured mouse, the return values are reversed. We do not take care of that.
- */
-
- if (isIE8orOlder()) {
- if (event.button & 1) {
- which = 1;
- } else if (event.button & 2) {
- which = 3;
- } else if (event.button & 4) {
- which = 2;
- }
- } else {
- if (event.button === 0 || event.button === '0') {
- which = 1;
- } else if (event.button & 1) {
- which = 2;
- } else if (event.button & 2) {
- which = 3;
- }
- }
- }
-
- return which;
- }
-
- function getNameOfClickedButton(event)
- {
- switch (getKeyCodeFromEvent(event)) {
- case 1:
- return 'left';
- case 2:
- return 'middle';
- case 3:
- return 'right';
- }
- }
-
- function getTargetElementFromEvent(event)
- {
- return event.target || event.srcElement;
- }
-
- /*
- * Handle click event
- */
- function clickHandler(enable) {
-
- return function (event) {
-
- event = event || windowAlias.event;
-
- var button = getNameOfClickedButton(event);
- var target = getTargetElementFromEvent(event);
-
- if (event.type === 'click') {
-
- var ignoreClick = false;
- if (enable && button === 'middle') {
- // if enabled, we track middle clicks via mouseup
- // some browsers (eg chrome) trigger click and mousedown/up events when middle is clicked,
- // whereas some do not. This way we make "sure" to track them only once, either in click
- // (default) or in mouseup (if enable == true)
- ignoreClick = true;
- }
-
- if (target && !ignoreClick) {
- processClick(target);
- }
- } else if (event.type === 'mousedown') {
- if (button === 'middle' && target) {
- lastButton = button;
- lastTarget = target;
- } else {
- lastButton = lastTarget = null;
- }
- } else if (event.type === 'mouseup') {
- if (button === lastButton && target === lastTarget) {
- processClick(target);
- }
- lastButton = lastTarget = null;
- } else if (event.type === 'contextmenu') {
- processClick(target);
- }
- };
- }
-
- /*
- * Add click listener to a DOM element
- */
- function addClickListener(element, enable) {
- addEventListener(element, 'click', clickHandler(enable), false);
-
- if (enable) {
- addEventListener(element, 'mouseup', clickHandler(enable), false);
- addEventListener(element, 'mousedown', clickHandler(enable), false);
- addEventListener(element, 'contextmenu', clickHandler(enable), false);
- }
- }
-
- /*
- * Add click handlers to anchor and AREA elements, except those to be ignored
- */
- function addClickListeners(enable) {
- if (!linkTrackingInstalled) {
- linkTrackingInstalled = true;
-
- // iterate through anchor elements with href and AREA elements
-
- var i,
- ignorePattern = getClassesRegExp(configIgnoreClasses, 'ignore'),
- linkElements = documentAlias.links;
-
- if (linkElements) {
- for (i = 0; i < linkElements.length; i++) {
- if (!ignorePattern.test(linkElements[i].className)) {
- addClickListener(linkElements[i], enable);
- }
- }
- }
- }
- }
-
-
- function enableTrackOnlyVisibleContent (checkOnSroll, timeIntervalInMs, tracker) {
-
- if (isTrackOnlyVisibleContentEnabled) {
- // already enabled, do not register intervals again
- return true;
- }
-
- isTrackOnlyVisibleContentEnabled = true;
-
- var didScroll = false;
- var events, index;
-
- function setDidScroll() { didScroll = true; }
-
- trackCallbackOnLoad(function () {
-
- function checkContent(intervalInMs) {
- setTimeout(function () {
- if (!isTrackOnlyVisibleContentEnabled) {
- return; // the tests stopped tracking only visible content
- }
- didScroll = false;
- tracker.trackVisibleContentImpressions();
- checkContent(intervalInMs);
- }, intervalInMs);
- }
-
- function checkContentIfDidScroll(intervalInMs) {
-
- setTimeout(function () {
- if (!isTrackOnlyVisibleContentEnabled) {
- return; // the tests stopped tracking only visible content
- }
-
- if (didScroll) {
- didScroll = false;
- tracker.trackVisibleContentImpressions();
- }
-
- checkContentIfDidScroll(intervalInMs);
- }, intervalInMs);
- }
-
- if (checkOnSroll) {
-
- // scroll event is executed after each pixel, so we make sure not to
- // execute event too often. otherwise FPS goes down a lot!
- events = ['scroll', 'resize'];
- for (index = 0; index < events.length; index++) {
- if (documentAlias.addEventListener) {
- documentAlias.addEventListener(events[index], setDidScroll);
- } else {
- windowAlias.attachEvent('on' + events[index], setDidScroll);
- }
- }
-
- checkContentIfDidScroll(100);
- }
-
- if (timeIntervalInMs && timeIntervalInMs > 0) {
- timeIntervalInMs = parseInt(timeIntervalInMs, 10);
- checkContent(timeIntervalInMs);
- }
-
- });
- }
-
- /*
- * Browser features (plugins, resolution, cookies)
- */
- function detectBrowserFeatures() {
- var i,
- mimeType,
- pluginMap = {
- // document types
- pdf: 'application/pdf',
-
- // media players
- qt: 'video/quicktime',
- realp: 'audio/x-pn-realaudio-plugin',
- wma: 'application/x-mplayer2',
-
- // interactive multimedia
- dir: 'application/x-director',
- fla: 'application/x-shockwave-flash',
-
- // RIA
- java: 'application/x-java-vm',
- gears: 'application/x-googlegears',
- ag: 'application/x-silverlight'
- };
-
- // detect browser features except IE < 11 (IE 11 user agent is no longer MSIE)
- if (!((new RegExp('MSIE')).test(navigatorAlias.userAgent))) {
- // general plugin detection
- if (navigatorAlias.mimeTypes && navigatorAlias.mimeTypes.length) {
- for (i in pluginMap) {
- if (Object.prototype.hasOwnProperty.call(pluginMap, i)) {
- mimeType = navigatorAlias.mimeTypes[pluginMap[i]];
- browserFeatures[i] = (mimeType && mimeType.enabledPlugin) ? '1' : '0';
- }
- }
- }
-
- // Safari and Opera
- // IE6/IE7 navigator.javaEnabled can't be aliased, so test directly
- if (typeof navigator.javaEnabled !== 'unknown' &&
- isDefined(navigatorAlias.javaEnabled) &&
- navigatorAlias.javaEnabled()) {
- browserFeatures.java = '1';
- }
-
- // Firefox
- if (isFunction(windowAlias.GearsFactory)) {
- browserFeatures.gears = '1';
- }
-
- // other browser features
- browserFeatures.cookie = hasCookies();
- }
-
- var width = parseInt(screenAlias.width, 10);
- var height = parseInt(screenAlias.height, 10);
- browserFeatures.res = parseInt(width, 10) + 'x' + parseInt(height, 10);
- }
-
-/*<DEBUG>*/
- /*
- * Register a test hook. Using eval() permits access to otherwise
- * privileged members.
- */
- function registerHook(hookName, userHook) {
- var hookObj = null;
-
- if (isString(hookName) && !isDefined(registeredHooks[hookName]) && userHook) {
- if (isObject(userHook)) {
- hookObj = userHook;
- } else if (isString(userHook)) {
- try {
- eval('hookObj =' + userHook);
- } catch (ignore) { }
- }
-
- registeredHooks[hookName] = hookObj;
- }
-
- return hookObj;
- }
-/*</DEBUG>*/
-
- /************************************************************
- * Constructor
- ************************************************************/
-
- /*
- * initialize tracker
- */
- detectBrowserFeatures();
- updateDomainHash();
- setVisitorIdCookie();
-
-/*<DEBUG>*/
- /*
- * initialize test plugin
- */
- executePluginMethod('run', registerHook);
-/*</DEBUG>*/
-
- /************************************************************
- * Public data and methods
- ************************************************************/
-
-
-/*<DEBUG>*/
- /*
- * Test hook accessors
- */
- this.hook = registeredHooks;
- this.getHook = function (hookName) {
- return registeredHooks[hookName];
- };
- this.getQuery = function () {
- return query;
- };
- this.getContent = function () {
- return content;
- };
-
- 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.getConfigIdPageView = function () {
- return configIdPageView;
- };
- 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)
- */
- this.getVisitorId = function () {
- return getValuesFromVisitorIdCookie().uuid;
- };
-
- /**
- * 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 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 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 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 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];
- };
-
- /**
- * 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
- */
- 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');
- }
-
- if (!isDefined(piwikUrl) || null === piwikUrl) {
- piwikUrl = this.getTrackerUrl();
- }
-
- var tracker = new Tracker(piwikUrl, siteId);
-
- asyncTrackers.push(tracker);
-
- return tracker;
- };
-
- /**
- * Returns the site ID
- *
- * @returns int
- */
- this.getSiteId = function() {
- return configTrackerSiteId;
- };
-
- /**
- * Specify the site ID
- *
- * @param int|string siteId
- */
- this.setSiteId = function (siteId) {
- setSiteId(siteId);
- };
-
- /**
- * 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);
- };
-
- /**
- * Gets the User ID if set.
- *
- * @returns string User ID
- */
- this.getUserId = function() {
- return configUserId;
- };
-
- /**
- * 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;
- }
- };
-
- /**
- * Get custom data
- *
- * @return mixed
- */
- this.getCustomData = function () {
- return configCustomData;
- };
-
- /**
- * 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;
- };
-
- /**
- * 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;
- };
-
- /**
- * 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);
- };
-
- /**
- * 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;
- };
-
- /**
- * 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 = '';
- }
- if (!isString(value)) {
- value = String(value);
- }
- customDimensions[customDimensionId] = value;
- }
- };
-
- /**
- * 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];
- }
- };
-
- /**
- * Delete a custom dimension.
- *
- * @param int index Custom dimension Id
- */
- this.deleteCustomDimension = function (customDimensionId) {
- customDimensionId = parseInt(customDimensionId, 10);
- if (customDimensionId > 0) {
- delete customDimensions[customDimensionId];
- }
- };
-
- /**
- * 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();
- 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;
- }
- }
- };
-
- /**
- * 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;
-
- if (!isDefined(scope)) {
- scope = "visit";
- }
-
- 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];
- }
-
- if (!isDefined(cvar)
- || (cvar && cvar[0] === '')) {
- return false;
- }
-
- return cvar;
- };
-
- /**
- * 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);
- }
- };
-
- /**
- * 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;
- };
-
- /**
- * 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;
- };
-
- /**
- * 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;
- }
-
- var pathName = getPathName(alias);
- if (pathName && pathName !== '/' && pathName !== '/*') {
- hasDomainAliasAlready = true;
- break;
- }
- }
-
- // 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 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 method
- *
- * @param string method GET or POST; default is GET
- */
- this.setRequestMethod = function (method) {
- configRequestMethod = method || defaultRequestMethod;
- };
-
- /**
- * 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 referrer
- *
- * @param string url
- */
- this.setReferrerUrl = function (url) {
- configReferrerUrl = url;
- };
-
- /**
- * Override url
- *
- * @param string url
- */
- this.setCustomUrl = function (url) {
- configCustomUrl = resolveRelativeReference(locationHrefAlias, url);
- };
-
- /**
- * Override document.title
- *
- * @param string title
- */
- this.setDocumentTitle = function (title) {
- configTitle = title;
- };
-
- /**
- * 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 downloads
- *
- * @param string|array downloadClasses
- */
- this.setDownloadClasses = function (downloadClasses) {
- configDownloadClasses = isString(downloadClasses) ? [downloadClasses] : downloadClasses;
- };
-
- /**
- * 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 name parameters
- *
- * @see http://piwik.org/faq/how-to/#faq_120
- * @param string|array campaignNames
- */
- this.setCampaignNameKey = function (campaignNames) {
- configCampaignNameParameters = isString(campaignNames) ? [campaignNames] : campaignNames;
- };
-
- /**
- * Set array of campaign keyword parameters
- *
- * @see http://piwik.org/faq/how-to/#faq_120
- * @param string|array campaignKeywords
- */
- this.setCampaignKeywordKey = function (campaignKeywords) {
- configCampaignKeywordParameters = isString(campaignKeywords) ? [campaignKeywords] : campaignKeywords;
- };
-
- /**
- * 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 name prefix
- *
- * @param string cookieNamePrefix
- */
- this.setCookieNamePrefix = function (cookieNamePrefix) {
- configCookieNamePrefix = cookieNamePrefix;
- // Re-init the Custom Variables cookie
- customVariables = getCustomVariablesFromCookie();
- };
-
- /**
- * Set first-party cookie domain
- *
- * @param string domain
- */
- this.setCookieDomain = function (domain) {
- var domainFixed = domainFixup(domain);
-
- if (isPossibleToSetCookieOnDomain(domainFixed)) {
- configCookieDomain = domainFixed;
- updateDomainHash();
- }
- };
-
- /**
- * Set first-party cookie path
- *
- * @param string domain
- */
- this.setCookiePath = function (path) {
- configCookiePath = path;
- updateDomainHash();
- };
-
- /**
- * Set visitor cookie timeout (in seconds)
- * Defaults to 13 months (timeout=33955200)
- *
- * @param int timeout
- */
- this.setVisitorCookieTimeout = function (timeout) {
- configVisitorCookieTimeout = 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 referral cookie timeout (in seconds).
- * Defaults to 6 months (15768000000)
- *
- * @param int timeout
- */
- this.setReferralCookieTimeout = function (timeout) {
- configReferralCookieTimeout = timeout * 1000;
- };
-
- /**
- * Set conversion attribution to first referrer and campaign
- *
- * @param bool if true, use first referrer (and first campaign)
- * if false, use the last referrer (or campaign)
- */
- this.setConversionAttributionFirstReferrer = function (enable) {
- configConversionAttributionFirstReferrer = enable;
- };
-
- /**
- * 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';
-
- if (configTrackerSiteId) {
- deleteCookies();
- }
- };
-
- /**
- * 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();
- };
-
- /**
- * 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');
-
- // do not track also disables cookies and deletes existing cookies
- if (configDoNotTrack) {
- this.disableCookies();
- }
- };
-
- /**
- * Add click listener to a specific link element.
- * When clicked, Piwik will log the click automatically.
- *
- * @param DOMElement element
- * @param bool enable If true, use pseudo click-handler (middle click + context menu)
- */
- this.addListener = function (element, enable) {
- addClickListener(element, enable);
- };
-
- /**
- * Install link tracker
- *
- * The default behaviour is to use actual click events. However, some browsers
- * (e.g., Firefox, Opera, and Konqueror) don't generate click events for the middle mouse button.
- *
- * To capture more "clicks", the pseudo click-handler uses mousedown + mouseup events.
- * This is not industry standard and is vulnerable to false positives (e.g., drag events).
- *
- * There is a Safari/Chrome/Webkit bug that prevents tracking requests from being sent
- * by either click handler. The workaround is to set a target attribute (which can't
- * be "_self", "_top", or "_parent").
- *
- * @see https://bugs.webkit.org/show_bug.cgi?id=54783
- *
- * @param bool enable If "true", use pseudo click-handler (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.
- */
- this.enableJSErrorTracking = function () {
- if (enableJSErrorTracking) {
- return;
- }
-
- enableJSErrorTracking = true;
- var onError = windowAlias.onerror;
-
- windowAlias.onerror = function (message, url, linenumber, column, error) {
- trackCallback(function () {
- var category = 'JavaScript Errors';
-
- var action = url + ':' + linenumber;
- if (column) {
- action += ':' + column;
- }
-
- logEvent(category, action, message);
- });
-
- if (onError) {
- return onError(message, url, linenumber, column, error);
- }
-
- return 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
- */
- this.setGenerationTimeMs = function (generationTime) {
- configPerformanceGenerationTime = parseInt(generationTime, 10);
- };
-
- /**
- * 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();
- }
- };
-
-/*<DEBUG>*/
- /**
- * Clear heartbeat.
- */
- this.disableHeartBeatTimer = function () {
- heartBeatDown();
- configHeartBeatDelay = null;
-
- window.removeEventListener('focus', heartBeatOnFocus);
- window.removeEventListener('blur', heartBeatOnBlur);
- };
-/*</DEBUG>*/
-
- /**
- * 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
- */
- 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
- */
- this.setCountPreRendered = function (enable) {
- configCountPreRendered = enable;
- };
-
- /**
- * 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 () {
- injectOverlayScripts(configTrackerUrl, configApiUrl, configTrackerSiteId);
- });
- } else {
- trackCallback(function () {
- logPageView(customTitle, customData, callback);
- });
- }
- };
-
- /**
- * 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);
-
- 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`.
- */
- this.trackVisibleContentImpressions = function (checkOnSroll, timeIntervalInMs) {
- if (isOverlaySession(configTrackerSiteId)) {
- return;
- }
-
- if (!isDefined(checkOnSroll)) {
- checkOnSroll = true;
- }
-
- if (!isDefined(timeIntervalInMs)) {
- timeIntervalInMs = 750;
- }
-
- 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);
-
- 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.
- */
- this.trackContentImpression = function (contentName, contentPiece, contentTarget) {
- if (isOverlaySession(configTrackerSiteId)) {
- return;
- }
-
- if (!contentName) {
- return;
- }
-
- contentPiece = contentPiece || 'Unknown';
-
- trackCallback(function () {
- var request = buildContentImpressionRequest(contentName, contentPiece, contentTarget);
- sendRequest(request, configTrackerPause);
- });
- };
-
- /**
- * 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;
- }
-
- 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);
-
- var requests = getContentImpressionsRequestsFromNodes(contentNodes);
- sendBulkRequest(requests, configTrackerPause);
- });
- }
- });
- };
-
- /**
- * 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;
- }
-
- if (!contentInteraction || !contentName) {
- return;
- }
-
- contentPiece = contentPiece || 'Unknown';
-
- trackCallback(function () {
- var request = buildContentInteractionRequest(contentInteraction, contentName, contentPiece, contentTarget);
- sendRequest(request, configTrackerPause);
- });
- };
-
- /**
- * 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;
- }
-
- trackCallback(function () {
- var request = buildContentInteractionRequestNode(domNode, contentInteraction);
- 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.
- */
- this.logAllContentBlocksOnPage = function () {
- var contentNodes = content.findContentNodes();
- var contents = content.collectContent(contentNodes);
-
- if (console !== undefined && console && console.log) {
- console.log(contents);
- }
- };
-
- /**
- * 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 function callback
- * @param mixed customData
- */
- this.trackEvent = function (category, action, name, value, customData, callback) {
- trackCallback(function () {
- logEvent(category, action, name, value, customData, callback);
- });
- };
-
- /**
- * 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);
- });
- };
-
- /**
- * 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);
- }
-
- customVariablesPage[5] = ['_pkc', category];
-
- if (isDefined(price) && String(price).length) {
- customVariablesPage[2] = ['_pkp', price];
- }
-
- // On a category page, do not track Product name not defined
- if ((!isDefined(sku) || !sku.length)
- && (!isDefined(name) || !name.length)) {
- return;
- }
-
- if (isDefined(sku) && sku.length) {
- customVariablesPage[3] = ['_pks', sku];
- }
-
- if (!isDefined(name) || !name.length) {
- name = "";
- }
-
- customVariablesPage[4] = ['_pkn', name];
- };
-
- /**
- * Adds an item (product) that is in the current Cart or in the Ecommerce order.
- * This function is called for every item (product) in the Cart or the Order.
- * The only required parameter is sku.
- * 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);
- };
-
- /**
- * 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 {
- push: apply
- };
- }
-
- /**
- * Applies the given methods in the given order if they are present in paq.
- *
- * @param {Array} paq
- * @param {Array} methodsToApply an array containing method names in the order that they should be applied
- * eg ['setSiteId', 'setTrackerUrl']
- * @returns {Array} the modified paq array with the methods that were already applied set to undefined
- */
- function applyMethodsInOrder(paq, methodsToApply)
- {
- var appliedMethods = {};
- var index, iterator;
-
- for (index = 0; index < methodsToApply.length; index++) {
- var methodNameToApply = methodsToApply[index];
- appliedMethods[methodNameToApply] = 1;
-
- for (iterator = 0; iterator < paq.length; iterator++) {
- if (paq[iterator] && paq[iterator][0]) {
- var methodName = paq[iterator][0];
-
- if (methodNameToApply === methodName) {
- apply(paq[iterator]);
- delete paq[iterator];
-
- if (appliedMethods[methodName] > 1) {
- 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]++;
- }
- }
- }
- }
-
- return paq;
- }
-
- /************************************************************
- * Constructor
- ************************************************************/
-
- var applyFirst = ['addTracker', 'disableCookies', 'setTrackerUrl', 'setAPIUrl', 'setCookiePath', 'setCookieDomain', 'setDomains', 'setUserId', 'setSiteId', 'enableLinkTracking'];
-
- function createFirstTracker(piwikUrl, siteId)
- {
- var tracker = new Tracker(piwikUrl, siteId);
- asyncTrackers.push(tracker);
-
- _paq = applyMethodsInOrder(_paq, applyFirst);
-
- // 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;
- }
-
- /************************************************************
- * 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,
-
- /**
- * DOM Document related methods
- */
- DOM: {
- /**
- * Adds an event listener to the given element.
- * @param element
- * @param eventType
- * @param eventHandler
- * @param useCapture Optional
- */
- addEventListener: function (element, eventType, eventHandler, useCapture) {
- var captureType = typeof useCapture;
- if (captureType === 'undefined') {
- useCapture = false;
- }
-
- addEventListener(element, eventType, eventHandler, useCapture);
- },
- /**
- * Specify a function to execute when the DOM is fully loaded.
- *
- * If the DOM is already loaded, the function will be executed immediately.
- *
- * @param function callback
- */
- onLoad: trackCallbackOnLoad,
-
- /**
- * Specify a function to execute when the DOM is ready.
- *
- * If the DOM is already ready, the function will be executed immediately.
- *
- * @param function callback
- */
- onReady: trackCallbackOnReady
- },
-
- /**
- * 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) {
- eventHandlers[event].splice(i, 1);
- }
- }
- },
-
- /**
- * 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
- *
- * @param string pluginName
- * @param Object pluginObj
- */
- addPlugin: function (pluginName, pluginObj) {
- plugins[pluginName] = pluginObj;
- },
-
- /**
- * Get Tracker (factory method)
- *
- * @param string piwikUrl
- * @param int|string siteId
- * @return Tracker
- */
- getTracker: function (piwikUrl, siteId) {
- if (!isDefined(siteId)) {
- siteId = this.getAsyncTracker().getSiteId();
- }
- if (!isDefined(piwikUrl)) {
- piwikUrl = this.getAsyncTracker().getTrackerUrl();
- }
-
- return new Tracker(piwikUrl, siteId);
- },
-
- /**
- * 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 (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;
- }
- }
- },
-
- /**
- * When calling plugin methods via "_paq.push(['...'])" and the plugin is loaded separately because
- * piwik.js is not writable then there is a chance that first piwik.js is loaded and later the plugin.
- * In this case we would have already executed all "_paq.push" methods and they would not have succeeded
- * because the plugin will be loaded only later. In this case, once a plugin is loaded, it should call
- * "Piwik.retryMissedPluginCalls()" so they will be executed after all.
- *
- * @param string piwikUrl
- * @param int|string siteId
- * @return Tracker
- */
- retryMissedPluginCalls: function () {
- var missedCalls = missedPluginTrackerCalls;
- missedPluginTrackerCalls = [];
- var i = 0;
- for (i; i < missedCalls.length; i++) {
- apply(missedCalls[i]);
- }
- }
- };
-
- // Expose Piwik as an AMD module
- if (typeof define === 'function' && define.amd) {
- define('piwik', [], function () { return Piwik; });
- }
-
- return Piwik;
- }());
-}
-
-/*!! 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();
-}
-
-/*jslint sloppy: true */
-(function () {
- var jsTrackerType = (typeof AnalyticsTracker);
- if (jsTrackerType === 'undefined') {
- AnalyticsTracker = window.Piwik;
- }
-}());
-/*jslint sloppy: false */
-
-/************************************************************
- * Deprecated functionality below
- * Legacy piwik.js compatibility ftw
- ************************************************************/
-
-/*
- * Piwik globals
- *
- * var piwik_install_tracker, piwik_tracker_pause, piwik_download_extensions, piwik_hosts_alias, piwik_ignore_classes;
- */
-/*global piwik_log:true */
-/*global piwik_track:true */
-
-/**
- * Track page visit
- *
- * @param string documentTitle
- * @param int|string siteId
- * @param string piwikUrl
- * @param mixed customData
- */
-if (typeof piwik_log !== 'function') {
- piwik_log = function (documentTitle, siteId, piwikUrl, customData) {
- 'use strict';
-
- function getOption(optionName) {
- try {
- if (window['piwik_' + optionName]) {
- return window['piwik_' + optionName];
- }
- } catch (ignore) { }
-
- return; // undefined
- }
-
- // instantiate the tracker
- var option,
- piwikTracker = window.Piwik.getTracker(piwikUrl, siteId);
-
- // initialize tracker
- piwikTracker.setDocumentTitle(documentTitle);
- piwikTracker.setCustomData(customData);
-
- // handle Piwik globals
- option = getOption('tracker_pause');
-
- if (option) {
- piwikTracker.setLinkTrackingTimer(option);
- }
-
- option = getOption('download_extensions');
-
- if (option) {
- piwikTracker.setDownloadExtensions(option);
- }
-
- option = getOption('hosts_alias');
-
- if (option) {
- piwikTracker.setDomains(option);
- }
-
- option = getOption('ignore_classes');
-
- if (option) {
- piwikTracker.setIgnoreClasses(option);
- }
-
- // track this page view
- piwikTracker.trackPageView();
-
- // default is to install the link tracker
- if (getOption('install_tracker')) {
-
- /**
- * Track click manually (function is defined below)
- *
- * @param string sourceUrl
- * @param int|string siteId
- * @param string piwikUrl
- * @param string linkType
- */
- piwik_track = function (sourceUrl, siteId, piwikUrl, linkType) {
- piwikTracker.setSiteId(siteId);
- piwikTracker.setTrackerUrl(piwikUrl);
- piwikTracker.trackLink(sourceUrl, linkType);
- };
-
- // set-up link tracking
- piwikTracker.enableLinkTracking();
- }
- };
-}
-
-/*! @license-end */