Skip to content
Snippets Groups Projects
Commit b28f33d9 authored by Matt Glaman's avatar Matt Glaman Committed by Jakob P
Browse files

Issue #3174214 by mglaman, japerry, dsnopek: Support Google Analytics 4/gtag.js

parent 74409d94
No related branches found
No related tags found
1 merge request!24Issue #3174214: Support Google Analytics 4/gtag.js
......@@ -15,19 +15,18 @@ function googleanalytics_admin_settings_form($form_state) {
);
$form['account']['googleanalytics_account'] = array(
'#title' => t('Web Property ID'),
'#title' => t('Web Property ID(s)'),
'#type' => 'textfield',
'#default_value' => variable_get('googleanalytics_account', 'UA-'),
'#default_value' => variable_get('googleanalytics_account', 'G-'),
'#size' => 20,
'#maxlength' => 20,
'#required' => TRUE,
'#description' => t('This ID is unique to each site you want to track separately, and is in the form of UA-xxxxxxx-yy. To get a Web Property ID, <a href="@analytics">register your site with Google Analytics</a>, or if you already have registered your site, go to your Google Analytics Settings page to see the ID next to every site profile. <a href="@webpropertyid">Find more information in the documentation</a>.', array('@analytics' => 'https://marketingplatform.google.com/about/analytics/', '@webpropertyid' => url('https://developers.google.com/analytics/resources/concepts/gaConceptsAccounts', array('fragment' => 'webProperty')))),
'#description' => t('This ID is unique to each site you want to track separately, and is in the form G-XXXXXXX, UA-xxxxxxx-yy, DC-XXXXXXX, or AW-XXXXXXX. To get a Web Property ID, <a href="@analytics">register your site with Google Analytics</a>, or if you already have registered your site, go to your Google Analytics Settings page to see the ID next to every site profile. <a href="@webpropertyid">Find more information in the documentation</a>. If you want to track with multiple IDs (for example, using both GA4 and UA for legacy tracking) seperate them with commas.', array('@analytics' => 'https://marketingplatform.google.com/about/analytics/', '@webpropertyid' => url('https://support.google.com/analytics/answer/10089681', array('fragment' => 'webProperty')))),
);
$form['account']['googleanalytics_premium'] = array(
'#type' => 'checkbox',
'#title' => t('Premium account'),
'#description' => t('If you are a Google Analytics Premium customer, you can use up to 200 instead of 20 custom dimensions and metrics.'),
'#title' => t('Analytics 360 (Premium) account'),
'#description' => t('If you are a Google Analytics 360 (Premium) customer, you can use up to 200 instead of 20 custom dimensions and metrics.'),
'#default_value' => variable_get('googleanalytics_premium', FALSE),
);
......@@ -87,7 +86,7 @@ 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' => 'https://support.google.com/analytics/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/10071811')),
'#states' => array(
'enabled' => array(
':input[name="googleanalytics_domain_mode"]' => array('value' => '2'),
......@@ -191,7 +190,7 @@ function googleanalytics_admin_settings_form($form_state) {
'#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 benefits of using User ID</a>.', array('@url' => 'https://support.google.com/analytics/answer/3123663')),
'#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 benefits of using User ID</a>.', array('@url' => 'https://support.google.com/analytics/answer/9213390')),
);
// Link specific configurations.
......@@ -285,7 +284,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' => 'https://support.google.com/analytics/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/9216061')) . $site_search_dependencies,
'#default_value' => variable_get('googleanalytics_site_search', FALSE),
'#disabled' => (module_exists('search') ? FALSE : TRUE),
);
......@@ -309,8 +308,8 @@ function googleanalytics_admin_settings_form($form_state) {
);
$form['tracking']['privacy']['googleanalytics_tracker_anonymizeip'] = array(
'#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.'),
'#title' => t('Anonymize visitors IP address (UA only)'),
'#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. This option has no effect in GA4, as all data is anonymized. 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', 1),
);
$form['tracking']['privacy']['googleanalytics_privacy_donottrack'] = array(
......@@ -346,6 +345,14 @@ function googleanalytics_admin_settings_form($form_state) {
'#title_display' => 'invisible',
'#type' => 'textfield',
);
$form['googleanalytics_custom_dimension']['indexes'][$i]['name'] = array(
'#default_value' => isset($googleanalytics_custom_dimension[$i]['name']) ? $googleanalytics_custom_dimension[$i]['name'] : '',
'#description' => t('The custom dimension name.'),
'#maxlength' => 255,
'#title' => t('Custom dimension name #@index', array('@index' => $i)),
'#title_display' => 'invisible',
'#type' => 'textfield',
);
$form['googleanalytics_custom_dimension']['indexes'][$i]['value'] = array(
'#default_value' => isset($googleanalytics_custom_dimension[$i]['value']) ? $googleanalytics_custom_dimension[$i]['value'] : '',
'#description' => t('The custom dimension value.'),
......@@ -377,7 +384,7 @@ function googleanalytics_admin_settings_form($form_state) {
$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')),
'#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://support.google.com/analytics/answer/9216061', '@setup_documentation' => 'https://support.google.com/analytics/answer/12229021?hl=en&ref_topic=9756175')),
'#theme' => 'googleanalytics_admin_custom_var_table',
'#title' => t('Custom metrics'),
'#tree' => TRUE,
......@@ -398,6 +405,14 @@ function googleanalytics_admin_settings_form($form_state) {
'#title_display' => 'invisible',
'#type' => 'textfield',
);
$form['googleanalytics_custom_metric']['indexes'][$i]['name'] = array(
'#default_value' => isset($googleanalytics_custom_metric[$i]['name']) ? $googleanalytics_custom_metric[$i]['name'] : '',
'#description' => t('The custom metric name.'),
'#maxlength' => 255,
'#title' => t('Custom metric name #@index', array('@index' => $i)),
'#title_display' => 'invisible',
'#type' => 'textfield',
);
$form['googleanalytics_custom_metric']['indexes'][$i]['value'] = array(
'#default_value' => isset($googleanalytics_custom_metric[$i]['value']) ? $googleanalytics_custom_metric[$i]['value'] : '',
'#description' => t('The custom metric value.'),
......@@ -464,7 +479,7 @@ function googleanalytics_admin_settings_form($form_state) {
'#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')),
'#description' => t('Enter one value per line, in the format name|value. Settings in this textarea will be added to <code>gtag("config", "G-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(
......@@ -473,7 +488,7 @@ function googleanalytics_admin_settings_form($form_state) {
'#default_value' => variable_get('googleanalytics_codesnippet_before', ''),
'#disabled' => $user_access_add_js_snippets,
'#rows' => 5,
'#description' => t('Code in this textarea will be added <strong>before</strong> <code>ga("send", "pageview");</code>.') . $user_access_add_js_snippets_permission_warning,
'#description' => t('Code in this textarea will be added <strong>before</strong> <code>gtag("event", "page_view");</code>.') . $user_access_add_js_snippets_permission_warning,
);
$form['advanced']['codesnippet']['googleanalytics_codesnippet_after'] = array(
'#type' => 'textarea',
......@@ -481,7 +496,7 @@ function googleanalytics_admin_settings_form($form_state) {
'#default_value' => variable_get('googleanalytics_codesnippet_after', ''),
'#disabled' => $user_access_add_js_snippets,
'#rows' => 5,
'#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.') . $user_access_add_js_snippets_permission_warning,
'#description' => t('Code in this textarea will be added <strong>after</strong> <code>gtag("event", "page_view");</code>. This is useful if you\'d like to track a site in two accounts.') . $user_access_add_js_snippets_permission_warning,
);
$form['advanced']['googleanalytics_debug'] = array(
......@@ -500,6 +515,7 @@ function googleanalytics_admin_settings_form($form_state) {
function googleanalytics_admin_settings_form_validate($form, &$form_state) {
// Trim custom dimensions and metrics.
foreach ($form_state['values']['googleanalytics_custom_dimension']['indexes'] as $dimension) {
$form_state['values']['googleanalytics_custom_dimension']['indexes'][$dimension['index']]['name'] = trim($dimension['name']);
$form_state['values']['googleanalytics_custom_dimension']['indexes'][$dimension['index']]['value'] = trim($dimension['value']);
// Remove empty values from the array.
if (!drupal_strlen($form_state['values']['googleanalytics_custom_dimension']['indexes'][$dimension['index']]['value'])) {
......@@ -509,6 +525,7 @@ function googleanalytics_admin_settings_form_validate($form, &$form_state) {
$form_state['values']['googleanalytics_custom_dimension'] = $form_state['values']['googleanalytics_custom_dimension']['indexes'];
foreach ($form_state['values']['googleanalytics_custom_metric']['indexes'] as $metric) {
$form_state['values']['googleanalytics_custom_metric']['indexes'][$metric['index']]['name'] = trim($metric['name']);
$form_state['values']['googleanalytics_custom_metric']['indexes'][$metric['index']]['value'] = trim($metric['value']);
// Remove empty values from the array.
if (!drupal_strlen($form_state['values']['googleanalytics_custom_metric']['indexes'][$metric['index']]['value'])) {
......@@ -528,8 +545,11 @@ function googleanalytics_admin_settings_form_validate($form, &$form_state) {
// 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+-\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.'));
$account_parts = array_filter(array_map("trim", explode(",", $form_state['values']['googleanalytics_account'])));
foreach ($account_parts as $account) {
if (!_google_analytics_valid_property_id($account)) {
form_set_error('googleanalytics_account', t('A valid Web Property ID is case sensitive and formatted like UA-xxxxxxx-yy, G-XXXXXXX, DC-XXXXXXX, or AW-XXXXXXX.'));
}
}
// If multiple top-level domains has been selected, a domain names list is required.
......@@ -579,6 +599,7 @@ function theme_googleanalytics_admin_custom_var_table($variables) {
$header = array(
array('data' => t('Index')),
array('data' => t('Name')),
array('data' => t('Value')),
);
......@@ -587,6 +608,7 @@ function theme_googleanalytics_admin_custom_var_table($variables) {
$rows[] = array(
'data' => array(
drupal_render($form['indexes'][$id]['index']),
drupal_render($form['indexes'][$id]['name']),
drupal_render($form['indexes'][$id]['value']),
),
);
......
......@@ -25,21 +25,18 @@ $(document).ready(function() {
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", {
"hitType": "event",
"eventCategory": "Downloads",
"eventAction": Drupal.googleanalytics.getDownloadExtension(this.href).toUpperCase(),
"eventLabel": Drupal.googleanalytics.getPageUrl(this.href),
"transport": "beacon"
gtag('event', Drupal.googleanalytics.getDownloadExtension(this.href).toUpperCase(), {
event_category: 'Downloads',
event_label: Drupal.googleanalytics.getPageUrl(this.href),
transport_type: 'beacon'
});
}
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", {
"hitType": "pageview",
"page": Drupal.googleanalytics.getPageUrl(this.href),
"transport": "beacon"
gtag('config', drupalSettings.google_analytics.account, {
page_path: Drupal.googleanalytics.getPageUrl(this.href),
transport_type: 'beacon'
});
}
else {
......@@ -51,24 +48,20 @@ $(document).ready(function() {
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", {
"hitType": "event",
"eventCategory": "Mails",
"eventAction": "Click",
"eventLabel": this.href.substring(7),
"transport": "beacon"
gtag('event', 'Click', {
event_category: 'Mails',
event_label: this.href.substring(7),
transport_type: 'beacon'
});
}
else if (Drupal.settings.googleanalytics.trackOutbound && this.href.match(/^\w+:\/\//i)) {
if (Drupal.settings.googleanalytics.trackDomainMode !== 2 || (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", {
"hitType": "event",
"eventCategory": "Outbound links",
"eventAction": "Click",
"eventLabel": this.href,
"transport": "beacon"
gtag('event', 'Click', {
event_category: 'Outbound links',
event_label: this.href,
transport_type: 'beacon'
});
}
else {
......@@ -85,9 +78,8 @@ $(document).ready(function() {
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", {
"hitType": "pageview",
"page": location.pathname + location.search + location.hash
gtag('config', drupalSettings.google_analytics.account, {
page_path: location.pathname + location.search + location.hash
});
};
}
......@@ -99,9 +91,8 @@ $(document).ready(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", {
"hitType": "pageview",
"page": Drupal.googleanalytics.getPageUrl(href)
gtag('config', drupalSettings.google_analytics.account, {
page_path: Drupal.googleanalytics.getPageUrl(href)
});
}
});
......
if (typeof ga !== 'function') {
function ga() {
var type = arguments[0];
if (type === 'create') {
gtag('config', arguments[1], arguments[2]);
} else if (type === 'set') {
gtag('set', arguments[1], arguments[2]);
} else if (type === 'require') {
// Anything needed in `require` is bundled into Google Tag.
}
else if (type === 'send') {
if (typeof arguments[1] === 'object') {
gtag('event', arguments[1].eventAction, {
event_category: arguments[1].eventCategory,
event_label: arguments[1].eventLabel,
transport_type: arguments[1].transport || 'beacon',
})
}
else if (arguments[1] === 'pageview') {
gtag('event', 'page_view');
}
else if (arguments.length === 2) {
gtag('event', arguments[1]);
}
else {
gtag('event', arguments[3] || '', {
event_category: arguments[2] || '',
event_label: arguments[4] || '',
})
}
} else {
gtag(arguments)
}
}
}
......@@ -65,7 +65,7 @@ function googleanalytics_requirements($phase) {
if ($phase == 'runtime') {
// Raise warning if Google user account has not been set yet.
if (!preg_match('/^UA-\d+-\d+$/', variable_get('googleanalytics_account', 'UA-'))) {
if (!_google_analytics_valid_property_id(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'))),
......
......@@ -21,43 +21,40 @@ $(document).ready(function() {
// 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.
ga("send", {
"hitType": "event",
"eventCategory": "Downloads",
"eventAction": Drupal.googleanalytics.getDownloadExtension(this.href).toUpperCase(),
"eventLabel": Drupal.googleanalytics.getPageUrl(this.href),
"transport": "beacon"
gtag('event', Drupal.googleanalytics.getDownloadExtension(this.href).toUpperCase(), {
event_category: 'Downloads',
event_label: Drupal.googleanalytics.getPageUrl(this.href),
transport_type: 'beacon'
});
}
else if (Drupal.googleanalytics.isInternalSpecial(this.href)) {
// Keep the internal URL for Google Analytics website overlay intact.
ga("send", {
"hitType": "pageview",
"page": Drupal.googleanalytics.getPageUrl(this.href),
"transport": "beacon"
// @todo: May require tracking ID
var target = this;
$.each(drupalSettings.google_analytics.account, function () {
gtag('config', this, {
page_path: Drupal.googleanalytics.getPageUrl(target.href),
transport_type: 'beacon'
});
});
}
}
else {
if (Drupal.settings.googleanalytics.trackMailto && $(this).is("a[href^='mailto:'],area[href^='mailto:']")) {
// Mailto link clicked.
ga("send", {
"hitType": "event",
"eventCategory": "Mails",
"eventAction": "Click",
"eventLabel": this.href.substring(7),
"transport": "beacon"
gtag('event', 'Click', {
event_category: 'Mails',
event_label: this.href.substring(7),
transport_type: 'beacon'
});
}
else if (Drupal.settings.googleanalytics.trackOutbound && this.href.match(/^\w+:\/\//i)) {
if (Drupal.settings.googleanalytics.trackDomainMode !== 2 || (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", {
"hitType": "event",
"eventCategory": "Outbound links",
"eventAction": "Click",
"eventLabel": this.href,
"transport": "beacon"
gtag('event', 'Click', {
event_category: 'Outbound links',
event_label: this.href,
transport_type: 'beacon'
});
}
}
......@@ -68,9 +65,10 @@ $(document).ready(function() {
// Track hash changes as unique pageviews, if this option has been enabled.
if (Drupal.settings.googleanalytics.trackUrlFragments) {
window.onhashchange = function() {
ga("send", {
"hitType": "pageview",
"page": location.pathname + location.search + location.hash
$.each(drupalSettings.google_analytics.account, function () {
gtag('config', this, {
page_path: location.pathname + location.search + location.hash
});
});
};
}
......@@ -81,9 +79,10 @@ $(document).ready(function() {
$(document).bind("cbox_complete", function () {
var href = $.colorbox.element().attr("href");
if (href) {
ga("send", {
"hitType": "pageview",
"page": Drupal.googleanalytics.getPageUrl(href)
$.each(drupalSettings.google_analytics.account, function () {
gtag('config', this, {
page_path: Drupal.googleanalytics.getPageUrl(href)
});
});
}
});
......
......@@ -113,13 +113,15 @@ function googleanalytics_page_alter(&$page) {
// 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 (preg_match('/^UA-\d+-\d+$/', $id) && (_googleanalytics_visibility_pages() || in_array($status, $trackable_status_codes)) && _googleanalytics_visibility_user($user)) {
if (_google_analytics_valid_property_id($id) && (_googleanalytics_visibility_pages() || in_array($status, $trackable_status_codes)) && _googleanalytics_visibility_user($user)) {
$id_list = array_filter(array_map("trim", explode(",", $id)));
$debug = variable_get('googleanalytics_debug', 0);
$url_custom = '';
// Add link tracking.
$link_settings = array();
$link_settings = array('account' => $id_list);
if ($track_outbound = variable_get('googleanalytics_trackoutbound', 1)) {
$link_settings['trackOutbound'] = $track_outbound;
}
......@@ -173,7 +175,10 @@ function googleanalytics_page_alter(&$page) {
if (in_array($type, $message_types)) {
foreach ($messages as $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)) . ');';
$event = array();
$event['event_category'] = t('Messages');
$event['event_label'] = strip_tags((string) $message);
$message_events .= 'gtag("event", ' . drupal_json_encode($status_heading[$type]) . ', ' . drupal_json_encode($event) . ');';
}
}
}
......@@ -221,7 +226,8 @@ function googleanalytics_page_alter(&$page) {
}
// Add custom dimensions and metrics.
$custom_var = '';
$custom_map = array();
$custom_vars = array();
foreach (array('dimension', 'metric') as $googleanalytics_custom_type) {
$googleanalytics_custom_vars = variable_get('googleanalytics_custom_' . $googleanalytics_custom_type, array());
// Are there dimensions or metrics configured?
......@@ -254,44 +260,38 @@ function googleanalytics_page_alter(&$page) {
settype($googleanalytics_custom_var['value'], 'float');
};
// Add variables to tracker.
$custom_var .= 'ga("set", ' . drupal_json_encode($googleanalytics_custom_type . $googleanalytics_custom_var['index']) . ', ' . drupal_json_encode($googleanalytics_custom_var['value']) . ');';
// Build the arrays of values.
$custom_map['custom_map'][$googleanalytics_custom_type . $googleanalytics_custom_var['index']] = $googleanalytics_custom_var['name'];
$custom_vars[$googleanalytics_custom_var['name']] = $googleanalytics_custom_var['value'];
}
}
}
// Build tracker code.
$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 = 'https://www.google-analytics.com/' . ($debug ? 'analytics_debug.js' : 'analytics.js');
// Should a local cached copy of analytics.js be used?
if (variable_get('googleanalytics_cache', 0) && $url = _googleanalytics_cache($library_tracker_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');
$custom_var = '';
if (!empty($custom_map)) {
// Add custom variables to tracker.
foreach ($id_list as $id) {
$custom_var .= 'gtag("config", ' . drupal_json_encode($id) . ', ' . drupal_json_encode($custom_map) . ');';
}
$custom_var .= 'gtag("event", "custom", ' . drupal_json_encode($custom_vars) . ');';
};
$script .= '"' . $url . $query_string . '"';
}
else {
$script .= '"' . $library_tracker_url . '"';
}
$script .= ',"ga");';
// Build tracker code.
$script = 'window.dataLayer = window.dataLayer || [];';
$script .= 'function gtag(){dataLayer.push(arguments)};';
$script .= PHP_EOL . file_get_contents(__DIR__ . '/googleanalytics.gtag-shim.js') . PHP_EOL;
$script .= 'gtag("js", new Date());';
// Developer ID for Drupal provided by Google.
$script .= 'gtag("set", "developer_id.dMDhkMT", true);';
// 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('cookieDomain' => 'auto');
// Build the arguments fields list.
// https://developers.google.com/analytics/devguides/collection/gtagjs/sending-data
$create_only_fields = array('groups' => 'default');
$create_only_fields = array_merge($create_only_fields, $codesnippet_create);
// Domain tracking type.
......@@ -302,61 +302,62 @@ function googleanalytics_page_alter(&$page) {
// 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))) {
$create_only_fields = array_merge($create_only_fields, array('cookieDomain' => $cookie_domain));
$create_only_fields = array_merge($create_only_fields, array('cookie_domain' => $cookie_domain));
$googleanalytics_adsense_script .= 'window.google_analytics_domain_name = ' . drupal_json_encode($cookie_domain) . ';';
}
elseif ($domain_mode == 2) {
// Cross Domain tracking. 'autoLinker' need to be enabled in 'create'.
$create_only_fields = array_merge($create_only_fields, array('allowLinker' => TRUE));
// Cross Domain tracking
// https://developers.google.com/analytics/devguides/collection/gtagjs/cross-domain
$create_only_fields['linker'] = array(
'domains' => $link_settings['trackCrossDomains'],
);
$googleanalytics_adsense_script .= 'window.google_analytics_domain_name = "none";';
}
// Track logged in users across all devices.
if (variable_get('googleanalytics_trackuserid', 0) && user_is_logged_in()) {
$create_only_fields['userId'] = google_analytics_user_id_hash($user->uid);
$create_only_fields['user_id'] = google_analytics_user_id_hash($user->uid);
}
// Create a tracker.
$script .= 'ga("create", ' . drupal_json_encode($id) . ', ' . drupal_json_encode($create_only_fields) .');';
if (variable_get('googleanalytics_tracker_anonymizeip', 1)) {
$create_only_fields['anonymize_ip'] = TRUE;
}
// Prepare Adsense tracking.
$googleanalytics_adsense_script .= 'window.google_analytics_uacct = ' . drupal_json_encode($id) . ';';
if (!empty($url_custom)) {
$create_only_fields['page_path'] = 'PLACEHOLDER_URL_CUSTOM';
}
// Add enhanced link attribution after 'create', but before 'pageview' send.
// @see https://support.google.com/analytics/answer/2558867
// @see https://developers.google.com/analytics/devguides/collection/gtagjs/enhanced-link-attribution
if (variable_get('googleanalytics_tracklinkid', 0)) {
$script .= 'ga("require", "linkid", "linkid.js");';
$create_only_fields['link_attribution'] = TRUE;
}
// Add display features after 'create', but before 'pageview' send.
// @see https://support.google.com/analytics/answer/2444872
// @see https://developers.google.com/analytics/devguides/collection/gtagjs/display-features
if (variable_get('googleanalytics_trackdoubleclick', 0)) {
$script .= 'ga("require", "displayfeatures");';
$create_only_fields['allow_ad_personalization_signals'] = FALSE;
}
// 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);';
}
// Convert array to JSON format.
$arguments_json = drupal_json_encode($create_only_fields);
// drupal_json_encode() cannot convert every data type properly.
$arguments_json = str_replace('"PLACEHOLDER_URL_CUSTOM"', $url_custom, $arguments_json);
if (!empty($custom_var)) {
$script .= $custom_var;
}
// Create a tracker.
if (!empty($codesnippet_before)) {
$script .= $codesnippet_before;
}
if (!empty($url_custom)) {
$script .= 'ga("set", "page", ' . $url_custom . ');';
foreach ($id_list as $id) {
$script .= 'gtag("config", ' . drupal_json_encode($id) . ', ' . $arguments_json . ');';
}
$script .= 'ga("send", "pageview");';
// Prepare Adsense tracking.
$googleanalytics_adsense_script .= 'window.google_analytics_uacct = ' . drupal_json_encode($id_list[0]) . ';';
if (!empty($custom_var)) {
$script .= $custom_var;
}
if (!empty($message_events)) {
$script .= $message_events;
}
......@@ -368,9 +369,30 @@ function googleanalytics_page_alter(&$page) {
// Custom tracking. Prepend before all other JavaScript.
// @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, 'requires_jquery' => FALSE));
$script = $googleanalytics_adsense_script . $script;
}
// Prepend tracking library directly before script code.
if ($debug) {
// Debug script has highest priority to load.
// @FIXME: Cannot find the debug URL!???
$library = 'https://www.googletagmanager.com/gtag/js?id=' . $id_list[0];
}
elseif (variable_get('googleanalytics_cache', 0) && $url = _googleanalytics_cache('https://www.googletagmanager.com/gtag/js')) {
// Should a local cached copy of gtag.js be used?
$query_string = '?' . variable_get('css_js_query_string', '0');
$library = $url . $query_string;
}
else {
// Fallback to default.
$library = 'https://www.googletagmanager.com/gtag/js?id=' . $id_list[0];
}
$options = array(
'type' => 'external',
'async' => TRUE,
);
drupal_add_js($library, $options);
drupal_add_js($script, array('scope' => 'header', 'type' => 'inline', 'requires_jquery' => FALSE));
}
}
......@@ -492,8 +514,8 @@ function googleanalytics_preprocess_search_results(&$variables) {
// There is no search result $variable available that hold the number of items
// found. But the pager item mumber can tell the number of search results.
global $pager_total_items;
drupal_add_js('window.googleanalytics_search_results = ' . intval($pager_total_items[0]) . ';', array('type' => 'inline', 'group' => JS_LIBRARY-1, 'requires_jquery' => FALSE));
$results = isset($pager_total_items[0]) ? $pager_total_items[0] : 0;
drupal_add_js('window.googleanalytics_search_results = ' . intval($results) . ';', array('type' => 'inline', 'group' => JS_LIBRARY-1, 'requires_jquery' => FALSE));
}
}
......@@ -713,3 +735,16 @@ function _googleanalytics_visibility_header($account) {
return TRUE;
}
/**
* Validate Google Analytics property IDs.
*
* @param string $property_id
* Property ID to validate.
* @return int|bool
* Whether property ID is valid.
*/
function _google_analytics_valid_property_id($property_id) {
$regex = '/^(?:(UA|G|AW|DC)-[\w-]+)(?:,\s*(?:(UA|G|AW|DC)-[\w-]+))*$/';
return !empty($property_id) && preg_match($regex, $property_id);
}
This diff is collapsed.
......@@ -13,7 +13,7 @@ function googleanalytics_variable_info($options) {
'type' => 'string',
'title' => t('Web Property ID', array(), $options),
'default' => 'UA-',
'description' => t('This ID is unique to each site you want to track separately, and is in the form of UA-xxxxxxx-yy. To get a Web Property ID, <a href="@analytics">register your site with Google Analytics</a>, or if you already have registered your site, go to your Google Analytics Settings page to see the ID next to every site profile. <a href="@webpropertyid">Find more information in the documentation</a>.', array('@analytics' => 'https://marketingplatform.google.com/about/analytics/', '@webpropertyid' => url('https://developers.google.com/analytics/resources/concepts/gaConceptsAccounts', array('fragment' => 'webProperty'))), $options),
'description' => t('This ID is unique to each site you want to track separately, and is in the form UA-xxxxxxx-yy, G-XXXXXXX, DC-XXXXXXX, or AW-XXXXXXX. To get a Web Property ID, <a href="@analytics">register your site with Google Analytics</a>, or if you already have registered your site, go to your Google Analytics Settings page to see the ID next to every site profile. <a href="@webpropertyid">Find more information in the documentation</a>.', array('@analytics' => 'https://marketingplatform.google.com/about/analytics/', '@webpropertyid' => url('https://developers.google.com/analytics/resources/concepts/gaConceptsAccounts', array('fragment' => 'webProperty'))), $options),
'required' => TRUE,
'group' => 'googleanalytics',
'localize' => TRUE,
......@@ -49,7 +49,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+-\d+$/', $variable['value'])) {
return t('A valid Google Analytics Web Property ID is case sensitive and formatted like UA-xxxxxxx-yy.');
if (!_google_analytics_valid_property_id($variable['value'])) {
return t('A valid Web Property ID is case sensitive and formatted like UA-xxxxxxx-yy, G-XXXXXXX, DC-XXXXXXX, or AW-XXXXXXX.');
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment