Commit af61b1dd authored by bgm's avatar bgm Committed by Aegir user

#131 Add google_tag drupal module

parent 312b6572
This diff is collapsed.
CONTENTS OF THIS FILE
---------------------
* Introduction
* Requirements
* Recommended modules
* Installation
* Configuration
* Troubleshooting
* Maintainers
INTRODUCTION
------------
This Google Tag Manager project allows non-technical stakeholders to manage the
analytics for their website by triggering the insertion of tags and tracking
systems onto their page(s) via Google's Tag Manager (GTM) hosted application.
* For a full description, visit the project page:
https://www.drupal.org/project/google_tag
* To submit bug reports and feature suggestions, or to track changes:
https://www.drupal.org/project/issues/google_tag
REQUIREMENTS
------------
Sign up for GTM and obtain a 'container ID' for your website. Enter the
'container ID' on the settings form for this module (see Configuration).
* https://www.google.com/analytics/tag-manager/
INSTALLATION
------------
Place the project files in an appropriate modules directory and enable the
module as you would any other contributed module. For further information see:
* https://www.drupal.org/node/895232
CONFIGURATION
-------------
Users in roles with the 'Administer Google Tag Manager' permission will be able
to manage the settings for this module. Configure permissions as usual at:
* Administration » People » Permissions
* admin/people/permissions
From the module settings page, configure the conditions on which the tags are
inserted on a page response. Conditions exist for: page paths, user roles, and
response statuses. See:
* Administration » Configuration » System » Google Tag Manager
* admin/config/system/google_tag
The module implements the Variable API, so that settings may be separately
configured by realm, thus enabling support for multiple languages and domains.
If these features are needed, then review the other projects at:
* https://www.drupal.org/project/variable
* https://www.drupal.org/project/18n
* https://www.drupal.org/project/domain_variable
For development purposes, create a GTM environment for your website and enter
the 'environment ID' on the 'Advanced' tab of the settings form.
* https://tagmanager.google.com/#/admin
For additional data layer management, consider the dataLayer module. It supports
the default name for the data layer. To use a non-default name, apply a patch to
the code from that module module.
* https://www.drupal.org/project/dataLayer
TROUBLESHOOTING
---------------
If the JavaScript snippets are not present in the HTML output, try the following
steps to debug the situation:
* Confirm the snippet files exist at public://google_tag/ (on most sites this
equates to sites/default/files/google_tag/).
If missing, then visit the module settings page and submit the form to
recreate the snippet files. The need to do this may arise if the project is
deployed from one environment to another (e.g. development to production) but
the snippet files are not deployed.
Due to a known bug during an update to releases 1.2-rc3 or 1.2, the snippet
directory is not created. A simple workaround for this bug is to disable and
enable the module (uninstall is not necessary).
* Enable debug output on the 'Advanced' tab of the settings page to display the
result of each snippet insertion condition in the message area. Modify the
insertion conditions as needed.
MAINTAINERS
-----------
Current maintainer:
* Jim Berry (https://www.drupal.org/u/solotandem)
/* Remove extraneous whitespace in panes of vertical tabs. */
#google-tag-settings-form div.vertical-tabs .vertical-tabs-panes fieldset.vertical-tabs-pane {
padding-top: 0;
}
#google-tag-settings-form div.vertical-tabs .fieldset-wrapper {
margin-top: 0;
}
<?php
/**
* @file
* Documents hooks provided by this module.
*
* @author Jim Berry ("solotandem", http://drupal.org/user/240748)
*/
/**
* @addtogroup hooks
* @{
*/
/**
* Alter the state of snippet insertion on the current page response.
*
* This hook allows other modules to alter the state of snippet insertion based
* on custom conditions that cannot be defined by the status, path, and role
* conditions provided by this module.
*
* @param bool $satisfied
* The snippet insertion state.
*/
function hook_google_tag_insert_alter(&$satisfied) {
// Do something to the state.
$state = !$state;
}
/**
* Alter the realm values for the current page response.
*
* This hook allows other modules to alter the realm values that affect the
* snippets to be inserted.
*
* @param array $realm
* Associative array of realm values keyed by name and key.
*/
function hook_google_tag_realm_alter(&$realm) {
// Do something to the realm values.
$realm['name'] = 'my_realm';
$realm['key'] = 'my_key';
}
/**
* Alter the snippets to be inserted on a page response.
*
* This hook allows other modules to alter the snippets to be inserted based on
* custom settings not defined by this module.
*
* @param array $snippets
* Associative array of snippets keyed by type: script, noscript and
* data_layer.
*/
function hook_google_tag_snippets_alter(&$snippets) {
// Do something to the script snippet.
$snippets['script'] = str_replace('insertBefore', 'insertAfter', $snippets['script']);
}
name = Google Tag Manager
description = Allows your website analytics to be managed using Google Tag Manager.
package = Statistics
core = 7.x
configure = admin/config/system/google_tag
; Information added by Drupal.org packaging script on 2018-08-31
version = "7.x-1.4"
core = "7.x"
project = "google_tag"
datestamp = "1535722684"
<?php
/**
* @file
* Provides install, update, and uninstall functions.
*
* @author Jim Berry ("solotandem", http://drupal.org/user/240748)
*/
/**
* Implements hook_enable().
*/
function google_tag_enable() {
global $google_tag_display_message;
$google_tag_display_message = TRUE;
google_tag_assets_create();
}
/**
* Implements hook_uninstall().
*/
function google_tag_uninstall() {
db_delete('variable')
->condition('name', db_like('google_tag_') . '%', 'LIKE')
->execute();
if (module_exists('variable_realm') && module_exists('variable_store')) {
db_delete('variable_store')
->condition('name', db_like('google_tag_') . '%', 'LIKE')
->execute();
// Remove variables from realm variable list.
$realms = variable_realm_list();
foreach ($realms as $realm => $label) {
$variables = variable_get('variable_realm_list_' . $realm, array());
if ($variables) {
foreach ($variables as $key => $variable) {
if (substr($variable, 0, 10) == 'google_tag') {
unset($variables[$key]);
}
}
variable_set('variable_realm_list_' . $realm, $variables);
}
}
}
@file_unmanaged_delete_recursive('public://google_tag');
// @todo Is this relevant here or in _google_tag_snippets_save()?
drupal_clear_js_cache();
}
/**
* Implements hook_requirements().
*/
function google_tag_requirements($phase) {
$t = get_t();
$requirements = array();
if ($phase == 'runtime') {
if (!preg_match('/^GTM-\w{4,}$/', variable_get('google_tag_container_id', ''))) {
// Google Tag Manager container ID has not been set.
$args = array('@path' => '/admin/config/system/google_tag');
$description = $t('Configure this integration module on its <a href="@path">settings page</a>.', $args);
$requirements['google_tag'] = array(
'title' => $t('Google Tag Manager'),
'description' => $description,
'severity' => REQUIREMENT_WARNING,
'value' => $t('Not configured'),
);
}
}
if ($phase == 'runtime' || $phase == 'update' || $phase == 'install') {
// Adapted from system_requirements().
$directory = 'public://google_tag';
module_load_include('inc', 'google_tag', 'includes/admin');
if (!is_dir($directory) || !_google_tag_is_writable($directory) || !_google_tag_is_executable($directory)) {
__file_prepare_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
}
$is_executable = _google_tag_is_executable($directory);
$is_writable = _google_tag_is_writable($directory);
$is_directory = is_dir($directory);
if (!$is_executable || !$is_writable || !$is_directory) {
// The snippet directory does not exist or is not writable or searchable.
// If applicable, get the directory path of stream wrapper.
$wrapper = file_stream_wrapper_get_instance_by_uri($directory);
if (method_exists($wrapper, 'getDirectoryPath') && ($path = $wrapper->getDirectoryPath())) {
// getDirectoryPath() is not defined in StreamWrapperInterface; it
// exists in LocalStream and the local storage replacement classes in
// google_appengine; s3fs returns an empty string.
$path .= '/google_tag';
}
elseif (!($path = $wrapper->getExternalUrl())) {
$path = $directory;
}
if (!$is_directory) {
$error = $t('The directory %directory does not exist.', array('%directory' => $path));
$description = $t('An automated attempt to create the directory failed, possibly due to a permissions problem. Create the directory and make it writable.');
$value = $t('Does not exist');
}
elseif (!$is_writable) {
$error = $t('The directory %directory is not writable.', array('%directory' => $path));
$description = $t('An automated attempt to make the directory writable failed, possibly due to a permissions problem. Make the directory writable.');
$value = $t('Not writable');
}
else {
$error = $t('The directory %directory is not searchable.', array('%directory' => $path));
$description = $t('An automated attempt to make the directory searchable failed, possibly due to a permissions problem. Make the directory searchable.');
$value = $t('Not searchable');
}
if ($phase == 'install') {
$description .= $t(' For more information, see INSTALL.txt or the <a href=":handbook_url">online handbook</a>.', array(':handbook_url' => 'https://www.drupal.org/server-permissions'));
$value = '';
}
$requirements['google_tag_snippet_directory'] = array(
'title' => $t('Google Tag Manager snippet directory'),
'description' => "$error $description",
'severity' => REQUIREMENT_ERROR,
'value' => $value,
);
}
}
return $requirements;
}
/**
* Convert values in role_list variable from rid to role name.
*/
function google_tag_update_7101(&$sandbox) {
$roles = user_roles();
$old_values = variable_get('google_tag_role_list', array());
$new_values = array();
foreach ($old_values as $rid => $old_value) {
$role_name = $roles[$rid];
$new_values[$role_name] = $old_value ? $role_name : $old_value;
}
variable_set('google_tag_role_list', $new_values);
return t('Converted @count role IDs to role names in google_tag_role_list variable', array('@count' => count($old_values)));
}
/**
* Create directory for snippet files, if not present.
*/
function google_tag_update_7102(&$sandbox) {
$result = _google_tag_snippet_directory_prepare();
return t('The directory exists (or was created) and is writable: @result', array('@result' => $result ? 1 : 0));
}
/**
* Creates directory for snippet files, if not present.
*
* @return bool
* Whether the directory exists (or was created) and is writable.
*/
function _google_tag_snippet_directory_prepare() {
// Create directory if not present.
$directory = 'public://google_tag';
$result = file_prepare_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
if (!$result) {
drupal_set_message(t('An error occurred creating a directory for snippet files. Please try again or contact the site administrator if it persists.'));
}
return $result;
}
/**
* Convert toggle settings from integer to string.
*/
function google_tag_update_7103(&$sandbox) {
$types = array('path', 'role', 'status');
$count = 0;
if (module_exists('variable_realm') && module_exists('variable_store')) {
// i18n_variable module depends on variable_realm, variable_store
// In the course of variable_realm_set() calls will be made to:
// - cache_clear_all('variables', 'cache_bootstrap');
// - cache_clear_all('variable:' . $realm . ':' . $key, 'cache_bootstrap');
// which will clear all relevant cache tables except for cache_variable.
// Clear the latter at end.
$realms = variable_realm_list();
foreach ($realms as $realm_name => $realm_title) {
$keys = variable_realm_keys($realm_name);
foreach ($keys as $key_name => $key_title) {
foreach ($types as $type) {
$name = "google_tag_{$type}_toggle";
$value = variable_realm_get($realm_name, $key_name, $name);
// Safer to set status toggle to 'all except' regardless of prior value.
$value = $type == 'status' ? 0 : $value;
if (!is_null($value)) {
$new_value = $value ? GOOGLE_TAG_INCLUDE_LISTED : GOOGLE_TAG_EXCLUDE_LISTED;
variable_realm_set($realm_name, $key_name, $name, $new_value, FALSE);
$count++;
}
}
}
}
variable_cache_clear();
}
else {
global $conf;
foreach ($types as $type) {
$name = "google_tag_{$type}_toggle";
if (isset($conf[$name])) {
$new_value = $conf[$name] ? GOOGLE_TAG_INCLUDE_LISTED : GOOGLE_TAG_EXCLUDE_LISTED;
variable_set($name, $new_value);
$count++;
}
}
}
return t('Converted @count toggle settings', array('@count' => $count));
}
/**
* Rename 'compact_tag' setting to 'compact_snippet'.
*/
function google_tag_update_7104(&$sandbox) {
$count = 0;
$tables = array('variable');
if (module_exists('variable_realm') && module_exists('variable_store')) {
$tables[] = 'variable_store';
$args = array(
':old' => 's:22:"google_tag_compact_tag"',
':new' => 's:26:"google_tag_compact_snippet"',
);
$count += db_update('variable')
->expression('value', 'REPLACE(value, :old, :new)', $args)
->condition('name', 'variable_realm_list_language')
->execute();
}
foreach ($tables as $table) {
$count += db_update($table)
->fields(array('name' => 'google_tag_compact_snippet'))
->condition('name', 'google_tag_compact_tag')
->execute();
}
cache_clear_all('variables', 'cache_bootstrap');
cache_clear_all('variable:', 'cache_bootstrap', TRUE);
if (module_exists('variable')) {
variable_cache_clear();
}
return t('Converted @count occurrences of setting', array('@count' => $count));
}
/**
* Deprecated update hook; no changes will be made.
*/
function google_tag_update_7105(&$sandbox) {
// This update used to [re]create the snippet files, but this is no longer
// necessary here as hook_enable() and hook_cron() were implemented.
}
/**
* Deprecated update hook; no changes will be made.
*/
function google_tag_update_7106(&$sandbox) {
// This update used to [re]create the snippet files, but this is no longer
// necessary here as hook_enable() and hook_cron() were implemented.
}
This diff is collapsed.
This diff is collapsed.
<?php
/**
* @file
* Provides info-type hook implementations that are infrequently called.
*
* @author Jim Berry ("solotandem", http://drupal.org/user/240748)
*/
/**
* Implements hook_help().
*/
function _google_tag_help($path, $arg) {
switch ($path) {
case 'admin/help#google_tag':
case 'admin/config/system/google_tag':
$args = array('@path' => 'https://tagmanager.google.com/');
return t('<a href="@path">Google Tag Manager</a> is a free service (registration required) to manage the insertion of tags for capturing website analytics.', $args);
}
}
/**
* Implements hook_menu().
*/
function _google_tag_menu() {
$items['admin/config/system/google_tag'] = array(
'title' => 'Google Tag Manager',
'description' => 'Configure the website integration with GTM and the resultant capturing of website analytics.',
'page callback' => 'drupal_get_form',
'page arguments' => array('google_tag_settings_form'),
'access arguments' => array('administer google tag manager'),
'type' => MENU_NORMAL_ITEM,
'file' => 'includes/admin.inc',
);
return $items;
}
/**
* Implements hook_permission().
*/
function _google_tag_permission() {
return array(
'administer google tag manager' => array(
'title' => t('Administer Google Tag Manager'),
'description' => t('Configure the website integration with Google Tag Manager.'),
),
);
}
<?php
/**
* @file
* Contains the JavaScript snippet insertion code.
*
* @author Jim Berry ("solotandem", http://drupal.org/user/240748)
*/
/**
* Returns JavaScript snippets.
*
* @return array
* Associative array of snippets keyed by type: script, noscript and
* data_layer.
*/
function google_tag_snippets() {
$snippets = array(
'script' => _google_tag_script_snippet(),
'noscript' => _google_tag_noscript_snippet(),
'data_layer' => _google_tag_data_layer_snippet(),
);
// Allow other modules to alter the snippets.
drupal_alter('google_tag_snippets', $snippets);
return $snippets;
}
/**
* Returns JavaScript script snippet.
*
* @return array
* The script snippet.
*/
function _google_tag_script_snippet() {
// Gather data.
$container_id = _google_tag_variable_clean('google_tag_container_id');
$data_layer = _google_tag_variable_clean('google_tag_data_layer', 'dataLayer');
$query = _google_tag_environment_query();
$compact = variable_get('google_tag_compact_snippet', 1);
// Build script snippet.
$script = <<<EOS
(function(w,d,s,l,i){
w[l]=w[l]||[];
w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});
var f=d.getElementsByTagName(s)[0];
var j=d.createElement(s);
var dl=l!='dataLayer'?'&l='+l:'';
j.type='text/javascript';
j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl+'$query';
j.async=true;
f.parentNode.insertBefore(j,f);
})(window,document,'script','$data_layer','$container_id');
EOS;
if ($compact) {
$script = str_replace(array("\n", ' '), '', $script);
}
/*
$script = <<<EOS
<!-- Google Tag Manager -->
$script
<!-- End Google Tag Manager -->
EOS;
*/
return $script;
}
/**
* Returns JavaScript noscript snippet.
*
* @return array
* The noscript snippet.
*/
function _google_tag_noscript_snippet() {
// Gather data.
$container_id = _google_tag_variable_clean('google_tag_container_id');
$query = _google_tag_environment_query();
$compact = variable_get('google_tag_compact_snippet', 1);
// Build noscript snippet.
$noscript = <<<EOS
<noscript aria-hidden="true"><iframe src="https://www.googletagmanager.com/ns.html?id=$container_id$query"
height="0" width="0" style="display:none;visibility:hidden" title="Google Tag Manager">Google Tag Manager</iframe></noscript>
EOS;
if ($compact) {
$noscript = str_replace("\n", '', $noscript);
}
/*
$noscript = <<<EOS
<!-- Google Tag Manager -->
$noscript
<!-- End Google Tag Manager -->
EOS;
*/
return $noscript;
}
/**
* Returns JavaScript data layer snippet or adds items to data layer.
*
* @return string|null
* The data layer snippet or NULL.
*/
function _google_tag_data_layer_snippet(&$classes = array()) {
// Gather data.
$data_layer = _google_tag_variable_clean('google_tag_data_layer', 'dataLayer');
$whitelist = variable_get('google_tag_whitelist_classes', GOOGLE_TAG_WHITELIST_CLASSES);
$blacklist = variable_get('google_tag_blacklist_classes', GOOGLE_TAG_BLACKLIST_CLASSES);
$classes = array();
$names = array('whitelist', 'blacklist');
foreach ($names as $name) {
google_tag_text_clean($$name, 'array');
if (empty($$name)) {
continue;
}
$classes["gtm.$name"] = $$name;
}
// Build data layer snippet.
$script = $classes ? "var $data_layer = [" . drupal_json_encode($classes) . '];' : '';
return $script;
}
/**
* Returns a query string with the environment parameters.
*
* @return string
* The query string.
*/
function _google_tag_environment_query() {
if (!variable_get('google_tag_include_environment', 0)) {
return '';
}
// Gather data.
$environment_id = _google_tag_variable_clean('google_tag_environment_id');
$environment_token = _google_tag_variable_clean('google_tag_environment_token');
// Build query string.
return "&gtm_auth=$environment_token&gtm_preview=$environment_id&gtm_cookies_win=x";
}
/**
* Returns a cleansed variable.
*
* @param string $variable
* The variable name.
* @param string $variable
* The default value for the variable.
*
* @return string
* The cleansed variable.
*/
function _google_tag_variable_clean($variable, $default = '') {
return trim(drupal_json_encode(variable_get($variable, $default)), '"');
}
This diff is collapsed.
/**
* @file
* Behaviors and utility functions for administrative pages.
*
* @author Jim Berry ("solotandem", http://drupal.org/user/240748)
*/
(function ($) {
/**
* Provides summary information for the vertical tabs.
*/
Drupal.behaviors.gtmInsertionSettings = {
attach: function (context) {
if (typeof jQuery.fn.drupalSetSummary == 'undefined') {
// This behavior only applies if drupalSetSummary is defined.
return;
}
$('fieldset#edit-path', context).drupalSetSummary(function (context) {
var $radio = $('input[name="google_tag_path_toggle"]:checked', context);
if ($radio.val() == 'exclude listed') {
if (!$('textarea[name="google_tag_path_list"]', context).val()) {
return Drupal.t('All paths');
}
else {
return Drupal.t('All paths except listed paths');
}
}
else {
if (!$('textarea[name="google_tag_path_list"]', context).val()) {
return Drupal.t('No paths');
}
else {
return Drupal.t('Only listed paths');
}
}
});
$('fieldset#edit-role', context).drupalSetSummary(function (context) {
var vals = [];
$('input[type="checkbox"]:checked', context).each(function () {
vals.push($.trim($(this).next('label').text()));
});
var $radio = $('input[name="google_tag_role_toggle"]:checked', context);
if ($radio.val() == 'exclude listed') {
if (!vals.length) {
return Drupal.t('All roles');
}
else {
return Drupal.t('All roles except selected roles');
}