-
-
Notifications
You must be signed in to change notification settings - Fork 167
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Create Matomo module; leave Piwik to be deprecated
With the rebranding of Piwik to Matomo, this commit: * copies the piwik module to matomo and rebrands * notes that the piwik module is deprecated * updates the javascript to the current Matomo version Implements #132
- Loading branch information
Showing
7 changed files
with
447 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
""" | ||
Matomo template tags and filters. | ||
""" | ||
|
||
from __future__ import absolute_import | ||
|
||
from collections import namedtuple | ||
from itertools import chain | ||
import re | ||
|
||
from django.conf import settings | ||
from django.template import Library, Node, TemplateSyntaxError | ||
|
||
from analytical.utils import (is_internal_ip, disable_html, | ||
get_required_setting, get_identity) | ||
|
||
|
||
# domain name (characters separated by a dot), optional port, optional URI path, no slash | ||
DOMAINPATH_RE = re.compile(r'^(([^./?#@:]+\.)*[^./?#@:]+)+(:[0-9]+)?(/[^/?#@:]+)*$') | ||
|
||
# numeric ID | ||
SITEID_RE = re.compile(r'^\d+$') | ||
|
||
TRACKING_CODE = """ | ||
<script type="text/javascript"> | ||
var _paq = window._paq || []; | ||
%(variables)s | ||
%(commands)s | ||
_paq.push(['trackPageView']); | ||
_paq.push(['enableLinkTracking']); | ||
(function() { | ||
var u="//%(url)s/"; | ||
_paq.push(['setTrackerUrl', u+'matomo.php']); | ||
_paq.push(['setSiteId', %(siteid)s]); | ||
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0]; | ||
g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s); | ||
})(); | ||
</script> | ||
<noscript><p><img src="//%(url)s/piwik.php?idsite=%(siteid)s" style="border:0;" alt="" /></p></noscript> | ||
""" # noqa | ||
|
||
VARIABLE_CODE = '_paq.push(["setCustomVariable", %(index)s, "%(name)s", "%(value)s", "%(scope)s"]);' # noqa | ||
IDENTITY_CODE = '_paq.push(["setUserId", "%(userid)s"]);' | ||
DISABLE_COOKIES_CODE = '_paq.push([\'disableCookies\']);' | ||
|
||
DEFAULT_SCOPE = 'page' | ||
|
||
MatomoVar = namedtuple('MatomoVar', ('index', 'name', 'value', 'scope')) | ||
|
||
|
||
register = Library() | ||
|
||
|
||
@register.tag | ||
def matomo(parser, token): | ||
""" | ||
Matomo tracking template tag. | ||
Renders Javascript code to track page visits. You must supply | ||
your Matomo domain (plus optional URI path), and tracked site ID | ||
in the ``MATOMO_DOMAIN_PATH`` and the ``MATOMO_SITE_ID`` setting. | ||
Custom variables can be passed in the ``matomo_vars`` context | ||
variable. It is an iterable of custom variables as tuples like: | ||
``(index, name, value[, scope])`` where scope may be ``'page'`` | ||
(default) or ``'visit'``. Index should be an integer and the | ||
other parameters should be strings. | ||
""" | ||
bits = token.split_contents() | ||
if len(bits) > 1: | ||
raise TemplateSyntaxError("'%s' takes no arguments" % bits[0]) | ||
return MatomoNode() | ||
|
||
|
||
class MatomoNode(Node): | ||
def __init__(self): | ||
self.domain_path = \ | ||
get_required_setting('MATOMO_DOMAIN_PATH', DOMAINPATH_RE, | ||
"must be a domain name, optionally followed " | ||
"by an URI path, no trailing slash (e.g. " | ||
"matomo.example.com or my.matomo.server/path)") | ||
self.site_id = \ | ||
get_required_setting('MATOMO_SITE_ID', SITEID_RE, | ||
"must be a (string containing a) number") | ||
|
||
def render(self, context): | ||
custom_variables = context.get('matomo_vars', ()) | ||
|
||
complete_variables = (var if len(var) >= 4 else var + (DEFAULT_SCOPE,) | ||
for var in custom_variables) | ||
|
||
variables_code = (VARIABLE_CODE % MatomoVar(*var)._asdict() | ||
for var in complete_variables) | ||
|
||
commands = [] | ||
if getattr(settings, 'MATOMO_DISABLE_COOKIES', False): | ||
commands.append(DISABLE_COOKIES_CODE) | ||
|
||
userid = get_identity(context, 'matomo') | ||
if userid is not None: | ||
variables_code = chain(variables_code, ( | ||
IDENTITY_CODE % {'userid': userid}, | ||
)) | ||
|
||
html = TRACKING_CODE % { | ||
'url': self.domain_path, | ||
'siteid': self.site_id, | ||
'variables': '\n '.join(variables_code), | ||
'commands': '\n '.join(commands) | ||
} | ||
if is_internal_ip(context, 'MATOMO'): | ||
html = disable_html(html, 'Matomo') | ||
return html | ||
|
||
|
||
def contribute_to_analytical(add_node): | ||
MatomoNode() # ensure properly configured | ||
add_node('body_bottom', MatomoNode) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
""" | ||
Tests for the Matomo template tags and filters. | ||
""" | ||
|
||
from django.contrib.auth.models import User | ||
from django.http import HttpRequest | ||
from django.template import Context | ||
from django.test.utils import override_settings | ||
|
||
from analytical.templatetags.matomo import MatomoNode | ||
from analytical.tests.utils import TagTestCase | ||
from analytical.utils import AnalyticalException | ||
|
||
|
||
@override_settings(MATOMO_DOMAIN_PATH='example.com', MATOMO_SITE_ID='345') | ||
class MatomoTagTestCase(TagTestCase): | ||
""" | ||
Tests for the ``matomo`` template tag. | ||
""" | ||
|
||
def test_tag(self): | ||
r = self.render_tag('matomo', 'matomo') | ||
self.assertTrue('"//example.com/"' in r, r) | ||
self.assertTrue("_paq.push(['setSiteId', 345]);" in r, r) | ||
self.assertTrue('img src="//example.com/matomo.php?idsite=345"' | ||
in r, r) | ||
|
||
def test_node(self): | ||
r = MatomoNode().render(Context({})) | ||
self.assertTrue('"//example.com/";' in r, r) | ||
self.assertTrue("_paq.push(['setSiteId', 345]);" in r, r) | ||
self.assertTrue('img src="//example.com/matomo.php?idsite=345"' | ||
in r, r) | ||
|
||
@override_settings(MATOMO_DOMAIN_PATH='example.com/matomo', | ||
MATOMO_SITE_ID='345') | ||
def test_domain_path_valid(self): | ||
r = self.render_tag('matomo', 'matomo') | ||
self.assertTrue('"//example.com/matomo/"' in r, r) | ||
|
||
@override_settings(MATOMO_DOMAIN_PATH='example.com:1234', | ||
MATOMO_SITE_ID='345') | ||
def test_domain_port_valid(self): | ||
r = self.render_tag('matomo', 'matomo') | ||
self.assertTrue('"//example.com:1234/";' in r, r) | ||
|
||
@override_settings(MATOMO_DOMAIN_PATH='example.com:1234/matomo', | ||
MATOMO_SITE_ID='345') | ||
def test_domain_port_path_valid(self): | ||
r = self.render_tag('matomo', 'matomo') | ||
self.assertTrue('"//example.com:1234/matomo/"' in r, r) | ||
|
||
@override_settings(MATOMO_DOMAIN_PATH=None) | ||
def test_no_domain(self): | ||
self.assertRaises(AnalyticalException, MatomoNode) | ||
|
||
@override_settings(MATOMO_SITE_ID=None) | ||
def test_no_siteid(self): | ||
self.assertRaises(AnalyticalException, MatomoNode) | ||
|
||
@override_settings(MATOMO_SITE_ID='x') | ||
def test_siteid_not_a_number(self): | ||
self.assertRaises(AnalyticalException, MatomoNode) | ||
|
||
@override_settings(MATOMO_DOMAIN_PATH='http://www.example.com') | ||
def test_domain_protocol_invalid(self): | ||
self.assertRaises(AnalyticalException, MatomoNode) | ||
|
||
@override_settings(MATOMO_DOMAIN_PATH='example.com/') | ||
def test_domain_slash_invalid(self): | ||
self.assertRaises(AnalyticalException, MatomoNode) | ||
|
||
@override_settings(MATOMO_DOMAIN_PATH='example.com:123:456') | ||
def test_domain_multi_port(self): | ||
self.assertRaises(AnalyticalException, MatomoNode) | ||
|
||
@override_settings(MATOMO_DOMAIN_PATH='example.com:') | ||
def test_domain_incomplete_port(self): | ||
self.assertRaises(AnalyticalException, MatomoNode) | ||
|
||
@override_settings(MATOMO_DOMAIN_PATH='example.com:/matomo') | ||
def test_domain_uri_incomplete_port(self): | ||
self.assertRaises(AnalyticalException, MatomoNode) | ||
|
||
@override_settings(MATOMO_DOMAIN_PATH='example.com:12df') | ||
def test_domain_port_invalid(self): | ||
self.assertRaises(AnalyticalException, MatomoNode) | ||
|
||
@override_settings(ANALYTICAL_INTERNAL_IPS=['1.1.1.1']) | ||
def test_render_internal_ip(self): | ||
req = HttpRequest() | ||
req.META['REMOTE_ADDR'] = '1.1.1.1' | ||
context = Context({'request': req}) | ||
r = MatomoNode().render(context) | ||
self.assertTrue(r.startswith( | ||
'<!-- Matomo disabled on internal IP address'), r) | ||
self.assertTrue(r.endswith('-->'), r) | ||
|
||
def test_uservars(self): | ||
context = Context({'matomo_vars': [(1, 'foo', 'foo_val'), | ||
(2, 'bar', 'bar_val', 'page'), | ||
(3, 'spam', 'spam_val', 'visit')]}) | ||
r = MatomoNode().render(context) | ||
msg = 'Incorrect Matomo custom variable rendering. Expected:\n%s\nIn:\n%s' | ||
for var_code in ['_paq.push(["setCustomVariable", 1, "foo", "foo_val", "page"]);', | ||
'_paq.push(["setCustomVariable", 2, "bar", "bar_val", "page"]);', | ||
'_paq.push(["setCustomVariable", 3, "spam", "spam_val", "visit"]);']: | ||
self.assertIn(var_code, r, msg % (var_code, r)) | ||
|
||
@override_settings(ANALYTICAL_AUTO_IDENTIFY=True) | ||
def test_default_usertrack(self): | ||
context = Context({ | ||
'user': User(username='BDFL', first_name='Guido', last_name='van Rossum') | ||
}) | ||
r = MatomoNode().render(context) | ||
msg = 'Incorrect Matomo user tracking rendering.\nNot found:\n%s\nIn:\n%s' | ||
var_code = '_paq.push(["setUserId", "BDFL"]);' | ||
self.assertIn(var_code, r, msg % (var_code, r)) | ||
|
||
def test_matomo_usertrack(self): | ||
context = Context({ | ||
'matomo_identity': 'BDFL' | ||
}) | ||
r = MatomoNode().render(context) | ||
msg = 'Incorrect Matomo user tracking rendering.\nNot found:\n%s\nIn:\n%s' | ||
var_code = '_paq.push(["setUserId", "BDFL"]);' | ||
self.assertIn(var_code, r, msg % (var_code, r)) | ||
|
||
def test_analytical_usertrack(self): | ||
context = Context({ | ||
'analytical_identity': 'BDFL' | ||
}) | ||
r = MatomoNode().render(context) | ||
msg = 'Incorrect Matomo user tracking rendering.\nNot found:\n%s\nIn:\n%s' | ||
var_code = '_paq.push(["setUserId", "BDFL"]);' | ||
self.assertIn(var_code, r, msg % (var_code, r)) | ||
|
||
@override_settings(ANALYTICAL_AUTO_IDENTIFY=True) | ||
def test_disable_usertrack(self): | ||
context = Context({ | ||
'user': User(username='BDFL', first_name='Guido', last_name='van Rossum'), | ||
'matomo_identity': None | ||
}) | ||
r = MatomoNode().render(context) | ||
msg = 'Incorrect Matomo user tracking rendering.\nFound:\n%s\nIn:\n%s' | ||
var_code = '_paq.push(["setUserId", "BDFL"]);' | ||
self.assertNotIn(var_code, r, msg % (var_code, r)) | ||
|
||
@override_settings(MATOMO_DISABLE_COOKIES=True) | ||
def test_disable_cookies(self): | ||
r = MatomoNode().render(Context({})) | ||
self.assertTrue("_paq.push(['disableCookies']);" in r, r) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.