Skip to content
Extraits de code Groupes Projets
piwik.js 54,9 ko
Newer Older
  • Learn to ignore specific revisions
  • robocoder's avatar
    robocoder a validé
     * JavaScript tracking client
     *
    
    robocoder's avatar
    robocoder a validé
     * @source http://dev.piwik.org/trac/browser/trunk/js/piwik.js
     * @license http://www.opensource.org/licenses/bsd-license.php Simplified BSD
    
    robocoder's avatar
    robocoder a validé
    // Refer to README for build instructions when minifying this file for distribution.
    
    
    /*
     * Browser [In]Compatibility
     *
     * This version of piwik.js is known to not work with:
    
     * - IE4 - try..catch and for..in introduced in IE5
    
     * - IE5 - named anonymous functions, array.push, encodeURIComponent, and decodeURIComponent introduced in IE5.5
    
    /************************************************************
     * JSON - public domain reference implementation by Douglas Crockford
     * @link http://www.JSON.org/json2.js
     *
     * Modifications against json2.js 2011-01-18:
     * - handle case JSON already defined (e.g., ECMAScript 5)
     * - use new RegExp(pattern) instead of /pattern/ for greater portability
     ************************************************************/
    /*jslint evil: true, strict: true, regexp: false */
    if (!this.JSON) {
        this.JSON = {};
    }
    
    (function () {
        "use strict";
    
        function f(n) {
            // Format integers to have at least two digits.
            return n < 10 ? '0' + n : n;
        }
    
        if (typeof Date.prototype.toJSON !== 'function') {
    
            Date.prototype.toJSON = function (key) {
    
                return isFinite(this.valueOf()) ?
                    this.getUTCFullYear()     + '-' +
                    f(this.getUTCMonth() + 1) + '-' +
                    f(this.getUTCDate())      + 'T' +
                    f(this.getUTCHours())     + ':' +
                    f(this.getUTCMinutes())   + ':' +
                    f(this.getUTCSeconds())   + 'Z' : null;
            };
    
            String.prototype.toJSON      =
                Number.prototype.toJSON  =
                Boolean.prototype.toJSON = function (key) {
                    return this.valueOf();
                };
        }
    
        var cx = new RegExp("[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]", 'g'),
            escapable = new RegExp("[\\\\\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]", 'g'),
            gap,
            indent,
            meta = {    // table of character substitutions
                '\b': '\\b',
                '\t': '\\t',
                '\n': '\\n',
                '\f': '\\f',
                '\r': '\\r',
                '"' : '\\"',
                '\\': '\\\\'
            },
            rep;
    
    
        function quote(string) {
    
    // If the string contains no control characters, no quote characters, and no
    // backslash characters, then we can safely slap some quotes around it.
    // Otherwise we must also replace the offending characters with safe escape
    // sequences.
    
            escapable.lastIndex = 0;
            return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
                var c = meta[a];
                return typeof c === 'string' ? c :
                    '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
            }) + '"' : '"' + string + '"';
        }
    
    
        function str(key, holder) {
    
    // Produce a string from holder[key].
    
            var i,          // The loop counter.
                k,          // The member key.
                v,          // The member value.
                length,
                mind = gap,
                partial,
                value = holder[key];
    
    // If the value has a toJSON method, call it to obtain a replacement value.
    
            if (value && typeof value === 'object' &&
                    typeof value.toJSON === 'function') {
                value = value.toJSON(key);
            }
    
    // If we were called with a replacer function, then call the replacer to
    // obtain a replacement value.
    
            if (typeof rep === 'function') {
                value = rep.call(holder, key, value);
            }
    
    // What happens next depends on the value's type.
    
            switch (typeof value) {
            case 'string':
                return quote(value);
    
            case 'number':
    
    // JSON numbers must be finite. Encode non-finite numbers as null.
    
                return isFinite(value) ? String(value) : 'null';
    
            case 'boolean':
            case 'null':
    
    // If the value is a boolean or null, convert it to a string. Note:
    // typeof null does not produce 'null'. The case is included here in
    // the remote chance that this gets fixed someday.
    
                return String(value);
    
    // If the type is 'object', we might be dealing with an object or an array or
    // null.
    
            case 'object':
    
    // Due to a specification blunder in ECMAScript, typeof null is 'object',
    // so watch out for that case.
    
                if (!value) {
                    return 'null';
                }
    
    // Make an array to hold the partial results of stringifying this object value.
    
                gap += indent;
                partial = [];
    
    // Is the value an array?
    
                if (Object.prototype.toString.apply(value) === '[object Array]') {
    
    // The value is an array. Stringify every element. Use null as a placeholder
    // for non-JSON values.
    
                    length = value.length;
                    for (i = 0; i < length; i += 1) {
                        partial[i] = str(i, value) || 'null';
                    }
    
    // Join all of the elements together, separated with commas, and wrap them in
    // brackets.
    
                    v = partial.length === 0 ? '[]' : gap ?
                        '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' :
                        '[' + partial.join(',') + ']';
                    gap = mind;
                    return v;
                }
    
    // If the replacer is an array, use it to select the members to be stringified.
    
                if (rep && typeof rep === 'object') {
                    length = rep.length;
                    for (i = 0; i < length; i += 1) {
                        k = rep[i];
                        if (typeof k === 'string') {
                            v = str(k, value);
                            if (v) {
                                partial.push(quote(k) + (gap ? ': ' : ':') + v);
                            }
                        }
                    }
                } else {
    
    // Otherwise, iterate through all of the keys in the object.
    
                    for (k in value) {
                        if (Object.hasOwnProperty.call(value, k)) {
                            v = str(k, value);
                            if (v) {
                                partial.push(quote(k) + (gap ? ': ' : ':') + v);
                            }
                        }
                    }
                }
    
    // Join all of the member texts together, separated with commas,
    // and wrap them in braces.
    
                v = partial.length === 0 ? '{}' : gap ?
                    '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' :
                    '{' + partial.join(',') + '}';
                gap = mind;
                return v;
            }
        }
    
    // If the JSON object does not yet have a stringify method, give it one.
    
        if (typeof JSON.stringify !== 'function') {
            JSON.stringify = function (value, replacer, space) {
    
    // The stringify method takes a value and an optional replacer, and an optional
    // space parameter, and returns a JSON text. The replacer can be a function
    // that can replace values, or an array of strings that will select the keys.
    // A default replacer method can be provided. Use of the space parameter can
    // produce text that is more easily readable.
    
                var i;
                gap = '';
                indent = '';
    
    // If the space parameter is a number, make an indent string containing that
    // many spaces.
    
                if (typeof space === 'number') {
                    for (i = 0; i < space; i += 1) {
                        indent += ' ';
                    }
    
    // If the space parameter is a string, it will be used as the indent string.
    
                } else if (typeof space === 'string') {
                    indent = space;
                }
    
    // If there is a replacer, it must be a function or an array.
    // Otherwise, throw an error.
    
                rep = replacer;
                if (replacer && typeof replacer !== 'function' &&
                        (typeof replacer !== 'object' ||
                        typeof replacer.length !== 'number')) {
                    throw new Error('JSON.stringify');
                }
    
    // Make a fake root object containing our value under the key of ''.
    // Return the result of stringifying the value.
    
                return str('', {'': value});
            };
        }
    
    
    // If the JSON object does not yet have a parse method, give it one.
    
        if (typeof JSON.parse !== 'function') {
            JSON.parse = function (text, reviver) {
    
    // The parse method takes a text and an optional reviver function, and returns
    // a JavaScript value if the text is a valid JSON text.
    
                var j;
    
                function walk(holder, key) {
    
    // The walk method is used to recursively walk the resulting structure so
    // that modifications can be made.
    
                    var k, v, value = holder[key];
                    if (value && typeof value === 'object') {
                        for (k in value) {
                            if (Object.hasOwnProperty.call(value, k)) {
                                v = walk(value, k);
                                if (v !== undefined) {
                                    value[k] = v;
                                } else {
                                    delete value[k];
                                }
                            }
                        }
                    }
                    return reviver.call(holder, key, value);
                }
    
    
    // Parsing happens in four stages. In the first stage, we replace certain
    // Unicode characters with escape sequences. JavaScript handles many characters
    // incorrectly, either silently deleting them, or treating them as line endings.
    
                text = String(text);
                cx.lastIndex = 0;
                if (cx.test(text)) {
                    text = text.replace(cx, function (a) {
                        return '\\u' +
                            ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
                    });
                }
    
    // In the second stage, we run the text against regular expressions that look
    // for non-JSON patterns. We are especially concerned with '()' and 'new'
    // because they can cause invocation, and '=' because it can cause mutation.
    // But just to be safe, we want to reject all unexpected forms.
    
    // We split the second stage into 4 regexp operations in order to work around
    // crippling inefficiencies in IE's and Safari's regexp engines. First we
    // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
    // replace all simple value tokens with ']' characters. Third, we delete all
    // open brackets that follow a colon or comma or that begin the text. Finally,
    // we look to see that the remaining characters are only whitespace or ']' or
    // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
    
                if ((new RegExp('^[\\],:{}\\s]*$'))
                        .test(text.replace(new RegExp('\\\\(?:["\\\\/bfnrt]|u[0-9a-fA-F]{4})', 'g'), '@')
                            .replace(new RegExp('"[^"\\\\n\\r]*"|true|false|null|-?\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?', 'g'), ']')
                            .replace(new RegExp('(?:^|:|,)(?:\\s*\\[)+', 'g'), ''))) {
    
    // In the third stage we use the eval function to compile the text into a
    // JavaScript structure. The '{' operator is subject to a syntactic ambiguity
    // in JavaScript: it can begin a block or an object literal. We wrap the text
    // in parens to eliminate the ambiguity.
    
                    j = eval('(' + text + ')');
    
    // In the optional fourth stage, we recursively walk the new structure, passing
    // each name/value pair to a reviver function for possible transformation.
    
                    return typeof reviver === 'function' ?
                        walk({'': j}, '') : j;
                }
    
    // If the text is not JSON parseable, then a SyntaxError is thrown.
    
                throw new SyntaxError('JSON.parse');
            };
        }
    }());
    /************************************************************
     * end JSON
     ************************************************************/
    
    /*jslint browser:true, forin:true, plusplus:false, onevar:false, strict:true, evil:true */
    /*global window unescape ActiveXObject _paq:true */
    
    robocoder's avatar
    robocoder a validé
    var
    	// asynchronous tracker (or proxy)
    	_paq = _paq || [],
    
    
    	// Piwik singleton and namespace
    
    	Piwik =	Piwik || (function () {
    		"use strict";
    
    
    		/************************************************************
    		 * Private data
    		 ************************************************************/
    
    		var expireDateTime,
    
    
    			/* alias frequently used globals for added minification */
    			documentAlias = document,
    			navigatorAlias = navigator,
    			screenAlias = screen,
    			windowAlias = window,
    
    			/* DOM Ready */
    			hasLoaded = false,
    			registeredOnLoadHandlers = [],
    
    			/*
    			 * encode
    			 */
    			encodeWrapper = windowAlias.encodeURIComponent,
    
    			/*
    			 * decode
    			 */
    			decodeWrapper = windowAlias.decodeURIComponent,
    
    		/************************************************************
    		 * Private methods
    		 ************************************************************/
    
    		/*
    
    		 * Is property defined?
    
    		 */
    		function isDefined(property) {
    
    			return typeof property !== '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;
    
    		/*
    		 * apply wrapper
    		 *
    		 * @param array parameterArray An array comprising either:
    
    robocoder's avatar
    robocoder a validé
    		 *      [ 'methodName', optional_parameters ]
    
    robocoder's avatar
    robocoder a validé
    		 *      [ functionObject, optional_parameters ] 
    
    		 */
    		function apply(parameterArray) {
    			var f = parameterArray.shift();
    
    
    				asyncTracker[f].apply(asyncTracker, parameterArray);
    
    				f.apply(asyncTracker, parameterArray);
    
    		/*
    		 * Cross-browser helper function to add event handler
    		 */
    		function addEventListener(element, eventType, eventHandler, useCapture) {
    			if (element.addEventListener) {
    				element.addEventListener(eventType, eventHandler, useCapture);
    				return true;
    
    robocoder's avatar
    robocoder a validé
    			}
    			if (element.attachEvent) {
    
    				return element.attachEvent('on' + eventType, eventHandler);
    			}
    			element['on' + eventType] = eventHandler;
    		}
    
    		/*
    		 * Call plugin hook methods
    		 */
    		function executePluginMethod(methodName, callback) {
    			var result = '', i, pluginMethod;
    
    			for (i in plugins) {
    				pluginMethod = plugins[i][methodName];
    
    					result += pluginMethod(callback);
    				}
    			}
    
    			return result;
    		}
    
    		/*
    		 * Handle beforeunload event
    		 */
    
    		function beforeUnloadHandler() {
    
    robocoder's avatar
    robocoder a validé
    			executePluginMethod('unload');
    
    
    			if (expireDateTime) {
    				// the things we do for backwards compatibility...
    				// in ECMA-262 5th ed., we could simply use:  while (Date.now() < expireDateTime) { }
    
    robocoder's avatar
    robocoder a validé
    				var now;
    
    robocoder's avatar
    robocoder a validé
    				do {
    
    robocoder's avatar
    robocoder a validé
    				} while (now.getTime() < expireDateTime);
    
    			if (!hasLoaded) {
    				hasLoaded = true;
    				executePluginMethod('load');
    
    				for (i = 0; i < registeredOnLoadHandlers.length; i++) {
    
    					registeredOnLoadHandlers[i]();
    				}
    			}
    			return true;
    		}
    
    		/*
    		 * Add onload or DOM ready handler
    		 */
    		function addReadyListener() {
    			if (documentAlias.addEventListener) {
    
    robocoder's avatar
    robocoder a validé
    				addEventListener(documentAlias, 'DOMContentLoaded', function ready() {
    
    					documentAlias.removeEventListener('DOMContentLoaded', ready, false);
    					loadHandler();
    				});
    
    			} else if (documentAlias.attachEvent) {
    
    robocoder's avatar
    robocoder a validé
    				documentAlias.attachEvent('onreadystatechange', function ready() {
    					if (documentAlias.readyState === 'complete') {
    						documentAlias.detachEvent('onreadystatechange', ready);
    
    				if (documentAlias.documentElement.doScroll && windowAlias === top) {
    
    robocoder's avatar
    robocoder a validé
    						if (!hasLoaded) {
    							try {
    								documentAlias.documentElement.doScroll('left');
    							} catch (error) {
    								setTimeout(ready, 0);
    								return;
    							}
    							loadHandler();
    
    						}
    					}());
    				}
    			}
    			// fallback
    			addEventListener(windowAlias, 'load', loadHandler, false);
    		}
    
    
    		/*
    		 * Get page referrer
    		 */
    		function getReferrer() {
    			var referrer = '';
    
    			try {
    				referrer = top.document.referrer;
    			} catch (e) {
    				if (parent) {
    					try {
    						referrer = parent.document.referrer;
    					} catch (e2) {
    						referrer = '';
    					}
    				}
    			}
    			if (referrer === '') {
    				referrer = documentAlias.referrer;
    			}
    
    			return referrer;
    		}
    
    
    		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, varName) {
    			// scheme : // [username [: password] @] hostame [: port] [/ [path] [? query] [# fragment]]
    
    			var e = new RegExp('^(?:https?|ftp)(?::/*(?:[^?]+)[?])([^#]+)'),
    
    				f = new RegExp('(?:^|&)' + varName + '=([^&]*)'),
    
    				result = matches ? f.exec(matches[1]) : 0;
    
    		 * Set cookie value
    
    		function setCookie(cookieName, value, msToExpire, path, domain, secure) {
    			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 ? path : '/') +
    				(domain ? ';domain=' + domain : '') +
    				(secure ? ';secure' : '');
    		}
    
    		/*
    		 * Get cookie value
    		 */
    		function getCookie(cookieName) {
    			var cookiePattern = new RegExp('(^|;)[ ]*' + cookieName + '=([^;]*)'),
    
    				cookieMatch = cookiePattern.exec(documentAlias.cookie);
    
    			return cookieMatch ? decodeWrapper(cookieMatch[2]) : 0;
    
    mattpiwik's avatar
    mattpiwik a validé
    		
    
    		/*
    		 * 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 str = "",
    						i,
    						v;
    
    					for (i = 7; i >= 0; i--) {
    						v = (val >>> (i * 4)) & 0x0f;
    						str += v.toString(16);
    					}
    					return str;
    				},
    
    				blockstart,
    
    				W = [],
    				H0 = 0x67452301,
    				H1 = 0xEFCDAB89,
    				H2 = 0x98BADCFE,
    				H3 = 0x10325476,
    				H4 = 0xC3D2E1F0,
    
    
    			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);
    
    			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 === 'webcache.googleusercontent.com' ||			// Google
    					hostName === 'cc.bingj.com' ||							// Bing
    					hostName.substring(0, 5) === '74.6.') {					// Yahoo (via Inktomi 74.6.0.0/16)
    				href = documentAlias.links[0].href;
    				hostName = getHostName(href);
    			} else if (hostName === 'translate.googleusercontent.com') {	// Google
    				if (referrer === '') {
    					referrer = href;
    				}
    				href = getParameter(href, 'u');
    				hostName = getHostName(href);
    			}
    			return [hostName, href, referrer];
    		}
    
    		/*
    		 * Fix-up domain
    		 */
    		function domainFixup(domain) {
    			var dl = domain.length;
    
    			return (domain.charAt(--dl) === '.') ? domain.substring(0, dl) : domain;
    		}
    
    
    		/*
    		 * Piwik Tracker class
    		 *
    		 * trackerUrl and trackerSiteId are optional arguments to the constructor
    		 *
    		 * See: Tracker.setTrackerUrl() and Tracker.setSiteId()
    		 */
    		function Tracker(trackerUrl, siteId) {
    
    			/************************************************************
    			 * Private members
    			 ************************************************************/
    
    
    				// Current URL and Referrer URL
    				locationArray = urlFixup(documentAlias.domain, windowAlias.location.href, getReferrer()),
    				domainAlias = domainFixup(locationArray[0]),
    				locationHrefAlias = locationArray[1],
    				configReferrerUrl = locationArray[2],
    
    				// Request method (GET or POST)
    				configRequestMethod = 'GET',
    
    				// Document title
    				configTitle = documentAlias.title,
    
    				// Extensions to be treated as download links
    				configDownloadExtensions = '7z|aac|ar[cj]|as[fx]|avi|bin|csv|deb|dmg|doc|exe|flv|gif|gz|gzip|hqx|jar|jpe?g|js|mp(2|3|4|e?g)|mov(ie)?|ms[ip]|od[bfgpst]|og[gv]|pdf|phps|png|ppt|qtm?|ra[mr]?|rpm|sea|sit|tar|t?bz2?|tgz|torrent|txt|wav|wm[av]|wpd||xls|xml|z|zip',
    
    				// Hosts or alias(es) to not treat as outlinks
    				configHostsAlias = [domainAlias],
    
    				// HTML anchor element classes to not track
    				configIgnoreClasses = [],
    
    				// HTML anchor element classes to treat as downloads
    				configDownloadClasses = [],
    
    				// HTML anchor element classes to treat at outlinks
    				configLinkClasses = [],
    
    				// Maximum delay to wait for web bug image to be fetched (in milliseconds)
    				configTrackerPause = 500,
    
    				// Minimum visit time after initial page view (in milliseconds)
    				configMinimumVisitTime,
    
    				// Recurring heart beat after initial ping (in milliseconds)
    				configHeartBeatTimer,
    
    
    robocoder's avatar
    robocoder a validé
    
    
    				// 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,
    
    				// Do we attribute the conversion to the first referrer or the most recent referrer?
    
    				configConversionAttributionFirstReferrer,
    
    				// Life of the visitor cookie (in milliseconds)
    				configVisitorCookieTimeout = 63072000000, // 2 years
    
    				// Life of the session cookie (in milliseconds)
    				configSessionCookieTimeout = 1800000, // 30 minutes
    
    				// Life of the referral cookie (in milliseconds)
    				configReferralCookieTimeout = 15768000000, // 6 months
    
    				// Custom Variables read from cookie
    				customVariables = false,
    
    mattpiwik's avatar
    mattpiwik a validé
    
    
    				// Custom Variables names and values are each truncated before being sent in the request or recorded in the cookie
    				customVariableMaximumLength = 100,
    
    mattpiwik's avatar
    mattpiwik a validé
    			
    
    				// Client-side data collection
    				browserHasCookies = '0',
    
    				// Plugin, Parameter name, MIME type, detected
    				pluginMap = {
    					// document types
    					pdf:         ['pdf',   'application/pdf',               '0'],
    					// media players
    					quicktime:   ['qt',    'video/quicktime',               '0'],
    					realplayer:  ['realp', 'audio/x-pn-realaudio-plugin',   '0'],
    					wma:         ['wma',   'application/x-mplayer2',        '0'],
    					// interactive multimedia 
    					director:    ['dir',   'application/x-director',        '0'],
    					flash:       ['fla',   'application/x-shockwave-flash', '0'],
    					// RIA
    					java:        ['java',  'application/x-java-vm',         '0'],
    					gears:       ['gears', 'application/x-googlegears',     '0'],
    					silverlight: ['ag',    'application/x-silverlight',     '0']
    				},
    
    				// Guard against installing the link tracker more than once per Tracker instance
    				linkTrackingInstalled = false,
    
    
    robocoder's avatar
    robocoder a validé
    				// Guard against installing the activity tracker more than once per Tracker instance
    				activityTrackingInstalled = false,
    
    				// Last activity timestamp
    				lastActivityTime,
    
    
    				// Internal state of the pseudo click handler
    				lastButton,
    				lastTarget,
    
    				// Visitor ID
    				visitorId,
    
    
    robocoder's avatar
    robocoder a validé
    				// Hash function
    				hash = sha1,
    
    
    			 * Purify URL.
    
    			function purify(str) {
    				var targetPattern;
    
    				if (configDiscardHashTag) {
    					targetPattern = new RegExp('#.*');
    					return str.replace(targetPattern, '');
    				}
    				return str;
    
    			 * Is the host local?  (i.e., not an outlink)
    
    			function isSiteHostName(hostName) {
    				var i, alias, offset;
    
    				for (i = 0; i < configHostsAlias.length; i++) {
    					alias = configHostsAlias[i].toLowerCase();
    
    					if (hostName === alias) {
    						return true;
    					}
    
    					if (alias.substring(0, 2) === '*.') {
    						if (hostName === alias.substring(2)) {
    							return true;
    						}
    
    robocoder's avatar
    robocoder a validé
    
    
    						offset = hostName.length - alias.length + 1;
    						if ((offset > 0) && (hostName.substring(offset) === alias.substring(1))) {
    							return true;
    						}
    					}
    
    robocoder's avatar
    robocoder a validé
    				}
    
    				return false;
    
    robocoder's avatar
    robocoder a validé
    			}
    
    
    			 * Send image request to Piwik server using GET.
    			 * The infamous web bug is a transparent, single pixel (1x1) image
    
    			function getImage(request) {
    				var image = new Image(1, 1);
    
    				image.onLoad = function () { };