Commit ef36339a authored by Pietro Albini's avatar Pietro Albini

Merge branch 'master' of code.ubuntu-it.org:ubuntu-it-web/www-test

parents e107f1a6 aeec080b
No preview for this file type
#!/bin/bash
#
# Script to create a complete dump of a Drupal Postgresql database, keeping two backups at
# the same time, one for yesterday and one for today.
#
# You have to specify the folder where backups have to be saved and the name of
# the database.
# The script uses pg_dump
# Suggestion: indicate the complete path.
#
# Use as ./backup.sh ~/backupFolder databaseName
#
# Copyright (C) 2014 Riccardo Padovani <riccardo@rpadovani.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
# Backup directory
DIR=${1}
DBNAME=${2}
# Check if the directory exists, if not, create it
if [ ! -d "$DIR" ]; then
mkdir "$DIR"
fi
# If there is yesterday backup, then delete it
if [ -f "$DIR"/yesterday.tar.gz ]; then
rm -f "$DIR"/yesterday.tar.gz
fi
# If there is a backup of today, move it to yesterday
if [ -f "$DIR"/today.tar.gz ]; then
mv "$DIR"/today.tar.gz "$DIR"/yesterday.tar.gz
fi
# Create the backup
pg_dump "$DBNAME" > "$DIR"/today.sql
tar -cvzf "$DIR"/today.tar.gz "$DIR"/today.sql
# Remove the temp file
rm "$DIR"/today.sql
-- SQL file to strip out private informations from wwwtest.ubuntu-it.org
-- database that should be not around on developer systems, disable modules
-- that shouldn't be enabled and set admin password to one easy to use for
-- developing purposes
--
-- All our tables have 'drupal' as prepend
--
-- Copyright (C) 2014 Riccardo Padovani <riccardo@rpadovani.com>
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program. If not, see <http://www.gnu.org/licenses/>.
-- In drupal_authname there are all users authentication names.
-- We use OpenID, so there are only links to OpenID profiles.
-- If classic authentication is used, decomment the following lines
-- UPDATE drupal_authmap SET authname = CONCAT(aid, '@localhost');
-- In drupal_users there are some interesting informations:
-- Anyway, we don't want to use in local same users we use on test server,
-- because we disable OpenID module, otherwise no one could access but
-- ubuntu-it-www members
-- So we truncate the table and insert a new admin user with pass admin
TRUNCATE drupal_users;
INSERT INTO drupal_users(uid, name, pass, mail, timezone, language, status, init)
VALUES (1, 'admin', '434c3264048726754ee0e07e508d867e', 'admin@localhost', 'Europe/Berlin', 'it', 1, 'admin@localhost');
-- Turnoff modules
DELETE FROM drupal_system WHERE name IN ('tweet_button', 'openid_test', 'openid_launchpad', 'openid_teams', 'googleanalytics', 'google_plusone', 'openid', 'fblikebutton', 'disqus');
-- Delete emails from comments
UPDATE drupal_comment SET name='Anonymous', mail='anonymous@localhost', homepage='http://www.ubuntu-it.org' WHERE uid=0;
-- Truncate tables with sensitive data
TRUNCATE drupal_authmap;
TRUNCATE drupal_cache;
TRUNCATE drupal_cache_block;
TRUNCATE drupal_cache_bootstrap;
TRUNCATE drupal_cache_field;
TRUNCATE drupal_cache_filter;
TRUNCATE drupal_cache_form;
TRUNCATE drupal_cache_image;
TRUNCATE drupal_cache_l10n_update;
TRUNCATE drupal_cache_menu;
TRUNCATE drupal_cache_page;
TRUNCATE drupal_cache_panels;
TRUNCATE drupal_cache_path;
TRUNCATE drupal_cache_token;
TRUNCATE drupal_cache_update;
TRUNCATE drupal_cache_views;
TRUNCATE drupal_cache_views_data;
TRUNCATE drupal_disqus;
TRUNCATE drupal_flood;
TRUNCATE drupal_history;
TRUNCATE drupal_openid_association;
TRUNCATE drupal_openid_nonce;
TRUNCATE drupal_openid_teams_roles;
TRUNCATE drupal_openid_teams_trusted;
TRUNCATE drupal_search_dataset;
TRUNCATE drupal_search_index;
TRUNCATE drupal_search_node_links;
TRUNCATE drupal_search_total;
TRUNCATE drupal_sessions;
TRUNCATE drupal_watchdog;
-- Delete sensitive variables
DELETE FROM drupal_variable WHERE name IN ('drupal_secret_key', 'cron_key');
DELETE FROM drupal_variable WHERE name LIKE 'openid_%';
DELETE FROM drupal_variable WHERE name LIKE 'disqus_%';
#!/bin/bash
#
# Script to create a scrubbed version of a pgsql 9.x database dump using a temp database
# as intermediary.
#
# You need a file named 'scrub.sql' with querys to scrub the db
#
# Use as ./sql_scrub_dump.sh dump_file_input.tar.gz dump_file_output.tar.gz
# Of course you need to launch it with a user which has a role to create
# database in Postgresql
#
# The input and the output dump have to be .tar.gz.
#
# Copyright (C) 2014 Riccardo Padovani <riccardo@rpadovani.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# Some params
local_db='temp_scrub'
dump_input=${1}
dump_output=${2}
# Destroy previous temp database and create a new one
dropdb "$local_db"
createdb "$local_db"
# If there is yesterday backup, then delete it
if [ -f "$dump_output" ]; then
rm -f "$dump_output"
fi
# Extract the file and import in psql
tar -xOvf "$dump_input" | psql "$local_db"
# Scrub the database
psql "$local_db" < scrub.sql
# Export the db
pg_dump "$local_db" > scrubbed-db.sql
tar -cvzf "$dump_output" scrubbed-db.sql
# Removed temporary files
dropdb "$local_db"
rm -f scrubbed-db.sql
......@@ -15,7 +15,7 @@ Requirements
Installation
============
* Copy the 'googleanalytics' module directory in to your Drupal
Copy the 'googleanalytics' module directory in to your Drupal
sites/all/modules directory as usual.
......@@ -51,19 +51,22 @@ choice for "Add if the following PHP code returns TRUE." Sample PHP snippets
that can be used in this textarea can be found on the handbook page
"Overview-approach to block visibility" at http://drupal.org/node/64135.
Custom variables
=================
One example for custom variables tracking is the "User roles" tracking. Enter
the below configuration data into the custom variables settings form under
admin/config/system/googleanalytics.
Custom dimensions and metrics
=============================
One example for custom dimensions tracking is the "User roles" tracking.
1. In the Google Analytics Management Interface you need to setup Dimension #1
with name e.g. "User roles". This step is required. Do not miss it, please.
2. Enter the below configuration data into the custom dimensions settings form
under admin/config/system/googleanalytics. You can also choose another index,
but keep it always in sync with the index used in step #1.
Slot: 1
Name: User roles
Value: [current-user:role-names]
Scope: Visitor
Index: 1
Value: [current-user:role-names]
More details about Custom variables can be found in the Google API documentation at
http://code.google.com/intl/en/apis/analytics/docs/tracking/gaTrackingCustomVariables.html
More details about custom dimensions and metrics can be found in the Google API
documentation at https://developers.google.com/analytics/devguides/collection/analyticsjs/custom-dims-mets
Advanced Settings
=================
......@@ -72,5 +75,5 @@ code textarea. These can be found on the official Google Analytics pages
and a few examples at http://drupal.org/node/248699. Support is not
provided for any customisations you include.
To speed up page loading you may also cache the Analytics ga.js
To speed up page loading you may also cache the Google Analytics "analytics.js"
file locally.
......@@ -80,7 +80,15 @@ function googleanalytics_admin_settings_form($form_state) {
'#title' => t('List of top-level domains'),
'#type' => 'textarea',
'#default_value' => variable_get('googleanalytics_cross_domains', ''),
'#description' => t('If you selected "Multiple top-level domains" above, enter all related top-level domains. Add one domain per line. By default, the data in your reports only includes the path and name of the page, and not the domain name. For more information see section <em>Show separate domain names</em> in <a href="@url">Tracking Multiple Domains</a>.', array('@url' => url('http://support.google.com/analytics/bin/answer.py', array('query' => array('answer' => '1034342'))))),
'#description' => t('If you selected "Multiple top-level domains" above, enter all related top-level domains. Add one domain per line. By default, the data in your reports only includes the path and name of the page, and not the domain name. For more information see section <em>Show separate domain names</em> in <a href="@url">Tracking Multiple Domains</a>.', array('@url' => 'https://support.google.com/analytics/answer/1034342')),
'#states' => array(
'enabled' => array(
':input[name="googleanalytics_domain_mode"]' => array('value' => '2'),
),
'required' => array(
':input[name="googleanalytics_domain_mode"]' => array('value' => '2'),
),
),
);
// Page specific visibility configurations.
......@@ -97,8 +105,8 @@ function googleanalytics_admin_settings_form($form_state) {
if ($visibility == 2 && !$php_access) {
$form['tracking']['page_vis_settings'] = array();
$form['tracking']['page_vis_settings']['visibility'] = array('#type' => 'value', '#value' => 2);
$form['tracking']['page_vis_settings']['pages'] = array('#type' => 'value', '#value' => $pages);
$form['tracking']['page_vis_settings']['googleanalytics_visibility_pages'] = array('#type' => 'value', '#value' => 2);
$form['tracking']['page_vis_settings']['googleanalytics_pages'] = array('#type' => 'value', '#value' => $pages);
}
else {
$options = array(
......@@ -172,6 +180,12 @@ function googleanalytics_admin_settings_form($form_state) {
),
'#default_value' => variable_get('googleanalytics_custom', 0),
);
$form['tracking']['user_vis_settings']['googleanalytics_trackuserid'] = array(
'#type' => 'checkbox',
'#title' => t('Track User ID'),
'#default_value' => variable_get('googleanalytics_trackuserid', 0),
'#description' => t('User ID enables the analysis of groups of sessions, across devices, using a unique, persistent, and non-personally identifiable ID string representing a user. <a href="@url">Learn more about the benfits of using User ID</a>.', array('@url' => 'https://support.google.com/analytics/answer/3123663')),
);
// Link specific configurations.
$form['tracking']['linktracking'] = array(
......@@ -200,6 +214,27 @@ function googleanalytics_admin_settings_form($form_state) {
'#default_value' => variable_get('googleanalytics_trackfiles_extensions', GOOGLEANALYTICS_TRACKFILES_EXTENSIONS),
'#description' => t('A file extension list separated by the | character that will be tracked as download when clicked. Regular expressions are supported. For example: !extensions', array('!extensions' => GOOGLEANALYTICS_TRACKFILES_EXTENSIONS)),
'#maxlength' => 255,
'#states' => array(
'enabled' => array(
':input[name="googleanalytics_trackfiles"]' => array('checked' => TRUE),
),
// Note: Form required marker is not visible as title is invisible.
'required' => array(
':input[name="googleanalytics_trackfiles"]' => array('checked' => TRUE),
),
),
);
$form['tracking']['linktracking']['googleanalytics_tracklinkid'] = array(
'#type' => 'checkbox',
'#title' => t('Track enhanced link attribution'),
'#default_value' => variable_get('googleanalytics_tracklinkid', 0),
'#description' => t('Enhanced Link Attribution improves the accuracy of your In-Page Analytics report by automatically differentiating between multiple links to the same URL on a single page by using link element IDs. <a href="@url">Enable enhanced link attribution</a> in the Admin UI of your Google Analytics account.', array('@url' => 'https://support.google.com/analytics/answer/2558867')),
);
$form['tracking']['linktracking']['googleanalytics_trackurlfragments'] = array(
'#type' => 'checkbox',
'#title' => t('Track changing URL fragments as pageviews'),
'#default_value' => variable_get('googleanalytics_trackurlfragments', 0),
'#description' => t('By default, the URL reported to Google Analytics will not include the "fragment identifier" (i.e. the portion of the URL beginning with a hash sign), and hash changes by themselves will not cause new pageviews to be reported. Checking this box will cause hash changes to be reported as pageviews (in modern browsers) and all pageview URLs to include the fragment where applicable.'),
);
// Message specific configurations.
......@@ -233,7 +268,7 @@ function googleanalytics_admin_settings_form($form_state) {
$form['tracking']['search_and_advertising']['googleanalytics_site_search'] = array(
'#type' => 'checkbox',
'#title' => t('Track internal search'),
'#description' => t('If checked, internal search keywords are tracked. You must configure your Google account to use the internal query parameter <strong>search</strong>. For more information see <a href="@url">Setting Up Site Search for a Profile</a>.', array('@url' => url('http://support.google.com/analytics/bin/answer.py', array('query' => array('answer' => '1012264'))))) . $site_search_dependencies,
'#description' => t('If checked, internal search keywords are tracked. You must configure your Google account to use the internal query parameter <strong>search</strong>. For more information see <a href="@url">Setting Up Site Search for a Profile</a>.', array('@url' => 'https://support.google.com/analytics/answer/1012264')) . $site_search_dependencies,
'#default_value' => variable_get('googleanalytics_site_search', FALSE),
'#disabled' => (module_exists('search') ? FALSE : TRUE),
);
......@@ -245,8 +280,8 @@ function googleanalytics_admin_settings_form($form_state) {
);
$form['tracking']['search_and_advertising']['googleanalytics_trackdoubleclick'] = array(
'#type' => 'checkbox',
'#title' => t('Track DoubleClick data'),
'#description' => t('If checked, the alternative Google <a href="@doubleclick">DoubleClick data tracking</a> is used to enable AdWords remarketing features. If you choose this option you will need to <a href="@privacy">update your privacy policy</a>.', array('@doubleclick' => url('http://support.google.com/analytics/bin/answer.py', array('query' => array('answer' => '2444872'))), '@privacy' => url('http://support.google.com/analytics/bin/answer.py', array('query' => array('answer' => '2636405'))))),
'#title' => t('Track display features'),
'#description' => t('The display features plugin can be used to enable Display Advertising Features in Google Analytics, such as Remarketing, Demographics and Interest Reporting, and more. <a href="@displayfeatures">Learn more about Display Advertising Features in Google Analytics</a>. If you choose this option you will need to <a href="@privacy">update your privacy policy</a>.', array('@displayfeatures' => 'https://support.google.com/analytics/answer/3450482', '@privacy' => 'https://support.google.com/analytics/answer/2700409')),
'#default_value' => variable_get('googleanalytics_trackdoubleclick', FALSE),
);
......@@ -259,7 +294,7 @@ function googleanalytics_admin_settings_form($form_state) {
'#type' => 'checkbox',
'#title' => t('Anonymize visitors IP address'),
'#description' => t('Tell Google Analytics to anonymize the information sent by the tracker objects by removing the last octet of the IP address prior to its storage. Note that this will slightly reduce the accuracy of geographic reporting. In some countries it is not allowed to collect personally identifying information for privacy reasons and this setting may help you to comply with the local laws.'),
'#default_value' => variable_get('googleanalytics_tracker_anonymizeip', 0),
'#default_value' => variable_get('googleanalytics_tracker_anonymizeip', 1),
);
$form['tracking']['privacy']['googleanalytics_privacy_donottrack'] = array(
'#type' => 'checkbox',
......@@ -268,80 +303,104 @@ function googleanalytics_admin_settings_form($form_state) {
'#default_value' => variable_get('googleanalytics_privacy_donottrack', 1),
);
// Custom variables.
$form['googleanalytics_custom_var'] = array(
// Custom Dimensions.
$form['googleanalytics_custom_dimension'] = array(
'#collapsed' => TRUE,
'#collapsible' => TRUE,
'#description' => t('You can add Google Analytics <a href="@custom_var_documentation">Custom Variables</a> here. These will be added to every page that Google Analytics tracking code appears on. Google Analytics will only accept custom variables if the <em>name</em> and <em>value</em> combined are less than 128 bytes after URL encoding. Keep the names as short as possible and expect long values to get trimmed. You may use tokens in custom variable names and values. Global and user tokens are always available; on node pages, node tokens are also available.', array('@custom_var_documentation' => 'https://developers.google.com/analytics/devguides/collection/gajs/gaTrackingCustomVariables')),
'#description' => t('You can set values for Google Analytics <a href="@custom_var_documentation">Custom Dimensions</a> here. You must have already configured your custom dimensions in the <a href="@setup_documentation">Google Analytics Management Interface</a>. You may use tokens. Global and user tokens are always available; on node pages, node tokens are also available.', array('@custom_var_documentation' => 'https://developers.google.com/analytics/devguides/collection/analyticsjs/custom-dims-mets', '@setup_documentation' => 'https://support.google.com/analytics/answer/2709829')),
'#theme' => 'googleanalytics_admin_custom_var_table',
'#title' => t('Custom variables'),
'#title' => t('Custom dimensions'),
'#tree' => TRUE,
'#type' => 'fieldset',
);
$googleanalytics_custom_vars = variable_get('googleanalytics_custom_var', array());
$googleanalytics_custom_dimension = variable_get('googleanalytics_custom_dimension', array());
// Google Analytics supports up to 5 custom variables.
for ($i = 1; $i < 6; $i++) {
$form['googleanalytics_custom_var']['slots'][$i]['slot'] = array(
// Google Analytics supports up to 20 custom dimensions.
for ($i = 1; $i <= 20; $i++) {
$form['googleanalytics_custom_dimension']['indexes'][$i]['index'] = array(
'#default_value' => $i,
'#description' => t('Slot number'),
'#description' => t('Index number'),
'#disabled' => TRUE,
'#size' => 1,
'#title' => t('Custom variable slot #@slot', array('@slot' => $i)),
'#title' => t('Custom dimension index #@index', array('@index' => $i)),
'#title_display' => 'invisible',
'#type' => 'textfield',
);
$form['googleanalytics_custom_var']['slots'][$i]['name'] = array(
'#default_value' => !empty($googleanalytics_custom_vars['slots'][$i]['name']) ? $googleanalytics_custom_vars['slots'][$i]['name'] : '',
'#description' => t('The custom variable name.'),
$form['googleanalytics_custom_dimension']['indexes'][$i]['value'] = array(
'#default_value' => !empty($googleanalytics_custom_dimension['indexes'][$i]['value']) ? $googleanalytics_custom_dimension['indexes'][$i]['value'] : '',
'#description' => t('The custom dimension value.'),
'#maxlength' => 255,
'#size' => 20,
'#title' => t('Custom variable name #@slot', array('@slot' => $i)),
'#title' => t('Custom dimension value #@index', array('@index' => $i)),
'#title_display' => 'invisible',
'#type' => 'textfield',
'#element_validate' => array('googleanalytics_token_element_validate'),
'#token_types' => array('node'),
);
$form['googleanalytics_custom_var']['slots'][$i]['value'] = array(
'#default_value' => !empty($googleanalytics_custom_vars['slots'][$i]['value']) ? $googleanalytics_custom_vars['slots'][$i]['value'] : '',
'#description' => t('The custom variable value.'),
if (module_exists('token')) {
$form['googleanalytics_custom_dimension']['indexes'][$i]['value']['#element_validate'][] = 'token_element_validate';
}
}
$form['googleanalytics_custom_dimension']['googleanalytics_description'] = array(
'#type' => 'item',
'#description' => t('You can supplement Google Analytics\' basic IP address tracking of visitors by segmenting users based on custom dimensions. Section 7 of the <a href="@ga_tos">Google Analytics terms of service</a> requires that You will not (and will not allow any third party to) use the Service to track, collect or upload any data that personally identifies an individual (such as a name, userid, email address or billing information), or other data which can be reasonably linked to such information by Google. You will have and abide by an appropriate Privacy Policy and will comply with all applicable laws and regulations relating to the collection of information from Visitors. You must post a Privacy Policy and that Privacy Policy must provide notice of Your use of cookies that are used to collect traffic data, and You must not circumvent any privacy features (e.g., an opt-out) that are part of the Service.', array('@ga_tos' => 'http://www.google.com/analytics/terms/gb.html')),
);
$form['googleanalytics_custom_dimension']['googleanalytics_token_tree'] = array(
'#theme' => 'token_tree',
'#token_types' => array('node'),
'#dialog' => TRUE,
);
// Custom Metrics.
$form['googleanalytics_custom_metric'] = array(
'#collapsed' => TRUE,
'#collapsible' => TRUE,
'#description' => t('You can add Google Analytics <a href="@custom_var_documentation">Custom Metrics</a> here. You must have already configured your custom metrics in the <a href="@setup_documentation">Google Analytics Management Interface</a>. You may use tokens. Global and user tokens are always available; on node pages, node tokens are also available.', array('@custom_var_documentation' => 'https://developers.google.com/analytics/devguides/collection/analyticsjs/custom-dims-mets', '@setup_documentation' => 'https://support.google.com/analytics/answer/2709829')),
'#theme' => 'googleanalytics_admin_custom_var_table',
'#title' => t('Custom metrics'),
'#tree' => TRUE,
'#type' => 'fieldset',
);
$googleanalytics_custom_metric = variable_get('googleanalytics_custom_metric', array());
// Google Analytics supports up to 20 custom metrics.
for ($i = 1; $i <= 20; $i++) {
$form['googleanalytics_custom_metric']['indexes'][$i]['index'] = array(
'#default_value' => $i,
'#description' => t('Index number'),
'#disabled' => TRUE,
'#size' => 1,
'#title' => t('Custom metric index #@index', array('@index' => $i)),
'#title_display' => 'invisible',
'#type' => 'textfield',
);
$form['googleanalytics_custom_metric']['indexes'][$i]['value'] = array(
'#default_value' => !empty($googleanalytics_custom_metric['indexes'][$i]['value']) ? $googleanalytics_custom_metric['indexes'][$i]['value'] : '',
'#description' => t('The custom metric value.'),
'#maxlength' => 255,
'#title' => t('Custom variable value #@slot', array('@slot' => $i)),
'#title' => t('Custom metric value #@index', array('@index' => $i)),
'#title_display' => 'invisible',
'#type' => 'textfield',
'#element_validate' => array('googleanalytics_token_element_validate'),
'#token_types' => array('node'),
);
if (module_exists('token')) {
$form['googleanalytics_custom_var']['slots'][$i]['name']['#element_validate'][] = 'token_element_validate';
$form['googleanalytics_custom_var']['slots'][$i]['value']['#element_validate'][] = 'token_element_validate';
$form['googleanalytics_custom_metric']['indexes'][$i]['value']['#element_validate'][] = 'token_element_validate';
}
$form['googleanalytics_custom_var']['slots'][$i]['scope'] = array(
'#default_value' => !empty($googleanalytics_custom_vars['slots'][$i]['scope']) ? $googleanalytics_custom_vars['slots'][$i]['scope'] : 3,
'#description' => t('The scope for the custom variable.'),
'#title' => t('Custom variable slot #@slot', array('@slot' => $i)),
'#title_display' => 'invisible',
'#type' => 'select',
'#options' => array(
1 => t('Visitor'),
2 => t('Session'),
3 => t('Page'),
),
);
}
$form['googleanalytics_custom_var']['googleanalytics_custom_var_description'] = array(
$form['googleanalytics_custom_metric']['googleanalytics_description'] = array(
'#type' => 'item',
'#description' => t('You can supplement Google Analytics\' basic IP address tracking of visitors by segmenting users based on custom variables. Section 7 of the <a href="@ga_tos">Google Analytics terms of service</a> requires that You will not (and will not allow any third party to) use the Service to track, collect or upload any data that personally identifies an individual (such as a name, email address or billing information), or other data which can be reasonably linked to such information by Google. You will have and abide by an appropriate Privacy Policy and will comply with all applicable laws and regulations relating to the collection of information from Visitors. You must post a Privacy Policy and that Privacy Policy must provide notice of Your use of cookies that are used to collect traffic data, and You must not circumvent any privacy features (e.g., an opt-out) that are part of the Service.', array('@ga_tos' => 'http://www.google.com/analytics/terms/gb.html')),
'#description' => t('You can supplement Google Analytics\' basic IP address tracking of visitors by segmenting users based on custom metrics. Section 7 of the <a href="@ga_tos">Google Analytics terms of service</a> requires that You will not (and will not allow any third party to) use the Service to track, collect or upload any data that personally identifies an individual (such as a name, userid, email address or billing information), or other data which can be reasonably linked to such information by Google. You will have and abide by an appropriate Privacy Policy and will comply with all applicable laws and regulations relating to the collection of information from Visitors. You must post a Privacy Policy and that Privacy Policy must provide notice of Your use of cookies that are used to collect traffic data, and You must not circumvent any privacy features (e.g., an opt-out) that are part of the Service.', array('@ga_tos' => 'http://www.google.com/analytics/terms/gb.html')),
);
$form['googleanalytics_custom_var']['googleanalytics_custom_var_token_tree'] = array(
$form['googleanalytics_custom_metric']['googleanalytics_token_tree'] = array(
'#theme' => 'token_tree',
'#token_types' => array('node'),
'#dialog' => TRUE,
);
// Advanced feature configurations.
$form['advanced'] = array(
'#type' => 'fieldset',
......@@ -372,33 +431,36 @@ function googleanalytics_admin_settings_form($form_state) {
'#title' => t('Custom JavaScript code'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#description' => t('You can add custom Google Analytics <a href="@snippets">code snippets</a> here. These will be added every time tracking is in effect. Before you add your custom code, you should read the <a href="@ga_concepts_overview">Google Analytics Tracking Code - Functional Overview</a> and the <a href="@ga_js_api">Google Analytics Tracking API</a> documentation. <strong>Do not include the &lt;script&gt; tags</strong>, and always end your code with a semicolon (;).', array('@snippets' => 'http://drupal.org/node/248699', '@ga_concepts_overview' => 'https://developers.google.com/analytics/resources/concepts/gaConceptsTrackingOverview', '@ga_js_api' => 'https://developers.google.com/analytics/devguides/collection/gajs/methods/')),
'#description' => t('You can add custom Google Analytics <a href="@snippets">code snippets</a> here. These will be added every time tracking is in effect. Before you add your custom code, you should read the <a href="@ga_concepts_overview">Google Analytics Tracking Code - Functional Overview</a> and the <a href="@ga_js_api">Google Analytics Tracking API</a> documentation. <strong>Do not include the &lt;script&gt; tags</strong>, and always end your code with a semicolon (;).', array('@snippets' => 'http://drupal.org/node/248699', '@ga_concepts_overview' => 'https://developers.google.com/analytics/resources/concepts/gaConceptsTrackingOverview', '@ga_js_api' => 'https://developers.google.com/analytics/devguides/collection/analyticsjs/method-reference')),
);
$form['advanced']['codesnippet']['googleanalytics_codesnippet_create'] = array(
'#type' => 'textarea',
'#title' => t('Create only fields'),
'#default_value' => _googleanalytics_get_name_value_string(variable_get('googleanalytics_codesnippet_create', array())),
'#rows' => 5,
'#description' => t('Enter one value per line, in the format name|value. Settings in this textarea will be added to <code>ga("create", "UA-XXXX-Y", {"name":"value"});</code>. For more information, read <a href="@url">create only fields</a> documentation in the Analytics.js field reference.', array('@url' => 'https://developers.google.com/analytics/devguides/collection/analyticsjs/field-reference#create')),
'#element_validate' => array('googleanalytics_validate_create_field_values'),
);
$form['advanced']['codesnippet']['googleanalytics_codesnippet_before'] = array(
'#type' => 'textarea',
'#title' => t('Code snippet (before)'),
'#default_value' => variable_get('googleanalytics_codesnippet_before', ''),
'#rows' => 5,
'#description' => t("Code in this textarea will be added <strong>before</strong> _gaq.push(['_trackPageview'])."),
'#description' => t('Code in this textarea will be added <strong>before</strong> <code>ga("send", "pageview");</code>.'),
);
$form['advanced']['codesnippet']['googleanalytics_codesnippet_after'] = array(
'#type' => 'textarea',
'#title' => t('Code snippet (after)'),
'#default_value' => variable_get('googleanalytics_codesnippet_after', ''),
'#rows' => 5,
'#description' => t("Code in this textarea will be added <strong>after</strong> _gaq.push(['_trackPageview']). This is useful if you'd like to track a site in two accounts."),
'#description' => t('Code in this textarea will be added <strong>after</strong> <code>ga("send", "pageview");</code>. This is useful if you\'d like to track a site in two accounts.'),
);
$form['advanced']['googleanalytics_js_scope'] = array(
'#type' => 'select',
'#title' => t('JavaScript scope'),
'#description' => t('Google recommends adding the external JavaScript files to the header for performance reasons. If <em>Multiple top-level domains</em> has been selected, this setting will be forced to header.'),
'#options' => array(
'footer' => t('Footer'),
'header' => t('Header'),
),
'#default_value' => variable_get('googleanalytics_js_scope', 'header'),
'#disabled' => (variable_get('googleanalytics_domain_mode', 0) == 2) ? TRUE : FALSE,
$form['advanced']['googleanalytics_debug'] = array(
'#type' => 'checkbox',
'#title' => t('Enable debugging'),
'#description' => t('If checked, the Google Universal Analytics debugging script will be loaded. You should not enable your production site to use this version of the JavaScript. The analytics_debug.js script is larger than the analytics.js tracking code and it is not typically cached. Using it in your production site will slow down your site for all of your users. Again, this is only for your own testing purposes. Debug messages are printed to the <code>window.console</code> object.'),
'#default_value' => variable_get('googleanalytics_debug', 0),
);
return system_settings_form($form);
......@@ -408,31 +470,26 @@ function googleanalytics_admin_settings_form($form_state) {
* Implements _form_validate().
*/
function googleanalytics_admin_settings_form_validate($form, &$form_state) {
// Custom variables validation.
foreach ($form_state['values']['googleanalytics_custom_var']['slots'] as $custom_var) {
$form_state['values']['googleanalytics_custom_var']['slots'][$custom_var['slot']]['name'] = trim($custom_var['name']);
$form_state['values']['googleanalytics_custom_var']['slots'][$custom_var['slot']]['value'] = trim($custom_var['value']);
// Validate empty names/values.
if (empty($custom_var['name']) && !empty($custom_var['value'])) {
form_set_error("googleanalytics_custom_var][slots][" . $custom_var['slot'] . "][name", t('The custom variable @slot-number requires a <em>Name</em> if a <em>Value</em> has been provided.', array('@slot-number' => $custom_var['slot'])));
}
elseif (!empty($custom_var['name']) && empty($custom_var['value'])) {
form_set_error("googleanalytics_custom_var][slots][" . $custom_var['slot'] . "][value", t('The custom variable @slot-number requires a <em>Value</em> if a <em>Name</em> has been provided.', array('@slot-number' => $custom_var['slot'])));
}
// Trim custom dimensions and metrics.
foreach ($form_state['values']['googleanalytics_custom_dimension']['indexes'] as $dimension) {
$form_state['values']['googleanalytics_custom_dimension']['indexes'][$dimension['index']]['value'] = trim($dimension['value']);
}
foreach ($form_state['values']['googleanalytics_custom_metric']['indexes'] as $metric) {
$form_state['values']['googleanalytics_custom_metric']['indexes'][$metric['index']]['value'] = trim($metric['value']);
}
// Trim some text values.
$form_state['values']['googleanalytics_account'] = trim($form_state['values']['googleanalytics_account']);
$form_state['values']['googleanalytics_pages'] = trim($form_state['values']['googleanalytics_pages']);
$form_state['values']['googleanalytics_cross_domains'] = trim($form_state['values']['googleanalytics_cross_domains']);
$form_state['values']['googleanalytics_codesnippet_create'] = _googleanalytics_extract_create_field_values($form_state['values']['googleanalytics_codesnippet_create']);
$form_state['values']['googleanalytics_codesnippet_before'] = trim($form_state['values']['googleanalytics_codesnippet_before']);
$form_state['values']['googleanalytics_codesnippet_after'] = trim($form_state['values']['googleanalytics_codesnippet_after']);
// Replace all type of dashes (n-dash, m-dash, minus) with the normal dashes.
$form_state['values']['googleanalytics_account'] = str_replace(array('–', '—', '−'), '-', $form_state['values']['googleanalytics_account']);
if (!preg_match('/^UA-\d{4,}-\d+$/', $form_state['values']['googleanalytics_account'])) {
if (!preg_match('/^UA-\d+-\d+$/', $form_state['values']['googleanalytics_account'])) {
form_set_error('googleanalytics_account', t('A valid Google Analytics Web Property ID is case sensitive and formatted like UA-xxxxxxx-yy.'));
}
......@@ -440,16 +497,20 @@ function googleanalytics_admin_settings_form_validate($form, &$form_state) {
if ($form_state['values']['googleanalytics_domain_mode'] == 2 && empty($form_state['values']['googleanalytics_cross_domains'])) {
form_set_error('googleanalytics_cross_domains', t('A list of top-level domains is required if <em>Multiple top-level domains</em> has been selected.'));
}
// Disallow empty list of download file extensions.
if ($form_state['values']['googleanalytics_trackfiles'] && empty($form_state['values']['googleanalytics_trackfiles_extensions'])) {
form_set_error('googleanalytics_trackfiles_extensions', t('List of download file extensions cannot empty.'));
}
// Clear obsolete local cache if cache has been disabled.
if (empty($form_state['values']['googleanalytics_cache']) && $form['advanced']['googleanalytics_cache']['#default_value']) {
googleanalytics_clear_js_cache();
}
// This is for the Newbie's who cannot read a text area description.
if (stristr($form_state['values']['googleanalytics_codesnippet_before'], 'google-analytics.com/ga.js')) {
if (stristr($form_state['values']['googleanalytics_codesnippet_before'], 'google-analytics.com/analytics.js')) {
form_set_error('googleanalytics_codesnippet_before', t('Do not add the tracker code provided by Google into the javascript code snippets! This module already builds the tracker code based on your Google Analytics account number and settings.'));
}
if (stristr($form_state['values']['googleanalytics_codesnippet_after'], 'google-analytics.com/ga.js')) {
if (stristr($form_state['values']['googleanalytics_codesnippet_after'], 'google-analytics.com/analytics.js')) {
form_set_error('googleanalytics_codesnippet_after', t('Do not add the tracker code provided by Google into the javascript code snippets! This module already builds the tracker code based on your Google Analytics account number and settings.'));
}
if (preg_match('/(.*)<\/?script(.*)>(.*)/i', $form_state['values']['googleanalytics_codesnippet_before'])) {
......@@ -458,11 +519,6 @@ function googleanalytics_admin_settings_form_validate($form, &$form_state) {
if (preg_match('/(.*)<\/?script(.*)>(.*)/i', $form_state['values']['googleanalytics_codesnippet_after'])) {
form_set_error('googleanalytics_codesnippet_after', t('Do not include the &lt;script&gt; tags in the javascript code snippets.'));
}
// Header section must be forced for multiple top-level domains.
if ($form_state['values']['googleanalytics_domain_mode'] == 2) {
$form_state['values']['googleanalytics_js_scope'] = 'header';
}
}
/**
......@@ -472,27 +528,23 @@ function theme_googleanalytics_admin_custom_var_table($variables) {
$form = $variables['form'];
$header = array(
array('data' => t('Slot')),
array('data' => t('Name')),
array('data' => t('Index')),
array('data' => t('Value')),
array('data' => t('Scope')),
);
$rows = array();
foreach (element_children($form['slots']) as $key => $id) {
foreach (element_children($form['indexes']) as $key => $id) {
$rows[] = array(
'data' => array(
drupal_render($form['slots'][$id]['slot']),
drupal_render($form['slots'][$id]['name']),
drupal_render($form['slots'][$id]['value']),
drupal_render($form['slots'][$id]['scope']),
drupal_render($form['indexes'][$id]['index']),
drupal_render($form['indexes'][$id]['value']),
),
);
}
$output = theme('table', array('header' => $header, 'rows' => $rows));
$output .= drupal_render($form['googleanalytics_custom_var_description']);
$output .= drupal_render($form['googleanalytics_custom_var_token_tree']);
$output .= drupal_render($form['googleanalytics_description']);
$output .= drupal_render($form['googleanalytics_token_tree']);
return $output;
}
......@@ -599,3 +651,193 @@ function _googleanalytics_contains_forbidden_token($token_string) {
return preg_match('/' . implode('|', array_map('preg_quote', $token_blacklist)) . '/i', $token_string);
}
/**
* #element_validate callback for create only fields.
*
* @param $element
* An associative array containing the properties and children of the
* generic form element.
* @param $form_state
* The $form_state array for the form this element belongs to.
*
* @see form_process_pattern()
*/
function googleanalytics_validate_create_field_values(&$element, &$form_state) {
$values = _googleanalytics_extract_create_field_values($element['#value']);
if (!is_array($values)) {
form_error($element, t('The %element-title field contains invalid input.', array('%element-title' => $element['#title'])));
}
else {
// Check that name and value are valid for the field type.
foreach ($values as $name => $value) {
if ($error = _googleanalytics_validate_create_field_name($name)) {
form_error($element, $error);
break;
}
if ($error = _googleanalytics_validate_create_field_value($value)) {
form_error($element, $error);
break;
}
}
return $element;
}
}
/**
* Extracts the values array from the element.
*
* @param string $string
* The raw string to extract values from.
*
* @return array|null
* The array of extracted key/value pairs, or NULL if the string is invalid.
*
* @see \Drupal\options\Plugin\Field\FieldType\ListTextItem::allowedValuesString()
*/
function _googleanalytics_extract_create_field_values($string) {
$values = array();
$list = explode("\n", $string);
$list = array_map('trim', $list);
$list = array_filter($list, 'strlen');
foreach ($list as $position => $text) {
// Check for an explicit key.
$matches = array();
if (preg_match('/(.*)\|(.*)/', $text, $matches)) {
// Trim key and value to avoid unwanted spaces issues.
$name = trim($matches[1]);
$value = trim($matches[2]);
}
else {
return;
}
$values[$name] = $value;
}
return _googleanalytics_convert_form_value_data_types($values);
}
/**
* Checks whether a field name is valid.
*
* @param string $name
* The option value entered by the user.
*
* @return string
* The error message if the specified value is invalid, NULL otherwise.
*/
function _googleanalytics_validate_create_field_name($name) {
// List of supported field names:
// https://developers.google.com/analytics/devguides/collection/analyticsjs/field-reference#create
$create_only_fields = array(
'name',
'clientId',
'userId',
'sampleRate',
'siteSpeedSampleRate',
'alwaysSendReferrer',
'allowAnchor',
'cookieName',
'cookieDomain',
'cookieExpires',
'legacyCookieDomain',
);
if (!in_array($name, $create_only_fields)) {
return t('Field name %name is an unknown field name. Please see <a href="@url">create only fields</a> documentation for supported field names.', array('%name' => $name, '@url' => 'https://developers.google.com/analytics/devguides/collection/analyticsjs/field-reference#create'));
}
}
/**
* Checks whether a field value is valid.
*
* @param string $value
* The option value entered by the user.
*
* @return string
* The error message if the specified value is invalid, NULL otherwise.
*/
function _googleanalytics_validate_create_field_value($value) {
if (!is_bool($value) && empty($value)) {
return t('A value is required.');
}
if (drupal_strlen($value) > 255) {
return t('Each value must be a string at most 255 characters long.');
}
}
/**
* Generates a string representation of an array.
*
* This string format is suitable for edition in a textarea.
*
* @param array $values
* An array of values, where array keys are values and array values are
* labels.
*
* @return string
* The string representation of the $values array:
* - Values are separated by a carriage return.
* - Each value is in the format "name|value" or "value".
*/
function _googleanalytics_get_name_value_string($values) {
$lines = array();
foreach ($values as $name => $value) {
// Convert data types.
// @todo: #2251377: Json utility class serializes boolean values to incorrect data type
if (is_bool($value)) {
$value = ($value) ? 'true' : 'false';
}
$lines[] = "$name|$value";
}
return implode("\n", $lines);
}
/**
* Prepare form data types for Json conversion.
*
* @param array $values
* Array of name/value pairs.
*
* @return array
* Array of name/value pairs with casted data types.
*/
function _googleanalytics_convert_form_value_data_types($values) {
foreach ($values as $name => $value) {
// Convert data types.
// @todo: #2251377: Json utility class serializes boolean values to incorrect data type
$match = drupal_strtolower($value);
if ($match == 'true') {
$value = TRUE;
}
elseif ($match == 'false') {
$value = FALSE;
}
// Convert other known fields.
// @todo: #2251343: Json utility class serializes numeric values to incorrect data type
switch ($name) {
case 'sampleRate':
// Float
settype($value, 'float');
break;
case 'siteSpeedSampleRate':
case 'cookieExpires':
// Integer
settype($value, 'integer');
break;
}
$values[$name] = $value;
}
return $values;
}
......@@ -91,7 +91,7 @@ Drupal.behaviors.trackingSettingsSummary = {
vals.push(Drupal.t('AdSense ads'));
}
if ($('input#edit-googleanalytics-trackdoubleclick', context).is(':checked')) {
vals.push(Drupal.t('DoubleClick data'));
vals.push(Drupal.t('Display features'));
}
if (!vals.length) {
return Drupal.t('Not tracked');
......
(function ($) {
Drupal.googleanalytics = {};
$(document).ready(function() {
// Attach mousedown, keyup, touchstart events to document only and catch
// clicks on all elements.
$(document.body).bind("mousedown keyup touchstart", function(event) {
console.group("Running Google Analytics for Drupal.");
console.info(event);
// Catch the closest surrounding link of a clicked element.
$(event.target).closest("a,area").each(function() {
console.info("Element '%o' has been detected. Link '%s' found.", this, this.href);
// Is the clicked URL internal?
if (Drupal.googleanalytics.isInternal(this.href)) {
// Skip 'click' tracking, if custom tracking events are bound.
if ($(this).is('.colorbox')) {
// Do nothing here. The custom event will handle all tracking.
console.info("Click on .colorbox item has been detected.");
}
// Is download tracking activated and the file extension configured for download tracking?
else if (Drupal.settings.googleanalytics.trackDownload && Drupal.googleanalytics.isDownload(this.href)) {
// Download link clicked.
console.info("Download url '%s' has been found. Tracked download as extension '%s'.", Drupal.googleanalytics.getPageUrl(this.href), Drupal.googleanalytics.getDownloadExtension(this.href).toUpperCase());
ga("send", "event", "Downloads", Drupal.googleanalytics.getDownloadExtension(this.href).toUpperCase(), Drupal.googleanalytics.getPageUrl(this.href));
}
else if (Drupal.googleanalytics.isInternalSpecial(this.href)) {
// Keep the internal URL for Google Analytics website overlay intact.
console.info("Click on internal special link '%s' has been tracked.", Drupal.googleanalytics.getPageUrl(this.href));
ga("send", "pageview", { page: Drupal.googleanalytics.getPageUrl(this.href) });
}
else {
// e.g. anchor in same page or other internal page link
console.info("Click on internal link '%s' detected, but not tracked by click.", this.href);
}
}
else {
if (Drupal.settings.googleanalytics.trackMailto && $(this).is("a[href^='mailto:'],area[href^='mailto:']")) {
// Mailto link clicked.
console.info("Click on e-mail '%s' has been tracked.", this.href.substring(7));
ga("send", "event", "Mails", "Click", this.href.substring(7));
}
else if (Drupal.settings.googleanalytics.trackOutbound && this.href.match(/^\w+:\/\//i)) {
if (Drupal.settings.googleanalytics.trackDomainMode != 2 && !Drupal.googleanalytics.isCrossDomain(this.hostname, Drupal.settings.googleanalytics.trackCrossDomains)) {
// External link clicked / No top-level cross domain clicked.
console.info("Outbound link '%s' has been tracked.", this.href);
ga("send", "event", "Outbound links", "Click", this.href);
}
else {
console.info("Internal link '%s' clicked, not tracked.", this.href);
}
}
}
});
console.groupEnd();
});
// Track hash changes as unique pageviews, if this option has been enabled.
if (Drupal.settings.googleanalytics.trackUrlFragments) {
window.onhashchange = function() {
console.info("Track URL '%s' as pageview. Hash '%s' has changed.", location.pathname + location.search + location.hash, location.hash);
ga('send', 'pageview', location.pathname + location.search + location.hash);
}
}
// Colorbox: This event triggers when the transition has completed and the
// newly loaded content has been revealed.
$(document).bind("cbox_complete", function () {
var href = $.colorbox.element().attr("href");
if (href) {
console.info("Colorbox transition to url '%s' has been tracked.", Drupal.googleanalytics.getPageUrl(href));
ga("send", "pageview", { page: Drupal.googleanalytics.getPageUrl(href) });
}
});
});
/**
* Check whether the hostname is part of the cross domains or not.
*
* @param string hostname
* The hostname of the clicked URL.
* @param array crossDomains
* All cross domain hostnames as JS array.
*
* @return boolean
*/
Drupal.googleanalytics.isCrossDomain = function (hostname, crossDomains) {
/**
* jQuery < 1.6.3 bug: $.inArray crushes IE6 and Chrome if second argument is
* `null` or `undefined`, http://bugs.jquery.com/ticket/10076,
* https://github.com/jquery/jquery/commit/a839af034db2bd934e4d4fa6758a3fed8de74174
*
* @todo: Remove/Refactor in D8
*/
if (!crossDomains) {
return false;
}
else {
return $.inArray(hostname, crossDomains) > -1 ? true : false;
}
}
/**
* Check whether this is a download URL or not.
*
* @param string url
* The web url to check.
*
* @return boolean
*/
Drupal.googleanalytics.isDownload = function (url) {
var isDownload = new RegExp("\\.(" + Drupal.settings.googleanalytics.trackDownloadExtensions + ")([\?#].*)?$", "i");
return isDownload.test(url);
}
/**
* Check whether this is an absolute internal URL or not.
*
* @param string url
* The web url to check.
*
* @return boolean
*/
Drupal.googleanalytics.isInternal = function (url) {
var isInternal = new RegExp("^(https?):\/\/" + window.location.host, "i");
return isInternal.test(url);
}
/**
* Check whether this is a special URL or not.
*
* URL types:
* - gotwo.module /go/* links.
*
* @param string url
* The web url to check.
*
* @return boolean
*/
Drupal.googleanalytics.isInternalSpecial = function (url) {
var isInternalSpecial = new RegExp("(\/go\/.*)$", "i");
return isInternalSpecial.test(url);
}
/**
* Extract the relative internal URL from an absolute internal URL.
*
* Examples:
* - http://mydomain.com/node/1 -> /node/1
* - http://example.com/foo/bar -> http://example.com/foo/bar
*
* @param string url
* The web url to check.
*
* @return string
* Internal website URL
*/
Drupal.googleanalytics.getPageUrl = function (url) {
var extractInternalUrl = new RegExp("^(https?):\/\/" + window.location.host, "i");
return url.replace(extractInternalUrl, '');
}
/**
* Extract the download file extension from the URL.
*
* @param string url
* The web url to check.
*
* @return string
* The file extension of the passed url. e.g. "zip", "txt"
*/
Drupal.googleanalytics.getDownloadExtension = function (url) {
var extractDownloadextension = new RegExp("\\.(" + Drupal.settings.googleanalytics.trackDownloadExtensions + ")([\?#].*)?$", "i");
var extension = extractDownloadextension.exec(url);
return (extension === null) ? '' : extension[1];
}
})(jQuery);
......@@ -5,9 +5,9 @@ package = Statistics
configure = admin/config/system/googleanalytics
files[] = googleanalytics.test
test_dependencies[] = token
; Information added by drupal.org packaging script on 2013-10-17
version = "7.x-1.4"
; Information added by Drupal.org packaging script on 2014-07-01
version = "7.x-2.0"
core = "7.x"
project = "google_analytics"
datestamp = "1382021586"
datestamp = "1404257628"
......@@ -5,33 +5,21 @@
* Installation file for Google Analytics module.
*/
/**
* Implements hook_install().
*/
function googleanalytics_install() {
// By German laws it's always best to enable the anonymizing of IP addresses.
// NOTE: If this is also an important default setting in other countries, please let us know!
$countries = array(
'DE',
);
if (in_array(variable_get('site_default_country', ''), $countries)) {
variable_set('googleanalytics_tracker_anonymizeip', 1);
}
}
/**
* Implements hook_uninstall().
*/
function googleanalytics_uninstall() {
variable_del('googleanalytics_account');
variable_del('googleanalytics_cache');
variable_del('googleanalytics_codesnippet_create');
variable_del('googleanalytics_codesnippet_before');
variable_del('googleanalytics_codesnippet_after');
variable_del('googleanalytics_cross_domains');
variable_del('googleanalytics_custom');
variable_del('googleanalytics_custom_var');
variable_del('googleanalytics_custom_dimension');
variable_del('googleanalytics_custom_metric');
variable_del('googleanalytics_debug');
variable_del('googleanalytics_domain_mode');
variable_del('googleanalytics_js_scope');
variable_del('googleanalytics_last_cache');
variable_del('googleanalytics_pages');
variable_del('googleanalytics_roles');
......@@ -41,6 +29,9 @@ function googleanalytics_uninstall() {
variable_del('googleanalytics_tracker_anonymizeip');
variable_del('googleanalytics_trackfiles');
variable_del('googleanalytics_trackfiles_extensions');
variable_del('googleanalytics_tracklinkid');
variable_del('googleanalytics_trackurlfragments');
variable_del('googleanalytics_trackuserid');
variable_del('googleanalytics_trackmailto');
variable_del('googleanalytics_trackmessages');
variable_del('googleanalytics_trackoutbound');
......@@ -52,6 +43,8 @@ function googleanalytics_uninstall() {
// Remove backup variables if exist. Remove this code in D8.
variable_del('googleanalytics_codesnippet_after_backup_6300');
variable_del('googleanalytics_codesnippet_before_backup_6300');
variable_del('googleanalytics_codesnippet_after_backup_7200');
variable_del('googleanalytics_codesnippet_before_backup_7200');
variable_del('googleanalytics_segmentation');
}
......@@ -73,8 +66,8 @@ function googleanalytics_requirements($phase) {
if ($phase == 'runtime') {
// Raise warning if Google user account has not been set yet.
if (!preg_match('/^UA-\d{4,}-\d+$/', variable_get('googleanalytics_account', 'UA-'))) {
$requirements['googleanalytics'] = array(
if (!preg_match('/^UA-\d+-\d+$/', variable_get('googleanalytics_account', 'UA-'))) {
$requirements['googleanalytics_account'] = array(
'title' => $t('Google Analytics module'),
'description' => $t('Google Analytics module has not been configured yet. Please configure its settings from the <a href="@url">Google Analytics settings page</a>.', array('@url' => url('admin/config/system/googleanalytics'))),
'severity' => REQUIREMENT_WARNING,
......@@ -82,6 +75,15 @@ function googleanalytics_requirements($phase) {
);
}
}
// Raise warning if debugging is enabled.
if (variable_get('googleanalytics_debug', 0)) {
$requirements['google_analytics_debugging'] = array(
'title' => $t('Google Analytics module'),
'description' => $t('Google Analytics module has debugging enabled. Please disable debugging setting in production sites from the <a href="@url">Google Analytics settings page</a>.', array('@url' => url('admin/config/system/googleanalytics'))),
'severity' => REQUIREMENT_WARNING,
'value' => $t('Debugging enabled'),
);
}
return $requirements;
}
......@@ -427,10 +429,55 @@ function googleanalytics_update_7006() {
}
/**
* Delete obsolete googleanalytics_trackpageloadtime variable.
*/
* Delete obsolete googleanalytics_trackpageloadtime variable.
*/
function googleanalytics_update_7007() {
variable_del('googleanalytics_trackpageloadtime');
return t('Deleted obsolete googleanalytics_trackpageloadtime variable.');
}
/**
* Delete custom ga.js code snipptes to prevent malfunctions in new Universal Analytics tracker. A backup of your snippets will be created.
*/
function googleanalytics_update_7200() {
$messages = array();
// ga.js code will cause the tracker to break. Remove custom code snippets.
$googleanalytics_codesnippet_before = variable_get('googleanalytics_codesnippet_before', '');
if (!empty($googleanalytics_codesnippet_before)) {
variable_set('googleanalytics_codesnippet_before_backup_7200', $googleanalytics_codesnippet_before);
variable_del('googleanalytics_codesnippet_before');
drupal_set_message(Database::getConnection()->prefixTables("A backup of your previous Google Analytics code snippet has been saved in database table '{variable}' as 'googleanalytics_codesnippet_before_backup_7200'. You need to manually upgrade the custom 'before' code snippet."), 'warning');
$messages[] = t('Manual upgrade of custom "before" code snippet is required.');
}
$googleanalytics_codesnippet_after = variable_get('googleanalytics_codesnippet_after', '');
if (!empty($googleanalytics_codesnippet_after)) {
variable_set('googleanalytics_codesnippet_after_backup_7200', $googleanalytics_codesnippet_after);
variable_del('googleanalytics_codesnippet_after');
drupal_set_message(Database::getConnection()->prefixTables("A backup of your previous Google Analytics code snippet has been saved in database table '{variable}' as 'googleanalytics_codesnippet_before_backup_7200'. You need to manually upgrade the custom 'before' code snippet."), 'warning');
$messages[] = t('Manual upgrade of custom "after" code snippet is required.');
}
return empty($messages) ? t('No custom code snipped found. Nothing to do.') : implode(' ', $messages);
}
/**
* Delete obsolete custom variables. Custom variables are now custom dimensions and metrics.
*/
function googleanalytics_update_7201() {
variable_del('googleanalytics_custom_var');
return t('Deleted obsolete custom variables. Custom variables are now custom dimensions and metrics and you need to manually configure them!');
}
/**
* Delete obsolete JavaScript scope variable.
*/
function googleanalytics_update_7202() {
// Remove obsolete scope variable
variable_del('googleanalytics_js_scope');
return t('Removed obsolete JavaScript scope variable.');
}
(function ($) {
Drupal.googleanalytics = {};
$(document).ready(function() {
// Expression to check for absolute internal links.
var isInternal = new RegExp("^(https?):\/\/" + window.location.host, "i");
// Attach mousedown, keyup, touchstart events to document only and catch
// clicks on all elements.
$(document.body).bind("mousedown keyup touchstart", function(event) {
// Attach onclick event to document only and catch clicks on all elements.
$(document.body).click(function(event) {
// Catch the closest surrounding link of a clicked element.
$(event.target).closest("a,area").each(function() {
var ga = Drupal.settings.googleanalytics;
// Expression to check for special links like gotwo.module /go/* links.
var isInternalSpecial = new RegExp("(\/go\/.*)$", "i");
// Expression to check for download links.
var isDownload = new RegExp("\\.(" + ga.trackDownloadExtensions + ")$", "i");
// Is the clicked URL internal?
if (isInternal.test(this.href)) {
if (Drupal.googleanalytics.isInternal(this.href)) {
// Skip 'click' tracking, if custom tracking events are bound.
if ($(this).is('.colorbox')) {
// Do nothing here. The custom event will handle all tracking.
//console.info("Click on .colorbox item has been detected.");
}
// Is download tracking activated and the file extension configured for download tracking?
else if (ga.trackDownload && isDownload.test(this.href)) {
else if (Drupal.settings.googleanalytics.trackDownload && Drupal.googleanalytics.isDownload(this.href)) {
// Download link clicked.
var extension = isDownload.exec(this.href);
_gaq.push(["_trackEvent", "Downloads", extension[1].toUpperCase(), this.href.replace(isInternal, '')]);
ga("send", "event", "Downloads", Drupal.googleanalytics.getDownloadExtension(this.href).toUpperCase(), Drupal.googleanalytics.getPageUrl(this.href));
}
else if (isInternalSpecial.test(this.href)) {
else if (Drupal.googleanalytics.isInternalSpecial(this.href)) {
// Keep the internal URL for Google Analytics website overlay intact.
_gaq.push(["_trackPageview", this.href.replace(isInternal, '')]);
ga("send", "pageview", { page: Drupal.googleanalytics.getPageUrl(this.href) });
}
}
else {
if (ga.trackMailto && $(this).is("a[href^='mailto:'],area[href^='mailto:']")) {
if (Drupal.settings.googleanalytics.trackMailto && $(this).is("a[href^='mailto:'],area[href^='mailto:']")) {
// Mailto link clicked.
_gaq.push(["_trackEvent", "Mails", "Click", this.href.substring(7)]);
ga("send", "event", "Mails", "Click", this.href.substring(7));
}
else if (ga.trackOutbound && this.href.match(/^\w+:\/\//i)) {
if (ga.trackDomainMode == 2 && isCrossDomain(this.hostname, ga.trackCrossDomains)) {
// Top-level cross domain clicked. document.location is handled by _link internally.
event.preventDefault();
_gaq.push(["_link", this.href]);
}
else {
// External link clicked.
_gaq.push(["_trackEvent", "Outbound links", "Click", this.href]);
else if (Drupal.settings.googleanalytics.trackOutbound && this.href.match(/^\w+:\/\//i)) {
if (Drupal.settings.googleanalytics.trackDomainMode != 2 && !Drupal.googleanalytics.isCrossDomain(this.hostname, Drupal.settings.googleanalytics.trackCrossDomains)) {
// External link clicked / No top-level cross domain clicked.
ga("send", "event", "Outbound links", "Click", this.href);
}
}
}
});
});
// Track hash changes as unique pageviews, if this option has been enabled.
if (Drupal.settings.googleanalytics.trackUrlFragments) {
window.onhashchange = function() {
ga('send', 'pageview', location.pathname + location.search + location.hash);
}
}
// Colorbox: This event triggers when the transition has completed and the
// newly loaded content has been revealed.
$(document).bind("cbox_complete", function() {
$(document).bind("cbox_complete", function () {
var href = $.colorbox.element().attr("href");
if (href) {
_gaq.push(["_trackPageview", href.replace(isInternal, '')]);
ga("send", "pageview", { page: Drupal.googleanalytics.getPageUrl(href) });
}
});
......@@ -74,7 +71,7 @@ $(document).ready(function() {
*
* @return boolean
*/
function isCrossDomain(hostname, crossDomains) {
Drupal.googleanalytics.isCrossDomain = function (hostname, crossDomains) {
/**
* jQuery < 1.6.3 bug: $.inArray crushes IE6 and Chrome if second argument is
* `null` or `undefined`, http://bugs.jquery.com/ticket/10076,
......@@ -90,4 +87,79 @@ function isCrossDomain(hostname, crossDomains) {
}
}
/**
* Check whether this is a download URL or not.
*
* @param string url
* The web url to check.
*
* @return boolean
*/
Drupal.googleanalytics.isDownload = function (url) {
var isDownload = new RegExp("\\.(" + Drupal.settings.googleanalytics.trackDownloadExtensions + ")([\?#].*)?$", "i");
return isDownload.test(url);
}
/**
* Check whether this is an absolute internal URL or not.
*
* @param string url
* The web url to check.
*
* @return boolean
*/
Drupal.googleanalytics.isInternal = function (url) {
var isInternal = new RegExp("^(https?):\/\/" + window.location.host, "i");
return isInternal.test(url);
}
/**
* Check whether this is a special URL or not.
*
* URL types:
* - gotwo.module /go/* links.
*
* @param string url
* The web url to check.
*
* @return boolean
*/
Drupal.googleanalytics.isInternalSpecial = function (url) {
var isInternalSpecial = new RegExp("(\/go\/.*)$", "i");
return isInternalSpecial.test(url);
}
/**
* Extract the relative internal URL from an absolute internal URL.
*
* Examples:
* - http://mydomain.com/node/1 -> /node/1
* - http://example.com/foo/bar -> http://example.com/foo/bar
*
* @param string url
* The web url to check.
*
* @return string
* Internal website URL
*/
Drupal.googleanalytics.getPageUrl = function (url) {
var extractInternalUrl = new RegExp("^(https?):\/\/" + window.location.host, "i");
return url.replace(extractInternalUrl, '');
}
/**
* Extract the download file extension from the URL.
*
* @param string url
* The web url to check.
*
* @return string
* The file extension of the passed url. e.g. "zip", "txt"
*/
Drupal.googleanalytics.getDownloadExtension = function (url) {
var extractDownloadextension = new RegExp("\\.(" + Drupal.settings.googleanalytics.trackDownloadExtensions + ")([\?#].*)?$", "i");
var extension = extractDownloadextension.exec(url);
return (extension === null) ? '' : extension[1];
}
})(jQuery);
......@@ -21,6 +21,15 @@ define('GOOGLEANALYTICS_TRACKFILES_EXTENSIONS', '7z|aac|arc|arj|asf|asx|avi|bin|
*/
define('GOOGLEANALYTICS_PAGES', "admin\nadmin/*\nbatch\nnode/add*\nnode/*/*\nuser/*/*");
/**
* Advertise the supported google analytics api details.
*/
function googleanalytics_api() {
return array(
'api' => 'analytics.js',
);
}
/**
* Implements hook_help().
*/
......@@ -95,14 +104,14 @@ function googleanalytics_page_alter(&$page) {
'404 Not Found',
);
// 1. Check if the GA account number has a value.
// 1. Check if the GA account number has a valid value.
// 2. Track page views based on visibility value.
// 3. Check if we should track the currently active user's role.
// 4. Ignore pages visibility filter for 404 or 403 status codes.
if (!empty($id) && (_googleanalytics_visibility_pages() || in_array($status, $trackable_status_codes)) && _googleanalytics_visibility_user($user)) {
if (preg_match('/^UA-\d+-\d+$/', $id) && (_googleanalytics_visibility_pages() || in_array($status, $trackable_status_codes)) && _googleanalytics_visibility_user($user)) {
// We allow different scopes. Default to 'header' but allow user to override if they really need to.
$scope = variable_get('googleanalytics_js_scope', 'header');
$debug = variable_get('googleanalytics_debug', 0);
$url_custom = '';
// Add link tracking.
$link_settings = array();
......@@ -122,10 +131,23 @@ function googleanalytics_page_alter(&$page) {
if ($track_cross_domains = variable_get('googleanalytics_cross_domains', '')) {
$link_settings['trackCrossDomains'] = preg_split('/(\r\n?|\n)/', $track_cross_domains);
}
if ($track_url_fragments = variable_get('googleanalytics_trackurlfragments', 0)) {
$link_settings['trackUrlFragments'] = $track_url_fragments;
$url_custom = 'location.pathname + location.search + location.hash';
}
if (!empty($link_settings)) {
drupal_add_js(array('googleanalytics' => $link_settings), 'setting');
drupal_add_js(drupal_get_path('module', 'googleanalytics') . '/googleanalytics.js');
// Add debugging code.
if ($debug) {
drupal_add_js(drupal_get_path('module', 'googleanalytics') . '/googleanalytics.debug.js');
// Add the JS test in development to the page.
//drupal_add_js(drupal_get_path('module', 'googleanalytics') . '/googleanalytics.test.js');
}
else {
drupal_add_js(drupal_get_path('module', 'googleanalytics') . '/googleanalytics.js');
}
}
// Add messages tracking.
......@@ -142,14 +164,14 @@ function googleanalytics_page_alter(&$page) {
// Track only the selected message types.
if (in_array($type, $message_types)) {
foreach ($messages as $message) {
$message_events .= '_gaq.push(["_trackEvent", ' . drupal_json_encode(t('Messages')) . ', ' . drupal_json_encode($status_heading[$type]) . ', ' . drupal_json_encode(strip_tags($message)) . ']);';
// @todo: Track as exceptions?
$message_events .= 'ga("send", "event", ' . drupal_json_encode(t('Messages')) . ', ' . drupal_json_encode($status_heading[$type]) . ', ' . drupal_json_encode(strip_tags($message)) . ');';
}
}
}
}
// Site search tracking support.
$url_custom = '';
if (module_exists('search') && variable_get('googleanalytics_site_search', FALSE) && arg(0) == 'search' && $keys = googleanalytics_search_get_keys()) {
// hook_preprocess_search_results() is not executed if search result is
// empty. Make sure the counter is set to 0 if there are no results.
......@@ -178,89 +200,135 @@ function googleanalytics_page_alter(&$page) {
$url_custom = '"/404.html?page=" + document.location.pathname + document.location.search + "&from=" + document.referrer';
}
// Add any custom code snippets if specified.
$codesnippet_before = variable_get('googleanalytics_codesnippet_before', '');
$codesnippet_after = variable_get('googleanalytics_codesnippet_after', '');
// Add custom variables.
$googleanalytics_custom_vars = variable_get('googleanalytics_custom_var', array());
// Add custom dimensions and metrics.
$custom_var = '';
for ($i = 1; $i < 6; $i++) {
$custom_var_name = !empty($googleanalytics_custom_vars['slots'][$i]['name']) ? $googleanalytics_custom_vars['slots'][$i]['name'] : '';
if (!empty($custom_var_name)) {
$custom_var_value = !empty($googleanalytics_custom_vars['slots'][$i]['value']) ? $googleanalytics_custom_vars['slots'][$i]['value'] : '';
$custom_var_scope = !empty($googleanalytics_custom_vars['slots'][$i]['scope']) ? $googleanalytics_custom_vars['slots'][$i]['scope'] : 3;
$types = array();
$node = menu_get_object();
if (is_object($node)) {
$types += array('node' => $node);
}
$custom_var_name = token_replace($custom_var_name, $types, array('clear' => TRUE));
$custom_var_value = token_replace($custom_var_value, $types, array('clear' => TRUE));
// Suppress empty custom names and/or variables.
if (!drupal_strlen(trim($custom_var_name)) || !drupal_strlen(trim($custom_var_value))) {
continue;
}
foreach (array('dimension', 'metric') as $googleanalytics_custom_type) {
$googleanalytics_custom_vars = variable_get('googleanalytics_custom_' . $googleanalytics_custom_type, array());
for ($i = 1; $i <= 20; $i++) {
$custom_var_value = !empty($googleanalytics_custom_vars['indexes'][$i]['value']) ? $googleanalytics_custom_vars['indexes'][$i]['value'] : '';
if (!empty($custom_var_value)) {
$types = array();
$node = menu_get_object();
if (is_object($node)) {
$types += array('node' => $node);
}
$custom_var_value = token_replace($custom_var_value, $types, array('clear' => TRUE));
// Suppress empty custom names and/or variables.
if (!drupal_strlen(trim($custom_var_value))) {
continue;
}
// The length of the string used for the 'name' and the length of the
// string used for the 'value' must not exceed 128 bytes after url encoding.
$name_length = drupal_strlen(rawurlencode($custom_var_name));
$tmp_value = rawurlencode($custom_var_value);
$value_length = drupal_strlen($tmp_value);
if ($name_length + $value_length > 128) {
// Trim value and remove fragments of url encoding.
$tmp_value = rtrim(substr($tmp_value, 0, 127 - $name_length), '%0..9A..F');
$custom_var_value = urldecode($tmp_value);
// Cast metric values for json_encode to data type numeric.
if ($googleanalytics_custom_type == 'metric') {
settype($custom_var_value, 'float');
};
$custom_var .= 'ga("set", ' . drupal_json_encode($googleanalytics_custom_type . $i) . ', ' . drupal_json_encode($custom_var_value) . ');';
}
$custom_var_name = drupal_json_encode($custom_var_name);
$custom_var_value = drupal_json_encode($custom_var_value);
$custom_var .= "_gaq.push(['_setCustomVar', $i, $custom_var_name, $custom_var_value, $custom_var_scope]);";
}
}
// Build tracker code.
$script = 'var _gaq = _gaq || [];';
$script .= '_gaq.push(["_setAccount", ' . drupal_json_encode($id) . ']);';
if (variable_get('googleanalytics_tracker_anonymizeip', 0)) {
// FIXME: The Google API is currently broken and "_gat._anonymizeIp" is only
// a workaround until "_anonymizeIp" has been implemented/fixed.
$script .= '_gaq.push(["_gat._anonymizeIp"]);';
$script = '(function(i,s,o,g,r,a,m){';
$script .= 'i["GoogleAnalyticsObject"]=r;i[r]=i[r]||function(){';
$script .= '(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),';
$script .= 'm=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)';
$script .= '})(window,document,"script",';
// Which version of the tracking library should be used?
$library_tracker_url = '//www.google-analytics.com/' . ($debug ? 'analytics_debug.js' : 'analytics.js');
$library_cache_url = 'http:' . $library_tracker_url;
// Should a local cached copy of analytics.js be used?
if (variable_get('googleanalytics_cache', 0) && $url = _googleanalytics_cache($library_cache_url)) {
// A dummy query-string is added to filenames, to gain control over
// browser-caching. The string changes on every update or full cache
// flush, forcing browsers to load a new copy of the files, as the
// URL changed.
$query_string = '?' . variable_get('css_js_query_string', '0');
$script .= '"' . $url . $query_string . '"';
}
else {
$script .= '"' . $library_tracker_url . '"';
}
$script .= ',"ga");';
// Prepare Adsense tracking.
$googleanalytics_adsense_script = 'window.google_analytics_uacct = ' . drupal_json_encode($id) . ';';
// Add any custom code snippets if specified.
$codesnippet_create = variable_get('googleanalytics_codesnippet_create', array());
$codesnippet_before = variable_get('googleanalytics_codesnippet_before', '');
$codesnippet_after = variable_get('googleanalytics_codesnippet_after', '');
// Build the create only fields list.
$create_only_fields = array();
$create_only_fields = array_merge($create_only_fields, $codesnippet_create);
// Domain tracking type.
global $cookie_domain;
$domain_mode = variable_get('googleanalytics_domain_mode', 0);
$googleanalytics_adsense_script = '';
// Per RFC 2109, cookie domains must contain at least one dot other than the
// first. For hosts such as 'localhost' or IP Addresses we don't set a cookie domain.
if ($domain_mode == 1 && count(explode('.', $cookie_domain)) > 2 && !is_numeric(str_replace('.', '', $cookie_domain))) {
$script .= '_gaq.push(["_setDomainName", ' . drupal_json_encode($cookie_domain) . ']);';
$create_only_fields = array_merge($create_only_fields, array('cookieDomain' => $cookie_domain));
$googleanalytics_adsense_script .= 'window.google_analytics_domain_name = ' . drupal_json_encode($cookie_domain) . ';';
}
elseif ($domain_mode == 2) {
$script .= '_gaq.push(["_setDomainName", "none"]);';
$script .= '_gaq.push(["_setAllowLinker", true]);';
// Cross Domain tracking. 'autoLinker' need to be enabled in 'create'.
$create_only_fields = array_merge($create_only_fields, array('allowLinker' => TRUE));
$googleanalytics_adsense_script .= 'window.google_analytics_domain_name = "none";';
}
// Track logged in users accross all devices.
if (variable_get('googleanalytics_trackuserid', 0) && user_is_logged_in()) {
// The USER_ID value should be a unique, persistent, and non-personally
// identifiable string identifier that represents a user or signed-in
// account across devices.
$create_only_fields['userId'] = drupal_base64_encode(drupal_get_hash_salt() . $user->uid);
}
// Create a tracker.
$create_only_fields = empty($create_only_fields) ? 'auto' : $create_only_fields;
$script .= 'ga("create", ' . drupal_json_encode($id) . ', ' . drupal_json_encode($create_only_fields) .');';
// Prepare Adsense tracking.
$googleanalytics_adsense_script .= 'window.google_analytics_uacct = ' . drupal_json_encode($id) . ';';
// Add enhanced link attribution after 'create', but before 'pageview' send.
// @see https://support.google.com/analytics/answer/2558867
if (variable_get('googleanalytics_tracklinkid', 0)) {
$script .= 'ga("require", "linkid", "linkid.js");';
}
// Add display features after 'create', but before 'pageview' send.
// @see https://support.google.com/analytics/answer/2444872
if (variable_get('googleanalytics_trackdoubleclick', 0)) {
$script .= 'ga("require", "displayfeatures");';
}
// Domain tracking type.
if ($domain_mode == 2) {
// Cross Domain tracking
// https://developers.google.com/analytics/devguides/collection/upgrade/reference/gajs-analyticsjs#cross-domain
$script .= 'ga("require", "linker");';
$script .= 'ga("linker:autoLink", ' . drupal_json_encode($link_settings['trackCrossDomains']) . ');';
}
if (variable_get('googleanalytics_tracker_anonymizeip', 1)) {
$script .= 'ga("set", "anonymizeIp", true);';
}
if (!empty($custom_var)) {
$script .= $custom_var;
}
if (!empty($codesnippet_before)) {
$script .= $codesnippet_before;
}
if (empty($url_custom)) {
$script .= '_gaq.push(["_trackPageview"]);';
}
else {
$script .= '_gaq.push(["_trackPageview", ' . $url_custom . ']);';
if (!empty($url_custom)) {
$script .= 'ga("set", "page", ' . $url_custom . ');';
}
$script .= 'ga("send", "pageview");';
if (!empty($message_events)) {
$script .= $message_events;
}
......@@ -268,52 +336,14 @@ function googleanalytics_page_alter(&$page) {
$script .= $codesnippet_after;
}
$script .= '(function() {';
$script .= 'var ga = document.createElement("script");';
$script .= 'ga.type = "text/javascript";';
$script .= 'ga.async = true;';
// Which version of the tracking library should be used?
if ($trackdoubleclick = variable_get('googleanalytics_trackdoubleclick', FALSE)) {
$library_tracker_url = 'stats.g.doubleclick.net/dc.js';
$library_cache_url = 'http://' . $library_tracker_url;
}
else {
$library_tracker_url = '.google-analytics.com/ga.js';
$library_cache_url = 'http://www' . $library_tracker_url;
}
// Should a local cached copy of ga.js be used?
if (variable_get('googleanalytics_cache', 0) && $url = _googleanalytics_cache($library_cache_url)) {
// A dummy query-string is added to filenames, to gain control over
// browser-caching. The string changes on every update or full cache
// flush, forcing browsers to load a new copy of the files, as the
// URL changed.
$query_string = '?' . variable_get('css_js_query_string', '0');
$script .= 'ga.src = "' . $url . $query_string . '";';
}
else {
// Library paths do not follow the same naming convention.
if ($trackdoubleclick) {
$script .= 'ga.src = ("https:" == document.location.protocol ? "https://" : "http://") + "' . $library_tracker_url . '";';
}
else {
$script .= 'ga.src = ("https:" == document.location.protocol ? "https://ssl" : "http://www") + "' . $library_tracker_url . '";';
}
}
$script .= 'var s = document.getElementsByTagName("script")[0];';
$script .= 's.parentNode.insertBefore(ga, s);';
$script .= '})();';
if (variable_get('googleanalytics_trackadsense', FALSE)) {
// Custom tracking. Prepend before all other JavaScript.
// @TODO: http://support.google.com/adsense/bin/answer.py?answer=98142
// @TODO: https://support.google.com/adsense/answer/98142
// sounds like it could be appended to $script.
drupal_add_js($googleanalytics_adsense_script, array('type' => 'inline', 'group' => JS_LIBRARY-1));
}
drupal_add_js($script, array('scope' => $scope, 'type' => 'inline'));
drupal_add_js($script, array('scope' => 'header', 'type' => 'inline'));
}
}
......@@ -398,13 +428,7 @@ function googleanalytics_user_presave(&$edit, $account, $category) {
function googleanalytics_cron() {
// Regenerate the tracking code file every day.
if (REQUEST_TIME - variable_get('googleanalytics_last_cache', 0) >= 86400 && variable_get('googleanalytics_cache', 0)) {
// Which version of the tracking library should be used?
if (variable_get('googleanalytics_trackdoubleclick', FALSE)) {
_googleanalytics_cache('http://stats.g.doubleclick.net/dc.js', TRUE);
}
else {
_googleanalytics_cache('http://www.google-analytics.com/ga.js', TRUE);
}
_googleanalytics_cache('http://www.google-analytics.com/analytics.js', TRUE);
variable_set('googleanalytics_last_cache', REQUEST_TIME);
}
}
......@@ -444,16 +468,16 @@ function googleanalytics_search_get_keys() {
*
* @param $location
* The full URL to the external javascript file.
* @param $sync_cached_file
* Synchronize tracking code and update if remote file have changed.
* @param $synchronize
* Synchronize to local cache if remote file has changed.
* @return mixed
* The path to the local javascript file on success, boolean FALSE on failure.
*/
function _googleanalytics_cache($location, $sync_cached_file = FALSE) {
function _googleanalytics_cache($location, $synchronize = FALSE) {
$path = 'public://googleanalytics';
$file_destination = $path . '/' . basename($location);
if (!file_exists($file_destination) || $sync_cached_file) {
if (!file_exists($file_destination) || $synchronize) {
// Download the latest tracking code.
$result = drupal_http_request($location);
......
......@@ -20,6 +20,8 @@ class GoogleAnalyticsBasicTest extends DrupalWebTestCase {
$permissions = array(
'access administration pages',
'administer google analytics',
'administer modules',
'administer site configuration',
);
// User to set up google_analytics.
......@@ -28,6 +30,16 @@ class GoogleAnalyticsBasicTest extends DrupalWebTestCase {
}
function testGoogleAnalyticsConfiguration() {
// Check if Configure link is available on 'Extend' page.
// Requires 'administer modules' permission.
$this->drupalGet('admin/modules');
$this->assertRaw('admin/config/system/googleanalytics', '[testGoogleAnalyticsConfiguration]: Configure link from Extend page to Google Analytics Settings page exists.');
// Check if Configure link is available on 'Status Reports' page. NOTE: Link is only shown without UA code configured.
// Requires 'administer site configuration' permission.
$this->drupalGet('admin/reports/status');
$this->assertRaw('admin/config/system/googleanalytics', '[testGoogleAnalyticsConfiguration]: Configure link from Status Reports page to Google Analytics Settings page exists.');
// Check for setting page's presence.
$this->drupalGet('admin/config/system/googleanalytics');
$this->assertRaw(t('Web Property ID'), '[testGoogleAnalyticsConfiguration]: Settings page displayed.');
......@@ -39,6 +51,11 @@ class GoogleAnalyticsBasicTest extends DrupalWebTestCase {
}
function testGoogleAnalyticsPageVisibility() {
// Verify that no tracking code is embedded into the webpage; if there is
// only the module installed, but UA code not configured. See #2246991.
$this->drupalGet('');
$this->assertNoRaw('//www.google-analytics.com/analytics.js', '[testGoogleAnalyticsPageVisibility]: Tracking code is not displayed without UA code configured.');
$ua_code = 'UA-123456-1';
variable_set('googleanalytics_account', $ua_code);
......@@ -58,7 +75,7 @@ class GoogleAnalyticsBasicTest extends DrupalWebTestCase {
$this->assertNoRaw($ua_code, '[testGoogleAnalyticsPageVisibility]: Tracking code is not displayed on admin page.');
$this->drupalGet('admin/config/system/googleanalytics');
// Checking for tracking code URI here, as $ua_code is displayed in the form.
$this->assertNoRaw('google-analytics.com/ga.js', '[testGoogleAnalyticsPageVisibility]: Tracking code is not displayed on admin subpage.');
$this->assertNoRaw('//www.google-analytics.com/analytics.js', '[testGoogleAnalyticsPageVisibility]: Tracking code is not displayed on admin subpage.');
// Test whether tracking code display is properly flipped.
variable_set('googleanalytics_visibility_pages', 1);
......@@ -66,7 +83,7 @@ class GoogleAnalyticsBasicTest extends DrupalWebTestCase {
$this->assertRaw($ua_code, '[testGoogleAnalyticsPageVisibility]: Tracking code is displayed on admin page.');
$this->drupalGet('admin/config/system/googleanalytics');
// Checking for tracking code URI here, as $ua_code is displayed in the form.
$this->assertRaw('google-analytics.com/ga.js', '[testGoogleAnalyticsPageVisibility]: Tracking code is displayed on admin subpage.');
$this->assertRaw('//www.google-analytics.com/analytics.js', '[testGoogleAnalyticsPageVisibility]: Tracking code is displayed on admin subpage.');
$this->drupalGet('');
$this->assertNoRaw($ua_code, '[testGoogleAnalyticsPageVisibility]: Tracking code is NOT displayed on front page.');
......@@ -89,22 +106,22 @@ class GoogleAnalyticsBasicTest extends DrupalWebTestCase {
$this->assertRaw('/404.html', '[testGoogleAnalyticsPageVisibility]: 404 Not Found tracking code shown on non-existent page.');
// DNT Tests:
// Enable caching of pages for anonymous users.
// Enable system internal page cache for anonymous users.
variable_set('cache', 1);
// Test whether DNT headers will fail to disable embedding of tracking code.
$this->drupalGet('', array(), array('DNT: 1'));
$this->assertRaw('_gaq.push(["_trackPageview"]);', '[testGoogleAnalyticsDNTVisibility]: DNT header send from client, but page caching is enabled and tracker cannot removed.');
// DNT works only with caching of pages for anonymous users disabled.
$this->assertRaw('ga("send", "pageview");', '[testGoogleAnalyticsDNTVisibility]: DNT header send from client, but page caching is enabled and tracker cannot removed.');
// DNT works only with system internal page cache for anonymous users disabled.
variable_set('cache', 0);
$this->drupalGet('');
$this->assertRaw('_gaq.push(["_trackPageview"]);', '[testGoogleAnalyticsDNTVisibility]: Tracking is enabled without DNT header.');
$this->assertRaw('ga("send", "pageview");', '[testGoogleAnalyticsDNTVisibility]: Tracking is enabled without DNT header.');
// Test whether DNT header is able to remove the tracking code.
$this->drupalGet('', array(), array('DNT: 1'));
$this->assertNoRaw('_gaq.push(["_trackPageview"]);', '[testGoogleAnalyticsDNTVisibility]: DNT header received from client. Tracking has been disabled by browser.');
$this->assertNoRaw('ga("send", "pageview");', '[testGoogleAnalyticsDNTVisibility]: DNT header received from client. Tracking has been disabled by browser.');
// Disable DNT feature and see if tracker is still embedded.
variable_set('googleanalytics_privacy_donottrack', 0);
$this->drupalGet('', array(), array('DNT: 1'));
$this->assertRaw('_gaq.push(["_trackPageview"]);', '[testGoogleAnalyticsDNTVisibility]: DNT feature is disabled, DNT header from browser has been ignored.');
$this->assertRaw('ga("send", "pageview");', '[testGoogleAnalyticsDNTVisibility]: DNT feature is disabled, DNT header from browser has been ignored.');
}
function testGoogleAnalyticsTrackingCode() {
......@@ -118,40 +135,75 @@ class GoogleAnalyticsBasicTest extends DrupalWebTestCase {
/* Sample JS code as added to page:
<script type="text/javascript" src="/sites/all/modules/google_analytics/googleanalytics.js?w"></script>
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-123456-7']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
<script>
(function(q,u,i,c,k){window['GoogleAnalyticsObject']=q;
window[q]=window[q]||function(){(window[q].q=window[q].q||[]).push(arguments)},
window[q].l=1*new Date();c=i.createElement(u),k=i.getElementsByTagName(u)[0];
c.async=true;c.src='//www.google-analytics.com/analytics.js';
k.parentNode.insertBefore(c,k)})('ga','script',document);
ga('create', 'UA-123456-7');
ga('send', 'pageview');
</script>
<!-- End Google Analytics -->
*/
// Test whether tracking code uses latest JS.
variable_set('googleanalytics_cache', 0);
$this->drupalGet('');
$this->assertRaw('google-analytics.com/ga.js', '[testGoogleAnalyticsTrackingCode]: Latest tracking code used.');
// Test whether the alternate doubleclick library is used
variable_set('googleanalytics_trackdoubleclick', 1);
$this->drupalGet('');
$this->assertRaw('stats.g.doubleclick.net/dc.js', '[testGoogleAnalyticsTrackingCode]: Doubleclick tracking code used.');
$this->assertRaw('//www.google-analytics.com/analytics.js', '[testGoogleAnalyticsTrackingCode]: Latest tracking code used.');
// Test whether anonymize visitors IP address feature has been enabled.
variable_set('googleanalytics_tracker_anonymizeip', 0);
$this->drupalGet('');
$this->assertNoRaw('_gaq.push(["_gat._anonymizeIp"]);', '[testGoogleAnalyticsTrackingCode]: Anonymize visitors IP address not found on frontpage.');
$this->assertNoRaw('ga("set", "anonymizeIp", true);', '[testGoogleAnalyticsTrackingCode]: Anonymize visitors IP address not found on frontpage.');
// Enable anonymizing of IP addresses.
variable_set('googleanalytics_tracker_anonymizeip', 1);
$this->drupalGet('');
$this->assertRaw('_gaq.push(["_gat._anonymizeIp"]);', '[testGoogleAnalyticsTrackingCode]: Anonymize visitors IP address found on frontpage.');
$this->assertRaw('ga("set", "anonymizeIp", true);', '[testGoogleAnalyticsTrackingCode]: Anonymize visitors IP address found on frontpage.');
// Test if track Enhanced Link Attribution is enabled.
variable_set('googleanalytics_tracklinkid', 1);
$this->drupalGet('');
$this->assertRaw('ga("require", "linkid", "linkid.js");', '[testGoogleAnalyticsTrackingCode]: Tracking code for Enhanced Link Attribution is enabled.');
// Test if track Enhanced Link Attribution is disabled.
variable_set('googleanalytics_tracklinkid', 0);
$this->drupalGet('');
$this->assertNoRaw('ga("require", "linkid", "linkid.js");', '[testGoogleAnalyticsTrackingCode]: Tracking code for Enhanced Link Attribution is not enabled.');
// Test if tracking of User ID is enabled.
variable_set('googleanalytics_trackuserid', 1);
$this->drupalGet('');
$this->assertRaw(', {"userId":"', '[testGoogleAnalyticsTrackingCode]: Tracking code for User ID is enabled.');
// Test if tracking of User ID is disabled.
variable_set('googleanalytics_trackuserid', 0);
$this->drupalGet('');
$this->assertNoRaw(', {"userId":"', '[testGoogleAnalyticsTrackingCode]: Tracking code for User ID is disabled.');
// Test if tracking of url fragments is enabled.
variable_set('googleanalytics_trackurlfragments', 1);
$this->drupalGet('');
$this->assertRaw('ga("set", "page", location.pathname + location.search + location.hash);', '[testGoogleAnalyticsTrackingCode]: Tracking code for url fragments is enabled.');
// Test if tracking of url fragments is disabled.
variable_set('googleanalytics_trackurlfragments', 0);
$this->drupalGet('');
$this->assertNoRaw('ga("set", "page", location.pathname + location.search + location.hash);', '[testGoogleAnalyticsTrackingCode]: Tracking code for url fragments is not enabled.');
// Test if track display features is enabled.
variable_set('googleanalytics_trackdoubleclick', 1);
$this->drupalGet('');
$this->assertRaw('ga("require", "displayfeatures");', '[testGoogleAnalyticsTrackingCode]: Tracking code for display features is enabled.');
// Test if track display features is disabled.
variable_set('googleanalytics_trackdoubleclick', 0);
$this->drupalGet('');
$this->assertNoRaw('ga("require", "displayfeatures");', '[testGoogleAnalyticsTrackingCode]: Tracking code for display features is not enabled.');
// Test whether single domain tracking is active.
$this->drupalGet('');
$this->assertNoRaw('_gaq.push(["_setDomainName"', '[testGoogleAnalyticsTrackingCode]: Single domain tracking is active.');
$this->assertNoRaw('{"cookieDomain":"', '[testGoogleAnalyticsTrackingCode]: Single domain tracking is active.');
// Enable "One domain with multiple subdomains".
variable_set('googleanalytics_domain_mode', 1);
......@@ -161,36 +213,62 @@ class GoogleAnalyticsBasicTest extends DrupalWebTestCase {
// TODO: Workaround to run tests successfully. This feature cannot tested reliable.
global $cookie_domain;
if (count(explode('.', $cookie_domain)) > 2 && !is_numeric(str_replace('.', '', $cookie_domain))) {
$this->assertRaw('_gaq.push(["_setDomainName",', '[testGoogleAnalyticsTrackingCode]: One domain with multiple subdomains is active on real host.');
$this->assertRaw('{"cookieDomain":"' . $cookie_domain . '"}', '[testGoogleAnalyticsTrackingCode]: One domain with multiple subdomains is active on real host.');
}
else {
// Special cases, Localhost and IP addresses don't show '_setDomainName'.
$this->assertNoRaw('_gaq.push(["_setDomainName",', '[testGoogleAnalyticsTrackingCode]: One domain with multiple subdomains may be active on localhost (test result is not reliable).');
$this->assertNoRaw('{"cookieDomain":"' . $cookie_domain . '"}', '[testGoogleAnalyticsTrackingCode]: One domain with multiple subdomains may be active on localhost (test result is not reliable).');
}
// Enable "Multiple top-level domains" tracking.
variable_set('googleanalytics_domain_mode', 2);
variable_set('googleanalytics_cross_domains', "www.example.com\nwww.example.net");
$this->drupalGet('');
$this->assertRaw('_gaq.push(["_setDomainName", "none"]);', '[testGoogleAnalyticsTrackingCode]: _setDomainName: "none" found. Cross domain tracking is active.');
$this->assertRaw('_gaq.push(["_setAllowLinker", true]);', '[testGoogleAnalyticsTrackingCode]: _setAllowLinker: true found. Cross domain tracking is active.');
$this->assertRaw('ga("create", "' . $ua_code . '", {"allowLinker":true', '[testGoogleAnalyticsTrackingCode]: "allowLinker" has been found. Cross domain tracking is active.');
$this->assertRaw('ga("require", "linker");', '[testGoogleAnalyticsTrackingCode]: Require linker has been found. Cross domain tracking is active.');
$this->assertRaw('ga("linker:autoLink", ["www.example.com","www.example.net"]);', '[testGoogleAnalyticsTrackingCode]: "linker:autoLink" has been found. Cross domain tracking is active.');
$this->assertRaw('"trackCrossDomains":["www.example.com","www.example.net"]', '[testGoogleAnalyticsTrackingCode]: Cross domain tracking with www.example.com and www.example.net is active.');
variable_set('googleanalytics_domain_mode', 0);
// Test whether debugging script has been enabled.
variable_set('googleanalytics_debug', 1);
$this->drupalGet('');
$this->assertRaw('//www.google-analytics.com/analytics_debug.js', '[testGoogleAnalyticsTrackingCode]: Google debugging script has been enabled.');
// Check if text and link is shown on 'Status Reports' page.
// Requires 'administer site configuration' permission.
$this->drupalGet('admin/reports/status');
$this->assertRaw(t('Google Analytics module has debugging enabled. Please disable debugging setting in production sites from the <a href="@url">Google Analytics settings page</a>.', array('@url' => url('admin/config/system/googleanalytics'))), '[testGoogleAnalyticsConfiguration]: Debugging enabled is shown on Status Reports page.');
// Test whether the BEFORE and AFTER code is added to the tracker.
variable_set('googleanalytics_codesnippet_before', '_setDetectFlash(false);');
variable_set('googleanalytics_codesnippet_after', '_gaq.push(["t2._setAccount", "UA-123456-3"]);_gaq.push(["t2._trackPageview"]);');
// Test whether debugging script has been disabled.
variable_set('googleanalytics_debug', 0);
$this->drupalGet('');
$this->assertRaw('//www.google-analytics.com/analytics.js', '[testGoogleAnalyticsTrackingCode]: Google debugging script has been disabled.');
// Test whether the CREATE and BEFORE and AFTER code is added to the tracker.
$codesnippet_create = array(
'cookieDomain' => 'foo.example.com',
'cookieName' => 'myNewName',
'cookieExpires' => 20000,
'allowAnchor' => TRUE,
'sampleRate' => 4.3,
);
variable_set('googleanalytics_codesnippet_create', $codesnippet_create);
variable_set('googleanalytics_codesnippet_before', 'ga("set", "forceSSL", true);');
variable_set('googleanalytics_codesnippet_after', 'ga("create", "UA-123456-3", {name: "newTracker"});ga("newTracker.send", "pageview");');
$this->drupalGet('');
$this->assertRaw('_setDetectFlash(false);', '[testGoogleAnalyticsTrackingCode]: Before codesnippet has been found with "Flash" detection disabled.');
$this->assertRaw('t2._setAccount', '[testGoogleAnalyticsTrackingCode]: After codesnippet with "t2" tracker has been found.');
$this->assertRaw('ga("create", "' . $ua_code . '", {"cookieDomain":"foo.example.com","cookieName":"myNewName","cookieExpires":20000,"allowAnchor":true,"sampleRate":4.3});', '[testGoogleAnalyticsTrackingCode]: Create only fields have been found.');
$this->assertRaw('ga("set", "forceSSL", true);', '[testGoogleAnalyticsTrackingCode]: Before codesnippet will force http pages to also send all beacons using https.');
$this->assertRaw('ga("create", "UA-123456-3", {name: "newTracker"});', '[testGoogleAnalyticsTrackingCode]: After codesnippet with "newTracker" tracker has been found.');
}
}
class GoogleAnalyticsCustomVariablesTest extends DrupalWebTestCase {
class GoogleAnalyticsCustomDimensionsAndMetricsTest extends DrupalWebTestCase {
public static function getInfo() {
return array(
'name' => 'Google Analytics Custom Variables tests',
'description' => 'Test custom variables functionality of Google Analytics module.',
'name' => 'Google Analytics custom dimensions and metrics tests',
'description' => 'Test custom dimensions and metrics functionality of Google Analytics module.',
'group' => 'Google Analytics',
'dependencies' => array('token'),
);
......@@ -208,99 +286,136 @@ class GoogleAnalyticsCustomVariablesTest extends DrupalWebTestCase {
$this->admin_user = $this->drupalCreateUser($permissions);
}
function testGoogleAnalyticsCustomVariables() {
function testGoogleAnalyticsCustomDimensions() {
$ua_code = 'UA-123456-3';
variable_set('googleanalytics_account', $ua_code);
// Basic test if the feature works.
$custom_vars = array(
'slots' => array(
$googleanalytics_custom_dimension = array(
'indexes' => array(
1 => array(
'slot' => 1,
'name' => 'Foo 1',
'index' => 1,
'value' => 'Bar 1',
'scope' => 3,
),
2 => array(
'slot' => 2,
'name' => 'Foo 2',
'index' => 2,
'value' => 'Bar 2',
'scope' => 2,
),
3 => array(
'slot' => 3,
'name' => 'Foo 3',
'index' => 3,
'value' => 'Bar 3',
'scope' => 3,
),
4 => array(
'slot' => 4,
'name' => 'Foo 4',
'index' => 4,
'value' => 'Bar 4',
'scope' => 2,
),
5 => array(
'slot' => 5,
'name' => 'Foo 5',
'index' => 5,
'value' => 'Bar 5',
'scope' => 1,
),
)
);
variable_set('googleanalytics_custom_var', $custom_vars);
variable_set('googleanalytics_custom_dimension', $googleanalytics_custom_dimension);
$this->drupalGet('');
foreach ($custom_vars['slots'] as $slot) {
$this->assertRaw("_gaq.push(['_setCustomVar', " . $slot['slot'] . ", \"" . $slot['name'] . "\", \"" . $slot['value'] . "\", " . $slot['scope'] . "]);", '[testGoogleAnalyticsCustomVariables]: _setCustomVar ' . $slot['slot'] . ' is shown.');
foreach ($googleanalytics_custom_dimension['indexes'] as $dimension) {
$this->assertRaw('ga("set", ' . drupal_json_encode('dimension' . $dimension['index']) . ', ' . drupal_json_encode($dimension['value']) . ');', '[testGoogleAnalyticsCustomDimensionsAndMetrics]: Dimension #' . $dimension['index'] . ' is shown.');
}
// Test whether tokens are replaced in custom variable names.
// Test whether tokens are replaced in custom dimension values.
$site_slogan = $this->randomName(16);
variable_set('site_slogan', $site_slogan);
$custom_vars = array(
'slots' => array(
$googleanalytics_custom_dimension = array(
'indexes' => array(
1 => array(
'slot' => 1,
'name' => 'Name: [site:slogan]',
'index' => 1,
'value' => 'Value: [site:slogan]',
'scope' => 3,
),
2 => array(
'slot' => 2,
'name' => '',
'index' => 2,
'value' => $this->randomName(16),
'scope' => 1,
),
3 => array(
'slot' => 3,
'name' => $this->randomName(16),
'index' => 3,
'value' => '',
'scope' => 2,
),
)
);
variable_set('googleanalytics_custom_dimension', $googleanalytics_custom_dimension);
$this->verbose('<pre>' . print_r($googleanalytics_custom_dimension, TRUE) . '</pre>');
$this->drupalGet('');
$this->assertRaw('ga("set", ' . drupal_json_encode('dimension1') . ', ' . drupal_json_encode("Value: $site_slogan") . ');', '[testGoogleAnalyticsCustomDimensionsAndMetrics]: Tokens have been replaced in dimension value.');
$this->assertRaw('ga("set", ' . drupal_json_encode('dimension2') . ', ' . drupal_json_encode($googleanalytics_custom_dimension['indexes']['2']['value']) . ');', '[testGoogleAnalyticsCustomDimensionsAndMetrics]: Random value is shown.');
$this->assertNoRaw('ga("set", ' . drupal_json_encode('dimension3') . ', ' . drupal_json_encode('') . ');', '[testGoogleAnalyticsCustomDimensionsAndMetrics]: Empty value is not shown.');
}
function testGoogleAnalyticsCustomMetrics() {
$ua_code = 'UA-123456-3';
variable_set('googleanalytics_account', $ua_code);
// Basic test if the feature works.
$googleanalytics_custom_metric = array(
'indexes' => array(
1 => array(
'index' => 1,
'value' => '6',
'value_expected' => 6,
),
2 => array(
'index' => 2,
'value' => '8000',
'value_expected' => 8000,
),
3 => array(
'index' => 3,
'value' => '7.8654',
'value_expected' => 7.8654,
),
4 => array(
'slot' => 4,
'name' => '',
'value' => '',
'scope' => 3,
'index' => 4,
'value' => '1123.4',
'value_expected' => 1123.4,
),
5 => array(
'slot' => 5,
'name' => '',
'index' => 5,
'value' => '5,67',
'value_expected' => 5,
),
)
);
variable_set('googleanalytics_custom_metric', $googleanalytics_custom_metric);
$this->drupalGet('');
foreach ($googleanalytics_custom_metric['indexes'] as $metric) {
$this->assertRaw('ga("set", ' . drupal_json_encode('metric' . $metric['index']) . ', ' . drupal_json_encode($metric['value_expected']) . ');', '[testGoogleAnalyticsCustomDimensionsAndMetrics]: Metric #' . $metric['index'] . ' is shown.');
}
// Test whether tokens are replaced in custom metric values.
$googleanalytics_custom_metric = array(
'indexes' => array(
1 => array(
'index' => 1,
'value' => '[current-user:roles:count]',
),
2 => array(
'index' => 2,
'value' => mt_rand(),
),
3 => array(
'index' => 3,
'value' => '',
'scope' => 3,
),
)
);
variable_set('googleanalytics_custom_var', $custom_vars);
$this->verbose('<pre>' . print_r($custom_vars, TRUE) . '</pre>');
variable_set('googleanalytics_custom_metric', $googleanalytics_custom_metric);
$this->verbose('<pre>' . print_r($googleanalytics_custom_metric, TRUE) . '</pre>');
$this->drupalGet('');
$this->assertRaw("_gaq.push(['_setCustomVar', 1, \"Name: $site_slogan\", \"Value: $site_slogan\", 3]", '[testGoogleAnalyticsCustomVariables]: Tokens have been replaced in custom variable.');
$this->assertNoRaw("_gaq.push(['_setCustomVar', 2,", '[testGoogleAnalyticsCustomVariables]: Value with empty name is not shown.');
$this->assertNoRaw("_gaq.push(['_setCustomVar', 3,", '[testGoogleAnalyticsCustomVariables]: Name with empty value is not shown.');
$this->assertNoRaw("_gaq.push(['_setCustomVar', 4,", '[testGoogleAnalyticsCustomVariables]: Empty name and value is not shown.');
$this->assertNoRaw("_gaq.push(['_setCustomVar', 5,", '[testGoogleAnalyticsCustomVariables]: Empty name and value is not shown.');
$this->assertRaw('ga("set", ' . drupal_json_encode('metric1') . ', ', '[testGoogleAnalyticsCustomDimensionsAndMetrics]: Tokens have been replaced in metric value.');
$this->assertRaw('ga("set", ' . drupal_json_encode('metric2') . ', ' . drupal_json_encode($googleanalytics_custom_metric['indexes']['2']['value']) . ');', '[testGoogleAnalyticsCustomDimensionsAndMetrics]: Random value is shown.');
$this->assertNoRaw('ga("set", ' . drupal_json_encode('metric3') . ', ' . drupal_json_encode('') . ');', '[testGoogleAnalyticsCustomDimensionsAndMetrics]: Empty value is not shown.');
}
}
......@@ -334,8 +449,8 @@ class GoogleAnalyticsStatusMessagesTest extends DrupalWebTestCase {
variable_set('googleanalytics_trackmessages', array('error' => 'error'));
$this->drupalPost('user/login', array(), t('Log in'));
$this->assertRaw('_gaq.push(["_trackEvent", "Messages", "Error message", "Username field is required."]);', '[testGoogleAnalyticsStatusMessages]: _trackEvent "Username field is required." is shown.');
$this->assertRaw('_gaq.push(["_trackEvent", "Messages", "Error message", "Password field is required."]);', '[testGoogleAnalyticsStatusMessages]: _trackEvent "Password field is required." is shown.');
$this->assertRaw('ga("send", "event", "Messages", "Error message", "Username field is required.");', '[testGoogleAnalyticsStatusMessages]: Event message "Username field is required." is shown.');
$this->assertRaw('ga("send", "event", "Messages", "Error message", "Password field is required.");', '[testGoogleAnalyticsStatusMessages]: Event message "Password field is required." is shown.');
// @todo: investigate why drupal_set_message() fails.
//drupal_set_message('Example status message.', 'status');
......@@ -343,10 +458,10 @@ class GoogleAnalyticsStatusMessagesTest extends DrupalWebTestCase {
//drupal_set_message('Example error message.', 'error');
//drupal_set_message('Example error <em>message</em> with html tags and <a href="http://example.com/">link</a>.', 'error');
//$this->drupalGet('');
//$this->assertNoRaw('_gaq.push(["_trackEvent", "Messages", "Status message", "Example status message."]);', '[testGoogleAnalyticsStatusMessages]: Example status message is not enabled for tracking.');
//$this->assertNoRaw('_gaq.push(["_trackEvent", "Messages", "Warning message", "Example warning message."]);', '[testGoogleAnalyticsStatusMessages]: Example warning message is not enabled for tracking.');
//$this->assertRaw('_gaq.push(["_trackEvent", "Messages", "Error message", "Example error message."]);', '[testGoogleAnalyticsStatusMessages]: Example error message is shown.');
//$this->assertRaw('_gaq.push(["_trackEvent", "Messages", "Error message", "Example error message with html tags and link."]);', '[testGoogleAnalyticsStatusMessages]: HTML has been stripped successful from Example error message with html tags and link.');
//$this->assertNoRaw('ga("send", "event", "Messages", "Status message", "Example status message.");', '[testGoogleAnalyticsStatusMessages]: Example status message is not enabled for tracking.');
//$this->assertNoRaw('ga("send", "event", "Messages", "Warning message", "Example warning message.");', '[testGoogleAnalyticsStatusMessages]: Example warning message is not enabled for tracking.');
//$this->assertRaw('ga("send", "event", "Messages", "Error message", "Example error message.");', '[testGoogleAnalyticsStatusMessages]: Example error message is shown.');
//$this->assertRaw('ga("send", "event", "Messages", "Error message", "Example error message with html tags and link.");', '[testGoogleAnalyticsStatusMessages]: HTML has been stripped successful from Example error message with html tags and link.');
}
}
......@@ -438,5 +553,135 @@ class GoogleAnalyticsRolesTest extends DrupalWebTestCase {
$this->drupalGet('');
$this->assertRaw($ua_code, '[testGoogleAnalyticsRoleVisibility]: Tracking code is displayed on frontpage for included anonymous users.');
}
}
class GoogleAnalyticsSearchTest extends DrupalWebTestCase {
public static function getInfo() {
return array(
'name' => 'Google Analytics search tests',
'description' => 'Test search functionality of Google Analytics module.',
'group' => 'Google Analytics',
);
}
function setUp() {
parent::setUp('googleanalytics', 'search', 'node');
$permissions = array(
'access administration pages',
'administer google analytics',
'search content',
);
// User to set up google_analytics.
$this->admin_user = $this->drupalCreateUser($permissions);
$this->drupalLogin($this->admin_user);
}
function testGoogleAnalyticsSearchTracking() {
$ua_code = 'UA-123456-1';
variable_set('googleanalytics_account', $ua_code);
// Check tracking code visibility.
$this->drupalGet('');
$this->assertRaw($ua_code, '[testGoogleAnalyticsSearch]: Tracking code is displayed for authenticated users.');
$this->drupalGet('search/node');
$this->assertNoRaw('ga("set", "page",', '[testGoogleAnalyticsSearch]: Custom url not set.');
// Search for random string.
$edit = array();
$edit['keys'] = $this->randomName(32);
variable_set('googleanalytics_site_search', 1);
$this->drupalPost('search/node', $edit, t('Search'));
$this->assertRaw('ga("set", "page", (window.googleanalytics_search_results) ?', '[testGoogleAnalyticsSearch]: Search results tracker is displayed.');
// Test search results counter.
$this->assertRaw('window.googleanalytics_search_results = ', '[testGoogleAnalyticsSearch]: Search results counter is displayed.');
}
}
class GoogleAnalyticsPhpFilterTest extends DrupalWebTestCase {
public static function getInfo() {
return array(
'name' => 'Google Analytics php filter tests',
'description' => 'Test php filter functionality of Google Analytics module.',
'group' => 'Google Analytics',
);
}
function setUp() {
parent::setUp('googleanalytics', 'php');
// Administrator with all permissions.
$permissions_admin_user = array(
'access administration pages',
'administer google analytics',
'use PHP for tracking visibility',
);
$this->admin_user = $this->drupalCreateUser($permissions_admin_user);
// Administrator who cannot configure tracking visibility with PHP.
$permissions_delegated_admin_user = array(
'access administration pages',
'administer google analytics',
);
$this->delegated_admin_user = $this->drupalCreateUser($permissions_delegated_admin_user);
}
function testGoogleAnalyticsPhpFilter() {
$ua_code = 'UA-123456-1';
$this->drupalLogin($this->admin_user);
$edit = array();
$edit['googleanalytics_account'] = $ua_code;
$edit['googleanalytics_visibility_pages'] = 2;
$edit['googleanalytics_pages'] = '<?php return 0; ?>';
$this->drupalPost('admin/config/system/googleanalytics', $edit, t('Save configuration'));
// Compare saved setting with posted setting.
$googleanalytics_pages = variable_get('googleanalytics_pages', $this->randomName(8));
$this->assertEqual('<?php return 0; ?>', $googleanalytics_pages, '[testGoogleAnalyticsPhpFilter]: PHP code snippet is intact.');
// Check tracking code visibility.
variable_set('googleanalytics_pages', '<?php return TRUE; ?>');
$this->drupalGet('');
$this->assertRaw('//www.google-analytics.com/analytics.js', '[testGoogleAnalyticsPhpFilter]: Tracking is displayed on frontpage page.');
$this->drupalGet('admin');
$this->assertRaw('//www.google-analytics.com/analytics.js', '[testGoogleAnalyticsPhpFilter]: Tracking is displayed on admin page.');
variable_set('googleanalytics_pages', '<?php return FALSE; ?>');
$this->drupalGet('');
$this->assertNoRaw('//www.google-analytics.com/analytics.js', '[testGoogleAnalyticsPhpFilter]: Tracking is not displayed on frontpage page.');
// Test administration form.
variable_set('googleanalytics_pages', '<?php return TRUE; ?>');
$this->drupalGet('admin/config/system/googleanalytics');
$this->assertRaw(t('Pages on which this PHP code returns <code>TRUE</code> (experts only)'), '[testGoogleAnalyticsPhpFilter]: Permission to administer PHP for tracking visibility.');
$this->assertRaw(check_plain('<?php return TRUE; ?>'), '[testGoogleAnalyticsPhpFilter]: PHP code snippted is displayed.');
// Login the delegated user and check if fields are visible.
$this->drupalLogin($this->delegated_admin_user);
$this->drupalGet('admin/config/system/googleanalytics');
$this->assertNoRaw(t('Pages on which this PHP code returns <code>TRUE</code> (experts only)'), '[testGoogleAnalyticsPhpFilter]: No permission to administer PHP for tracking visibility.');
$this->assertNoRaw(check_plain('<?php return TRUE; ?>'), '[testGoogleAnalyticsPhpFilter]: No permission to view PHP code snippted.');
// Set a different value and verify that this is still the same after the post.
variable_set('googleanalytics_pages', '<?php return 0; ?>');
$edit = array();
$edit['googleanalytics_account'] = $ua_code;
$this->drupalPost('admin/config/system/googleanalytics', $edit, t('Save configuration'));
// Compare saved setting with posted setting.
$googleanalytics_visibility_pages = variable_get('googleanalytics_visibility_pages', 0);
$googleanalytics_pages = variable_get('googleanalytics_pages', $this->randomName(8));
$this->assertEqual(2, $googleanalytics_visibility_pages, '[testGoogleAnalyticsPhpFilter]: Pages on which this PHP code returns TRUE is selected.');
$this->assertEqual('<?php return 0; ?>', $googleanalytics_pages, '[testGoogleAnalyticsPhpFilter]: PHP code snippet is intact.');
}
}
(function ($) {
/**
* This file is for developers only.
*
* This tests are made for the javascript functions used in GA module.
* These tests verify if the return values are properly working.
*
* Hopefully this can be added somewhere else once Drupal core has JavaScript
* unit testing integrated.
*/
"use strict";
Drupal.googleanalytics.test = {};
Drupal.googleanalytics.test.assertSame = function (value1, value2, message) {
if (value1 === value2) {
console.info(message);
}
else {
console.error(message);
}
}
Drupal.googleanalytics.test.assertNotSame = function (value1, value2, message) {
if (value1 !== value2) {
console.info(message);
}
else {
console.error(message);
}
}
Drupal.googleanalytics.test.assertTrue = function (value1, message) {
if (value1 === true) {
console.info(message);
}
else {
console.error(message);
}
}
Drupal.googleanalytics.test.assertFalse = function (value1, message) {
if (value1 === false) {
console.info(message);
}
else {
console.error(message);
}
}
// Run after the documented is ready or Drupal.settings is undefined.
$(document).ready(function() {
/**
* Run javascript tests against the GA module.
*/
// JavaScript debugging
var base_url = window.location.protocol + '//' + window.location.host;
var base_path = window.location.pathname;
console.dir(Drupal);
console.group("Test 'isDownload':");
Drupal.googleanalytics.test.assertFalse(Drupal.googleanalytics.isDownload(base_url + Drupal.settings.basePath + 'node/8'), "Verify that '/node/8' url is not detected as file download.");
Drupal.googleanalytics.test.assertTrue(Drupal.googleanalytics.isDownload(base_url + Drupal.settings.basePath + 'files/foo1.zip'), "Verify that '/files/foo1.zip' url is detected as a file download.");
Drupal.googleanalytics.test.assertTrue(Drupal.googleanalytics.isDownload(base_url + Drupal.settings.basePath + 'files/foo1.zip#foo'), "Verify that '/files/foo1.zip#foo' url is detected as a file download.");
Drupal.googleanalytics.test.assertTrue(Drupal.googleanalytics.isDownload(base_url + Drupal.settings.basePath + 'files/foo1.zip?foo=bar'), "Verify that '/files/foo1.zip?foo=bar' url is detected as a file download.");
Drupal.googleanalytics.test.assertTrue(Drupal.googleanalytics.isDownload(base_url + Drupal.settings.basePath + 'files/foo1.zip?foo=bar#foo'), "Verify that '/files/foo1.zip?foo=bar#foo' url is detected as a file download.");
Drupal.googleanalytics.test.assertFalse(Drupal.googleanalytics.isDownload(base_url + Drupal.settings.basePath + 'files/foo2.ddd'), "Verify that '/files/foo2.ddd' url is not detected as file download.");
console.groupEnd();
console.group("Test 'isInternal':");
Drupal.googleanalytics.test.assertTrue(Drupal.googleanalytics.isInternal(base_url + Drupal.settings.basePath + 'node/1'), "Link '" + base_url + Drupal.settings.basePath + "node/2' has been detected as internal link.");
Drupal.googleanalytics.test.assertTrue(Drupal.googleanalytics.isInternal(base_url + Drupal.settings.basePath + 'node/1#foo'), "Link '" + base_url + Drupal.settings.basePath + "node/1#foo' has been detected as internal link.");
Drupal.googleanalytics.test.assertTrue(Drupal.googleanalytics.isInternal(base_url + Drupal.settings.basePath + 'node/1?foo=bar'), "Link '" + base_url + Drupal.settings.basePath + "node/1?foo=bar' has been detected as internal link.");
Drupal.googleanalytics.test.assertTrue(Drupal.googleanalytics.isInternal(base_url + Drupal.settings.basePath + 'node/1?foo=bar#foo'), "Link '" + base_url + Drupal.settings.basePath + "node/1?foo=bar#foo' has been detected as internal link.");
Drupal.googleanalytics.test.assertTrue(Drupal.googleanalytics.isInternal(base_url + Drupal.settings.basePath + 'go/foo'), "Link '" + base_url + Drupal.settings.basePath + "go/foo' has been detected as internal link.");
Drupal.googleanalytics.test.assertFalse(Drupal.googleanalytics.isInternal('http://example.com/node/3'), "Link 'http://example.com/node/3' has been detected as external link.");
console.groupEnd();
console.group("Test 'isInternalSpecial':");
Drupal.googleanalytics.test.assertTrue(Drupal.googleanalytics.isInternalSpecial(base_url + Drupal.settings.basePath + 'go/foo'), "Link '" + base_url + Drupal.settings.basePath + "go/foo' has been detected as special internal link.");
Drupal.googleanalytics.test.assertFalse(Drupal.googleanalytics.isInternalSpecial(base_url + Drupal.settings.basePath + 'node/1'), "Link '" + base_url + Drupal.settings.basePath + "node/1' has been detected as special internal link.");
console.groupEnd();
console.group("Test 'getPageUrl':");
Drupal.googleanalytics.test.assertSame(base_path + 'node/1', Drupal.googleanalytics.getPageUrl(base_url + Drupal.settings.basePath + 'node/1'), "Absolute internal URL '" + Drupal.settings.basePath + "node/1' has been extracted from full qualified url '" + base_url + base_path + "node/1'.");
Drupal.googleanalytics.test.assertSame(base_path + 'node/1', Drupal.googleanalytics.getPageUrl(Drupal.settings.basePath + 'node/1'), "Absolute internal URL '" + Drupal.settings.basePath + "node/1' has been extracted from absolute url '" + base_path + "node/1'.");
Drupal.googleanalytics.test.assertSame('http://example.com/node/2', Drupal.googleanalytics.getPageUrl('http://example.com/node/2'), "Full qualified external url 'http://example.com/node/2' has been extracted.");
Drupal.googleanalytics.test.assertSame('//example.com/node/2', Drupal.googleanalytics.getPageUrl('//example.com/node/2'), "Full qualified external url '//example.com/node/2' has been extracted.");
console.groupEnd();
console.group("Test 'getDownloadExtension':");
Drupal.googleanalytics.test.assertSame('zip', Drupal.googleanalytics.getDownloadExtension(base_url + Drupal.settings.basePath + '/files/foo1.zip'), "Download extension 'zip' has been found in '" + base_url + Drupal.settings.basePath + "files/foo1.zip'.");
Drupal.googleanalytics.test.assertSame('zip', Drupal.googleanalytics.getDownloadExtension(base_url + Drupal.settings.basePath + '/files/foo1.zip#foo'), "Download extension 'zip' has been found in '" + base_url + Drupal.settings.basePath + "files/foo1.zip#foo'.");
Drupal.googleanalytics.test.assertSame('zip', Drupal.googleanalytics.getDownloadExtension(base_url + Drupal.settings.basePath + '/files/foo1.zip?foo=bar'), "Download extension 'zip' has been found in '" + base_url + Drupal.settings.basePath + "files/foo1.zip?foo=bar'.");
Drupal.googleanalytics.test.assertSame('zip', Drupal.googleanalytics.getDownloadExtension(base_url + Drupal.settings.basePath + '/files/foo1.zip?foo=bar#foo'), "Download extension 'zip' has been found in '" + base_url + Drupal.settings.basePath + "files/foo1.zip?foo=bar'.");
Drupal.googleanalytics.test.assertSame('', Drupal.googleanalytics.getDownloadExtension(base_url + Drupal.settings.basePath + '/files/foo2.dddd'), "No download extension found in '" + base_url + Drupal.settings.basePath + "files/foo2.dddd'.");
console.groupEnd();
// List of top-level domains: example.com, example.net
console.group("Test 'isCrossDomain' (requires cross domain configuration with 'example.com' and 'example.net'):");
if (Drupal.settings.googleanalytics.trackCrossDomains) {
console.dir(Drupal.settings.googleanalytics.trackCrossDomains);
Drupal.googleanalytics.test.assertTrue(Drupal.googleanalytics.isCrossDomain('example.com', Drupal.settings.googleanalytics.trackCrossDomains), "URL 'example.com' has been found in cross domain list.");
Drupal.googleanalytics.test.assertTrue(Drupal.googleanalytics.isCrossDomain('example.net', Drupal.settings.googleanalytics.trackCrossDomains), "URL 'example.com' has been found in cross domain list.");
Drupal.googleanalytics.test.assertFalse(Drupal.googleanalytics.isCrossDomain('www.example.com', Drupal.settings.googleanalytics.trackCrossDomains), "URL 'www.example.com' not found in cross domain list.");
Drupal.googleanalytics.test.assertFalse(Drupal.googleanalytics.isCrossDomain('www.example.net', Drupal.settings.googleanalytics.trackCrossDomains), "URL 'www.example.com' not found in cross domain list.");
}
else {
console.warn('Cross domain tracking is not enabled. Tests skipped.');
}
console.groupEnd();
});
})(jQuery);
......@@ -45,7 +45,7 @@ function googleanalytics_validate_googleanalytics_account($variable) {
// Replace all type of dashes (n-dash, m-dash, minus) with the normal dashes.
$variable['value'] = str_replace(array('–', '—', '−'), '-', $variable['value']);
if (!preg_match('/^UA-\d{4,}-\d+$/', $variable['value'])) {
if (!preg_match('/^UA-\d+-\d+$/', $variable['value'])) {
return t('A valid Google Analytics Web Property ID is case sensitive and formatted like UA-xxxxxxx-yy.');
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment