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:
authorvipsoft <vipsoft@59fd770c-687e-43c8-a1e3-f5a4ff64c105>2009-05-28 03:10:11 +0400
committervipsoft <vipsoft@59fd770c-687e-43c8-a1e3-f5a4ff64c105>2009-05-28 03:10:11 +0400
commitc5ee66efac7bc3cea460f4905a27b6f33212a90f (patch)
treef02932d8b4491834e41a8ee7285bc3da1eef9de0 /js
parentcb769d35031a6fc516790777ddb544e503d9b7c8 (diff)
fixes #355 - OO version of piwik.js which no longer modifies DOM;
remove misc/testJavascripTracker (interactive tests); add tests/javascript (QUnit unit tests); fixes #661 - use click event instead of mousedown; fixes #549 - define your own download/outlink tracking classes; fixes #82 - add hook interface for module
Diffstat (limited to 'js')
-rwxr-xr-xjs/index.php41
-rw-r--r--js/piwik.js1008
2 files changed, 1049 insertions, 0 deletions
diff --git a/js/index.php b/js/index.php
new file mode 100755
index 0000000000..60786f2849
--- /dev/null
+++ b/js/index.php
@@ -0,0 +1,41 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id$
+ */
+
+$file = "../piwik.js";
+
+/*
+ * Conditional GET
+ */
+if (file_exists($file)) {
+ $modifiedSince = '';
+ if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
+ $modifiedSince = $_SERVER['HTTP_IF_MODIFIED_SINCE'];
+ }
+ $lastModified = gmdate('D, d M Y H:i:s', filemtime($file)) . ' GMT';
+
+ // strip any trailing data appended to header
+ if (false !== ($semicolon = strpos($modifiedSince, ';'))) {
+ $modifiedSince = substr($modifiedSince, 0, $semicolon);
+ }
+
+ if ($modifiedSince == $lastModified) {
+ header('HTTP/1.1 304 Not Modified');
+ } else {
+ header('Last-Modified: ' . $lastModified);
+ header('Content-Length: ' . filesize($file));
+ header('Content-Type: application/x-javascript');
+
+ if (!readfile($file)) {
+ header ("HTTP/1.0 505 Internal server error");
+ }
+ }
+} else {
+ header ("HTTP/1.0 404 Not Found");
+}
+exit;
diff --git a/js/piwik.js b/js/piwik.js
new file mode 100644
index 0000000000..63b26796ac
--- /dev/null
+++ b/js/piwik.js
@@ -0,0 +1,1008 @@
+/*!
+ * Piwik - Web Analytics
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
+ * @version $Id$
+ */
+
+/*jslint browser:true, forin:true, plusplus:false, onevar:false, eqeqeq:false */
+/*global window escape unescape ActiveXObject */
+
+// Note: YUICompressor 2.4.2 won't compress piwik_log() because of the the "evil" eval().
+// Override this behaviour using http://yuilibrary.com/projects/yuicompressor/ticket/2343811
+/*jslint evil:true */
+
+/*
+ * Browser [In]Compatibility
+ *
+ * This version of piwik.js is known to not work with:
+ * - IE4 (and below) - try..catch and for..in not introduced until IE5
+ */
+
+// Guard against loading the script twice
+var Piwik;
+try {
+ if (Piwik.getTracker) { }
+} catch (e) {
+ // Piwik singleton and namespace
+ Piwik = (function () {
+ /************************************************************
+ * Private data
+ ************************************************************/
+
+ var expireDateTime,
+
+ /* plugins */
+ plugins = {},
+
+ /* alias frequently used globals for added minification */
+ documentAlias = document,
+ navigatorAlias = navigator,
+ screenAlias = screen,
+ windowAlias = window,
+
+ /* DOM Ready */
+ hasLoaded = false,
+ registeredOnLoadHandlers = [];
+
+ /************************************************************
+ * Private methods
+ ************************************************************/
+
+ /*
+ * Is property (or variable) defined?
+ */
+ function isDefined(property) {
+ return typeof property !== 'undefined';
+ }
+
+ /*
+ * Cross-browser helper function to add event handler
+ */
+ function addEventListener(element, eventType, eventHandler, useCapture) {
+ if (element.addEventListener) {
+ element.addEventListener(eventType, eventHandler, useCapture);
+ return true;
+ } else 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];
+ if (typeof pluginMethod === 'function') {
+ result += pluginMethod(callback);
+ }
+ }
+
+ return result;
+ }
+
+ /*
+ * Handle beforeunload event
+ */
+ function beforeUnloadHandler(unloadEvent /* not used */) {
+ /*
+ * Delay/pause (blocks UI)
+ */
+ if (isDefined(expireDateTime)) {
+ var now = new Date();
+
+ while (now.getTime() < expireDateTime) {
+ now = new Date();
+ }
+ }
+
+ executePluginMethod('unload');
+ }
+
+ /*
+ * Handler for onload event
+ */
+ function loadHandler(loadEvent /* unused */) {
+ if (!hasLoaded) {
+ hasLoaded = true;
+ executePluginMethod('load');
+ for (var i = 0; i < registeredOnLoadHandlers.length; i++) {
+ registeredOnLoadHandlers[i]();
+ }
+ }
+ return true;
+ }
+
+ /*
+ * Add onload or DOM ready handler
+ */
+ function addReadyListener() {
+ if (documentAlias.addEventListener) {
+ addEventListener(documentAlias, "DOMContentLoaded", function () {
+ documentAlias.removeEventListener("DOMContentLoaded", arguments.callee, false);
+ loadHandler();
+ });
+ } else if (documentAlias.attachEvent) {
+ documentAlias.attachEvent("onreadystatechange", function () {
+ if (documentAlias.readyState === "complete") {
+ documentAlias.detachEvent("onreadystatechange", arguments.callee);
+ loadHandler();
+ }
+ });
+
+ if (documentAlias.documentElement.doScroll && windowAlias == windowAlias.top) {
+ (function () {
+ if (hasLoaded) {
+ return;
+ }
+ try {
+ documentAlias.documentElement.doScroll("left");
+ } catch (error) {
+ setTimeout(arguments.callee, 0);
+ return;
+ }
+ loadHandler();
+ }());
+ }
+ }
+ // fallback
+ addEventListener(windowAlias, 'load', loadHandler, false);
+ }
+
+ /*
+ * 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 // Tracker URL
+ configTrackerUrl = trackerUrl || '',
+
+ // Site ID
+ configTrackerSiteId = siteId || '',
+
+ // Document title
+ configTitle = documentAlias.title || '',
+
+ // Extensions to be treated as download links
+ configDownloadExtensions = '7z|aac|arc|arj|asf|asx|avi|bin|csv|doc|exe|flv|gif|gz|gzip|hqx|jar|jpe?g|js|mp(2|3|4|e?g)|mov(ie)?|msi|msp|pdf|phps|png|ppt|qtm?|ra(m|r)?|sea|sit|tar|tgz|torrent|txt|wav|wma|wmv|wpd||xls|xml|z|zip',
+
+ // Hosts or alias(es) to not treat as outlinks
+ configHostsAlias = [windowAlias.location.hostname],
+
+ // Anchor classes to not track
+ configIgnoreClasses = [],
+
+ // Download class name
+ configDownloadClass = 'piwik_download',
+
+ // (Out) Link class name
+ configLinkClass = 'piwik_link',
+
+ // Maximum delay to wait for web bug image to be fetched (in milliseconds)
+ configTrackerPause = 500,
+
+ // Custom data
+ configCustomData,
+
+ // Client-side data collection
+ browserHasCookies = '0',
+ pageReferrer,
+
+ // Plugin, Parameter name, MIME type, ActiveX progid, detected, version
+ pluginMap = {
+ director: ['dir', 'application/x-director',
+ ['SWCtl.SWctl.1'],
+ '0', ''],
+ flash: ['fla', 'application/x-shockwave-flash',
+ ['ShockwaveFlash.ShockwaveFlash.1'],
+ '0', ''],
+ pdf: ['pdf', 'application/pdf',
+ ['AcroPDF.PDF.1', 'PDF.PdfCtrl.6', 'PDF.PdfCtrl.5', 'PDF.PdfCtrl.1'],
+ '0', ''],
+ realplayer: ['realp', 'audio/x-pn-realaudio-plugin',
+ ['rmocx.RealPlayer G2 Control.1', 'RealPlayer.RealPlayer(tm) ActiveX Control (32-bit)'],
+ '0', ''],
+ wma: ['wma', 'application/x-mplayer2',
+ ['WMPlayer.OCX', 'MediaPlayer.MediaPlayer.1'],
+ '0', '']
+ },
+
+ // Guard against installing the link tracker more than once per Tracker instance
+ linkTrackingInstalled = false,
+
+ /*
+ * encode or escape
+ * - encodeURIComponent added in IE5.5
+ */
+ escapeWrapper = windowAlias.encodeURIComponent || escape,
+
+ /*
+ * decode or unescape
+ * - decodeURIComponent added in IE5.5
+ */
+ unescapeWrapper = windowAlias.decodeURIComponent || unescape,
+
+ /*
+ * stringify
+ * - based on public domain JSON implementation at http://www.json.org/json2.js (2009-04-16)
+ */
+ stringify = function (value) {
+
+ var escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+ // table of character substitutions
+ meta = {'\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"' : '\\"', '\\': '\\\\'};
+
+ // 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.
+ function quote(string) {
+ 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 f(n) {
+ return n < 10 ? '0' + n : n;
+ }
+
+ // Produce a string from holder[key].
+ function str(key, holder) {
+ var i, // The loop counter.
+ k, // The member key.
+ v, // The member value.
+ partial,
+ value = holder[key];
+
+ if (value === null) {
+ return 'null';
+ }
+
+ // 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);
+ }
+
+ // 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);
+
+ case 'object':
+ // Make an array to hold the partial results of stringifying this object value.
+ partial = [];
+
+ // Is the value an array?
+// if (Object.prototype.toString.call(value)=="[object Array]") { // call added in IE5.5
+ if (value instanceof Array) {
+ // The value is an array. Stringify every element. Use null as a placeholder
+ // for non-JSON values.
+ for (i = 0; i < value.length; i++) {
+ partial[i] = str(i, value) || 'null';
+ }
+
+ // Join all of the elements together, separated with commas, and wrap them in
+ // brackets.
+ v = partial.length === 0 ? '[]' : '[' + partial.join(',') + ']';
+ return v;
+ }
+
+// if (Object.prototype.toString.call(value)=="[object Date]") { // call added in IE5.5
+ if (value instanceof Date) {
+ return quote(value.getUTCFullYear() + '-' +
+ f(value.getUTCMonth() + 1) + '-' +
+ f(value.getUTCDate()) + 'T' +
+ f(value.getUTCHours()) + ':' +
+ f(value.getUTCMinutes()) + ':' +
+ f(value.getUTCSeconds()) + 'Z');
+ }
+
+ // Otherwise, iterate through all of the keys in the object.
+ for (k in value) {
+ v = str(k, value);
+ if (v) {
+ // partial.push(quote(k) + ':' + v); // array.push added in IE5.5
+ partial[partial.length] = quote(k) + ':' + v;
+ }
+ }
+
+ // Join all of the member texts together, separated with commas,
+ // and wrap them in braces.
+ v = partial.length === 0 ? '{}' : '{' + partial.join(',') + '}';
+ return v;
+ }
+ }
+
+ return str('', {'': value});
+ },
+
+ /*
+ * registered (user-defined) hooks
+ */
+ registeredHooks = {};
+
+ /*
+ * Platform test for Internet Explorer on Windows
+ */
+ function isWindowsIE() {
+ var agent = navigatorAlias.userAgent.toLowerCase();
+
+ return (agent.indexOf('msie') != -1) && (agent.indexOf('opera') == -1) &&
+ ((agent.indexOf('win') != -1) || (agent.indexOf('32bit') != -1));
+ }
+
+ /*
+ * Set cookie value
+ */
+ function setCookie(cookieName, value, daysToExpire, path, domain, secure) {
+ var expiryDate;
+
+ if (daysToExpire) {
+ // time is in milliseconds
+ expiryDate = new Date();
+ // there are 1000 * 60 * 60 * 24 milliseconds in a day (i.e., 86400000 or 8.64e7)
+ expiryDate.setTime(expiryDate.getTime() + daysToExpire * 8.64e7);
+ }
+
+ documentAlias.cookie = cookieName + '=' + escapeWrapper(value) +
+ (daysToExpire ? ';expires=' + expiryDate.toGMTString() : '') +
+ ';path=' + (path ? path : '/') +
+ (domain ? ';domain=' + domain : '') +
+ (secure ? ';secure' : '');
+ }
+
+ /*
+ * Get cookie value
+ */
+ function getCookie(cookieName, path, domain) {
+ var cookiePattern = new RegExp('(^|;)[ ]*' + cookieName + '=([^;]*)' +
+ (path ? '(;[ ]*expires=[^;]*)?;[ ]*path=' + path.replace('/', '\\/') + '' : '') +
+ (domain ? ';[ ]*domain=' + domain + '(;|$)' : '')),
+
+ cookieMatch = cookiePattern.exec(documentAlias.cookie);
+
+ return cookieMatch ? unescapeWrapper(cookieMatch[2]) : 0;
+ }
+
+ /*
+ * Discard cookie
+ */
+/* // NOT CURRENTLY USED
+ function dropCookie(cookieName, path, domain) {
+ // browser may not delete cookie until browser closed (session ends)
+ if (getCookie(cookieName)) {
+ // clear value, set expires in the past
+ setCookie(cookieName, '', -1, path, domain);
+ }
+ }
+*/
+
+ /*
+ * Send image request to Piwik server using GET.
+ */
+ function getImage(url, delay) {
+ var now = new Date(),
+ image = new Image(1, 1);
+
+ expireDateTime = now.getTime() + delay;
+
+ image.onLoad = function () { };
+ image.src = url;
+ }
+
+ /*
+ * Browser plugin tests
+ */
+ function detectBrowserPlugins() {
+ var i, mimeTypes = '';
+
+ function setPluginInfo(pluginName, mimeType) {
+ if (mimeTypes.indexOf(mimeType) != -1 && navigatorAlias.mimeTypes[mimeType].enabledPlugin !== null) {
+ pluginMap[pluginName][3] = '1';
+ }
+ }
+
+ /*
+ * Note: an ActiveXObject object has no intrinsic properties or methods;
+ * thus, no standard means of getting a plugin's version number
+ */
+ function setIEPluginInfo(pluginName, progids) {
+ if (progids !== null && isDefined(windowAlias.ActiveXObject)) {
+ for (var j = 0; j < progids.length; j++) {
+ try {
+ if (new ActiveXObject(progids[j])) {
+ pluginMap[pluginName][3] = '1';
+ break;
+ }
+ } catch (e) { }
+ }
+ }
+ }
+
+ // Begin detection of browser plugins
+ if (isWindowsIE()) {
+ for (i in pluginMap) {
+ setIEPluginInfo(i, pluginMap[i][2]);
+ }
+ } else {
+ for (i = 0; i < navigatorAlias.mimeTypes.length; i++) {
+ mimeTypes += navigatorAlias.mimeTypes[i].type.toLowerCase();
+ }
+
+ for (i in pluginMap) {
+ setPluginInfo(i, pluginMap[i][1]);
+ }
+ }
+ }
+
+ /*
+ * 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;
+ }
+
+ /*
+ * Does browser have cookies enabled (for this site)?
+ */
+ function hasCookies() {
+ var testCookieName = '_pk_testcookie';
+ if (!isDefined(navigatorAlias.cookieEnabled)) {
+ setCookie(testCookieName, '1');
+ return getCookie(testCookieName) == '1' ? '1' : '0';
+ }
+
+ return navigatorAlias.cookieEnabled ? '1' : '0';
+ }
+
+
+ /*
+ * Get the web bug image (transparent single pixel, 1x1, image) to log visit in Piwik
+ */
+ function getWebBug() {
+ var i, customDataString, pluginString, extraString, now, request;
+
+ /*
+ * encode custom vars
+ */
+ customDataString = '';
+ if (isDefined(configCustomData)) {
+ customDataString = '&data=' + escapeWrapper(stringify(configCustomData));
+ }
+
+ /*
+ * encode plugin data
+ */
+ pluginString = '';
+ for (i in pluginMap) {
+ pluginString += '&' + pluginMap[i][0] + '=' + pluginMap[i][3];
+ }
+
+ extraString = executePluginMethod('log');
+
+ now = new Date();
+ request = configTrackerUrl + '?idsite=' + configTrackerSiteId +
+ '&url=' + escapeWrapper(documentAlias.location.href) +
+ '&action_name=' + escapeWrapper(configTitle) + // refs #530
+ '&res=' + screenAlias.width + 'x' + screenAlias.height +
+ '&h=' + now.getHours() + '&m=' + now.getMinutes() + '&s=' + now.getSeconds() +
+ '&cookie=' + browserHasCookies +
+ '&urlref=' + escapeWrapper(pageReferrer) +
+ pluginString + customDataString + extraString;
+
+ getImage(request, configTrackerPause);
+ }
+
+ /*
+ * Log the click with the server
+ */
+ function logClick(url, linkType, customData) {
+ var customDataString, extraString, request;
+
+ /*
+ * encode custom data
+ */
+ customDataString = '';
+ if (isDefined(customData)) {
+ customDataString = '&data=' + escapeWrapper(stringify(configCustomData));
+ }
+
+ extraString = executePluginMethod('click');
+
+ request = configTrackerUrl + '?idsite=' + configTrackerSiteId +
+ '&' + linkType + '=' + escapeWrapper(url) +
+ '&rand=' + Math.random() +
+ '&redirect=0' +
+ customDataString + extraString;
+
+ getImage(request, configTrackerPause);
+ }
+
+ /*
+ * 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];
+
+ if (hostName == alias) {
+ return true;
+ }
+
+ if (alias.substr(0, 2) == '*.') {
+ if ((hostName) == alias.substr(2)) {
+ return true;
+ }
+
+ offset = hostName.length - alias.length + 1;
+ if ((offset > 0) && (hostName.substr(offset) == alias.substr(1))) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /*
+ * Link or Download?
+ */
+ function getLinkType(className, href, isInLink) {
+ // outlinks
+ if (!isInLink) {
+ return 'link';
+ }
+
+ // does class indicate whether it is an (explicit/forced) outlink or a download?
+ var downloadOrLinkPattern = new RegExp('(^| )(' + configDownloadClass + '|' + configLinkClass + ')( |$)'),
+ match = downloadOrLinkPattern.exec(className),
+
+ // does file extension indicate that it is a download?
+ downloadExtensionsPattern = new RegExp('\\.(' + configDownloadExtensions + ')$', 'i');
+
+ // optimization of the nested if..elseif..else construct below
+ return match ? (match[2] == configDownloadClass ? 'download' : 'link') :
+ (downloadExtensionsPattern.test(href) ? 'download' : 0);
+
+/*
+ var linkType;
+
+ if (match) {
+ if (match[2] == configDownloadClass) {
+ // class attribute contains 'piwik_download' (or user's override)
+ linkType = 'download';
+ } else {
+ // class attribute contains 'piwik_link' (or user's override)
+ linkType = 'link';
+ }
+ } else if (downloadExtensionsPattern.test(sourceHref)) {
+ // file extension matches a defined download extension
+ linkType = 'download';
+ } else {
+ // otherwise none of the above
+ linkType = 0;
+ }
+
+ return linkType;
+ */
+ }
+
+ /*
+ * Handle click event
+ */
+ function clickHandler(clickEvent) {
+ var sourceElement, tag, linkType;
+
+ if (!isDefined(clickEvent)) {
+ clickEvent = windowAlias.event;
+ }
+
+ if (isDefined(clickEvent.target)) {
+ sourceElement = clickEvent.target;
+ } else if (isDefined(clickEvent.srcElement)) {
+ sourceElement = clickEvent.srcElement;
+ } else {
+ return true;
+ }
+
+ while ((tag = sourceElement.tagName) != 'A' && tag != 'AREA') {
+ sourceElement = sourceElement.parentNode;
+ }
+
+ if (isDefined(sourceElement.href)) {
+ // browsers, such as Safari, don't downcase hostname and href
+ var originalSourceHostName = sourceElement.hostname,
+ sourceHostName = originalSourceHostName.toLowerCase(),
+ sourceHref = sourceElement.href.replace(originalSourceHostName, sourceHostName),
+ scriptProtocol = /^(javascript|vbscript|jscript|mocha|livescript|ecmascript):/i;
+
+ // ignore script pseudo-protocol links
+ if (!scriptProtocol.test(sourceHref)) {
+ // track outlinks and all downloads
+ linkType = getLinkType(sourceElement.className, sourceHref, isSiteHostName(sourceHostName));
+ if (linkType) {
+ logClick(sourceHref, linkType);
+ }
+ }
+ }
+
+ // Returns true so href isn't cancelled
+ return true;
+ }
+
+ /*
+ * Construct regular expression of classes to be ignored
+ */
+ function getIgnoreRegExp() {
+ var i, ignoreRegExp = '(^| )(piwik_ignore';
+
+ if (isDefined(configIgnoreClasses)) {
+ for (i = 0; i < configIgnoreClasses.length; i++) {
+ ignoreRegExp += '|' + configIgnoreClasses[i];
+ }
+ }
+ ignoreRegExp += ')( |$)';
+
+ return new RegExp(ignoreRegExp);
+ }
+
+ /*
+ * Add click listener to a DOM element
+ */
+ function addClickListener(element) {
+ addEventListener(element, 'click', clickHandler, false);
+ }
+
+ /*
+ * Add click handlers to anchor and AREA elements, except those to be ignored
+ */
+ function addClickListeners() {
+ if (!linkTrackingInstalled) {
+ linkTrackingInstalled = true;
+
+ // iterate through anchor elements with href and AREA elements
+
+ var i, ignorePattern = getIgnoreRegExp(), linkElements = documentAlias.links;
+
+ if (linkElements) {
+ for (i = 0; i < linkElements.length; i++) {
+ if (!ignorePattern.test(linkElements[i].className)) {
+ addClickListener(linkElements[i]);
+ }
+ }
+ }
+ }
+ }
+
+ /*
+ * Register a user-defined hook
+ * - if userHook is a string, object construction is deferred,
+ * permitting the addition of privileged members
+ */
+ function registerHook(hookName, userHook) {
+ var hookObj = null;
+
+ if (typeof hookName == 'string' && !isDefined(registeredHooks[hookName])) {
+ if (typeof userHook == 'object') {
+ hookObj = userHook;
+ } else if (typeof userHook == 'string') {
+ try {
+ eval('hookObj =' + userHook);
+ } catch (e) { }
+ }
+
+ registeredHooks[hookName] = hookObj;
+ }
+ return hookObj;
+ }
+
+ /************************************************************
+ * Constructor
+ ************************************************************/
+
+ /*
+ * initialize tracker
+ */
+ pageReferrer = getReferrer();
+ browserHasCookies = hasCookies();
+ detectBrowserPlugins();
+
+ /*
+ * initialize plugins
+ */
+ executePluginMethod('run', registerHook);
+
+ /************************************************************
+ * Public data and methods
+ ************************************************************/
+
+ return {
+ /*
+ * Hook accessors
+ */
+ hook: registeredHooks,
+ getHook: function (hookName) {
+ return registeredHooks[hookName];
+ },
+
+ /*
+ * Specify the Piwik server URL
+ */
+ setTrackerUrl: function (trackerUrl) {
+ if (isDefined(trackerUrl)) {
+ configTrackerUrl = trackerUrl;
+ }
+ },
+
+ /*
+ * Specify the site ID
+ */
+ setSiteId: function (siteId) {
+ if (isDefined(siteId)) {
+ configTrackerSiteId = siteId;
+ }
+ },
+
+ /*
+ * Pass custom data to the server
+ */
+ setCustomData: function (customData) {
+ if (isDefined(customData)) {
+ configCustomData = customData;
+ }
+ },
+
+ /*
+ * Set delay for link tracking (in milliseconds)
+ */
+ setLinkTrackingTimer: function (delay) {
+ if (isDefined(delay)) {
+ configTrackerPause = delay;
+ }
+ },
+
+ /*
+ * Set list of file extensions to be recognized as downloads
+ */
+ setDownloadExtensions: function (extensions) {
+ if (isDefined(extensions)) {
+ configDownloadExtensions = extensions;
+ }
+ },
+
+ /*
+ * Specify additional file extensions to be recognized as downloads
+ */
+ addDownloadExtensions: function (extensions) {
+ if (isDefined(extensions)) {
+ configDownloadExtensions += '|' + extensions;
+ }
+ },
+
+ /*
+ * Set array of domains to be treated as local
+ */
+ setDomains: function (hostsAlias) {
+ if (typeof hostsAlias == 'object' && hostsAlias instanceof Array) {
+ configHostsAlias = hostsAlias;
+ // configHostAlias.push(windowAlias.location.hostname); // array.push added in IE5.5
+ configHostsAlias[configHostsAlias.length] = windowAlias.location.hostname;
+ } else if (typeof hostsAlias == 'string') {
+ configHostsAlias = [hostsAlias, windowAlias.location.hostname];
+ }
+ },
+
+ /*
+ * Set array of classes to be ignored if present in link
+ */
+ setIgnoreClasses: function (ignoreClasses) {
+ if (typeof ignoreClasses == 'object' && ignoreClasses instanceof Array) {
+ configIgnoreClasses = ignoreClasses;
+ } else if (typeof ignoreClasses == 'string') {
+ configIgnoreClasses = [ignoreClasses];
+ }
+ },
+
+ /*
+ * Override document.title
+ */
+ setDocumentTitle: function (title) {
+ if (isDefined(title)) {
+ configTitle = title;
+ }
+ },
+
+ /*
+ * Set download class name (i.e., override default: piwik_download)
+ */
+ setDownloadClass: function (className) {
+ if (typeof className == 'string' && className.length > 0) {
+ configDownloadClass = className;
+ }
+ },
+
+ /*
+ * Set outlink class name (i.e., override default: piwik_link)
+ */
+ setLinkClass: function (className) {
+ if (typeof className == 'string' && className.length > 0) {
+ configLinkClass = className;
+ }
+ },
+
+ /*
+ * Add click listener to a specific link element.
+ * When clicked, Piwik will log the click automatically.
+ */
+ addListener: function (element) {
+ if (isDefined(element)) {
+ addClickListener(element);
+ }
+ },
+
+ /*
+ * Install link tracker
+ */
+ enableLinkTracking: function () {
+ if (hasLoaded) {
+ // the load event has already fired, add the click listeners now
+ addClickListeners();
+ } else {
+ // defer until page has loaded
+ registeredOnLoadHandlers[registeredOnLoadHandlers.length] = function () {
+ addClickListeners();
+ };
+ }
+ },
+
+ /*
+ * Manually log a click from your own code.
+ */
+ trackLink: function (sourceUrl, linkType, customData) {
+ logClick(sourceUrl, linkType, customData);
+ },
+
+ /*
+ * Log visit to this page
+ */
+ trackPageView: function () {
+ getWebBug();
+ }
+ };
+ }
+
+ /************************************************************
+ * Constructor
+ ************************************************************/
+
+ // initialize the Piwik singleton
+ addEventListener(windowAlias, 'beforeunload', beforeUnloadHandler, false);
+ addReadyListener();
+
+ /************************************************************
+ * Public data and methods
+ ************************************************************/
+
+ return {
+ /*
+ * Add plugin
+ */
+ addPlugin: function (pluginName, pluginObj) {
+ plugins[pluginName] = pluginObj;
+ },
+
+ /*
+ * Get Tracker
+ */
+ getTracker: function (piwikUrl, siteId) {
+ return new Tracker(piwikUrl, siteId);
+ }
+ };
+ }());
+
+ /************************************************************
+ * Deprecated functionality below
+ * - for legacy piwik.js compatibility
+ ************************************************************/
+
+ /*
+ * Piwik globals
+ *
+ * var piwik_install_tracker, piwik_tracker_pause, piwik_download_extensions, piwik_hosts_alias, piwik_ignore_classes;
+ */
+
+ /*
+ * Track click manually (function is defined below)
+ */
+ var piwik_track;
+
+ /*
+ * Track page visit
+ */
+ function piwik_log(documentTitle, siteId, piwikUrl, customData) {
+
+ function getOption(optionName) {
+ try {
+ return eval('piwik_' + optionName);
+ } catch (e) { }
+
+ return; /* undefined */
+ }
+
+ // instantiate the tracker
+ var piwikTracker = Piwik.getTracker(piwikUrl, siteId);
+
+ // initializer tracker
+ piwikTracker.setDocumentTitle(documentTitle);
+ piwikTracker.setCustomData(customData);
+
+ // handle Piwik globals
+ piwikTracker.setLinkTrackingTimer(getOption('tracker_pause'));
+ piwikTracker.setDownloadExtensions(getOption('download_extensions'));
+ piwikTracker.setDomains(getOption('hosts_alias'));
+ piwikTracker.setIgnoreClasses(getOption('ignore_classes'));
+
+ // track this page view
+ piwikTracker.trackPageView();
+
+ // default is to install the link tracker
+ if (getOption('install_tracker') !== false) {
+
+ // set-up click handler
+ piwik_track = function (sourceUrl, siteId, piwikUrl, linkType) {
+ piwikTracker.setSiteId(siteId);
+ piwikTracker.setTrackerUrl(piwikUrl);
+ piwikTracker.trackLink(sourceUrl, linkType);
+ };
+
+ // set-up link tracking
+ piwikTracker.enableLinkTracking();
+ }
+ }
+}