Added conditional tracking consent for Matomo

Modified utils.build_paq_cmd to remove the trailing comma and white space as well as add a semi colon to the end of the push command.
Added context_providers directory with matomo.py file with a context provider that builds the tracking consent code and adds it to the context.
Modified matomo.MatomoNode.render. Removed original ugly settings check code with simply rendering the 'consent_script' context variable and adding it to the html variable only if its length is greater than 1.
NOTE: The context provider and rendering it (line 110 templatetags/matomo.py) are untested. Both utils.build_paq_cmd and utils.get_event_bind_js are tested but need need more rigorous testing to be sure it won't break.
This commit is contained in:
SilverStrings024 2021-07-13 00:35:26 -04:00
parent 3a514444f3
commit d1d1be189a
3 changed files with 54 additions and 29 deletions

View file

@ -0,0 +1,34 @@
import utils
from django.conf import settings
def matomo_consent_provider(request):
"""
Add Mamoto consent script to the requests context.
:Cases:
- If MATOMO_REQURE_CONSENT is True OR If ALWAYS_TRACK_REGISTERED True == continue on
- If ALWAYS_TRACK_REGISTERED is True AND the user is authenticated
"""
# Do we require consent?
if getattr(settings, 'MATOMO_REQUIRE_CONSENT', False):
provide_script = True
if request.user.is_authenticated and not getattr(settings, "ALWAYS_TRACK_REGISTERED", True):
provide_script = False
if provide_script:
grant_class_name = getattr(settings, 'GRANT_CONSENT_TAG_CLASSNAME')
revoke_class_name = getattr(settings, 'REVOKE_CONSENT_CLASSNAME')
return {"consent_script":"""
%s;
%s
%s
""" % (
utils.build_paq_cmd('requireConsent'),
utils.get_event_bind_js(
class_name=grant_class_name,
matomo_event="rememberConsentGiven",
),
utils.get_event_bind_js(
class_name=revoke_class_name,
matomo_event="forgetConsentGiven",
)
)}
return {'consent_script': ""}

View file

@ -7,15 +7,12 @@ from collections import namedtuple
from itertools import chain
from django.conf import settings
from django.template import Library, Node, TemplateSyntaxError
from django.template import Library, Node, TemplateSyntaxError, Template
from analytical.utils import (
disable_html,
get_identity,
get_required_setting,
is_internal_ip,
build_paq_cmd,
get_event_bind_js
is_internal_ip
)
# domain name (characters separated by a dot), optional port, optional URI path, no slash
@ -97,19 +94,6 @@ class MatomoNode(Node):
if getattr(settings, 'MATOMO_DISABLE_COOKIES', False):
commands.append(DISABLE_COOKIES_CODE)
if getattr(settings, "MATOMO_REQUIRE_CONSENT", False):
grant_class_name = settings.GRANT_CONSENT_TAG_CLASSNAME
revoke_class_name = settings.REVOKE_CONSENT_CLASSNAME
commands.append(build_paq_cmd('requireConsent'))
commands.append(get_event_bind_js(
class_name=grant_class_name,
matomo_event="rememberConsentGiven",
))
commands.append(get_event_bind_js(
class_name=revoke_class_name,
matomo_event="forgetConsentGiven",
))
userid = get_identity(context, 'matomo')
if userid is not None:
variables_code = chain(variables_code, (
@ -122,8 +106,14 @@ class MatomoNode(Node):
'variables': '\n '.join(variables_code),
'commands': '\n '.join(commands)
}
# Force the consent script to render so we can inject it into the template
consent_script = Template("{{consent_script}}").render(context)
if len(consent_script) > 1:
html += consent_script
if is_internal_ip(context, 'MATOMO'):
html = disable_html(html, 'Matomo')
return html

View file

@ -167,8 +167,8 @@ class AnalyticalException(Exception):
def build_paq_cmd(cmd, args=[]):
"""
:Args:
- cmd -> The command to be pushed to paq (i.e enableHeartbeatTimer or contentInteraction)
- args -> Arguments to be added to the paq command. This is mainly
- cmd: The command to be pushed to paq (i.e enableHeartbeatTimer or contentInteraction)
- args: Arguments to be added to the paq command. This is mainly
used when building commands to be used on manual event trigger.
:Returns:
@ -176,12 +176,12 @@ def build_paq_cmd(cmd, args=[]):
"""
def __to_js_arg(arg):
"""
Turn 'arg' into its javascript counter-part
:Args:
- arg: The variable (Matomo argument) to be converted to JS.
- arg: the argument that's to be passed to the array in _paq.push()
:Return:
Javascript version of the passed arg parameter
The javascript counter-part to the argument that was passed
"""
# Recursively handle dictionaries
if isinstance(arg, dict):
arg_cpy = deepcopy(arg)
for k, v in arg_cpy.items():
@ -196,18 +196,20 @@ def build_paq_cmd(cmd, args=[]):
elif isinstance(arg, list):
for elem_idx in range(len(arg)):
arg[elem_idx] = __to_js_arg(arg[elem_idx])
return arg
paq = "_paq.push(['%s', " % (cmd)
paq = "_paq.push(['%s'" % (cmd)
if len(args) > 0:
paq += ", "
for arg_idx in range(len(args)):
current_arg = __to_js_arg(args[arg_idx])
no_quotes = type(current_arg) in [bool, dict, list]
no_quotes = type(current_arg) in [bool, int, dict, list]
if arg_idx == len(args)-1:
if no_quotes:
segment = "%s])" % (current_arg)
segment = "%s]);" % (current_arg)
else:
segment = "'%s'])" % (current_arg)
segment = "'%s']);" % (current_arg)
else:
if no_quotes:
segment = "%s, "% (current_arg)
@ -215,7 +217,7 @@ def build_paq_cmd(cmd, args=[]):
segment = "'%s', " % (current_arg)
paq += segment
else:
paq += "])"
paq += "]);"
return paq
def get_event_bind_js(
@ -248,4 +250,3 @@ def get_event_bind_js(
}}
""" % (class_name, js_event, build_paq_cmd(matomo_event, matomo_args))
return script