With almost 400,000 sites reporting use of the Google Analytics module, it’s in use on 30% of Drupal sites that report to Drupal.org, and among the top 20 most popular Drupal modules. Porting to Drupal 8 is active, with a release candidate and over 3,000 active installs already.

A couple reasons have led me to create an alternative module for Google Analytics in Drupal 8.

The Challenges

Customizing Behaviour

A recent Drupal 7 project I was involved in required some flexibility around setting custom dimensions. The essential behaviour wasn’t particularly complex: two content types in a hierarchy, with the child referencing the parent, and a custom dimension for the parent’s title.

However, the Google Analytics module only offers tokens for setting dimensions, and the tokens would need to differ for each of the content types. Since the JavaScript for sending dimensions is concatenated in a string and output inline, only the Google Analytics module can output it in the correct place so that dimensions are set before the pageview event is sent. The solution required a custom patch to call a hook, where I could implement the logic to load the correct dimension values for each content type in a separate module.

The current Drupal 8 project I’m working on requires flexibility in a different way: the site runs on multiple domains with each having a separate tracking id, but the rest of the behaviour is identical.

Inline JavaScript and Content Security Policy

Content Security Policy allows sites to specify valid sources of JavaScript, so that the browser can block unwanted scripts. Should an un-escaped string be output, a cross-site scripting attack would be prevented because the browser would not run inline or eval()’d code (unless the aptly named ‘unsafe-inline’ or ‘unsafe-eval’ options are used).

While Drupal 8 core doesn’t (yet) add Content-Security-Policy headers, it removed support for attaching inline JavaScript in render arrays and doesn’t add any inline scripts itself so that core doesn’t restrict a site from using CSP. I think how drupalSettings was converted from being inline script is particularly interesting (but did receive some heated response about the removal of core support for inline JavaScript). There are still options for modules and themes to add inline script, but it is not recommended and I would argue that it should not be necessary.

Building a solution

API-first

I wanted to create a toolbox that didn’t carry any assumptions about a particular site’s requirements. There is excellent API documentation for Analytics.js, so I focused on adding a minimal layer to bridge the gap between determining the values in PHP and the commands being sent with JavaScript. The result is a set of classes that map to the available analytics commands, and an event that collects a set of command objects to be output.

To add custom analytics commands, first register an event subscriber in your module’s *.services.yml:

services:

my_module.analytics_command_subscriber:

class: Drupal\my_module\EventSubscriber\AnalyticsCommandSubscriber

tags:

— { name: event_subscriber }

Then create the subscriber class:

<?php

namespace Drupal\my_module\EventSubscriber; class AnalyticsCommandSubscriber

implements EventSubscriberInterface

{

public static function getSubscribedEvents() {

return [AnalyticsEvents::COLLECT => 'onCollect'];

}



public function onCollect(CollectEvent $event) {

$event->addCommand(new Create('UA-12345678-1'));

$event->addCommand(new Pageview());

}

}

Each command class has a default weight that ensures they are sorted into the correct order before being output (Create, Require, Set, Pageview), so multiple subscribers can be created without conflict.

Inline Not Required

To output the commands on the page, they are added to an array in drupalSettings, which a small wrapper script then iterates over to add each command to the queue. Since the full analytics JavaScript library is loaded asynchronously, and only executes after every other JavaScript file on the page is complete, there is no affect on performance or timing versus using inline code.

Configuration

Since the module is first of all a toolkit, the primary option in the configuration form allows to completely disable outputting any default commands. Once enabled, each option is individually controllable in order to keep an alter hook from being needed to override default behaviour. A developer can optionally rely on the module’s defaults and just add custom commands like dimensions, or completely disable the default module behaviour and implement all commands themself.