* Order core standard roles on top
[project/google_analytics.git] / googleanalytics.module
CommitLineData
0efd556a 1<?php
430ff5f8
MC
2// $Id$
3/*
4 * Drupal Module: GoogleAnalytics
d18f90cc 5 * Adds the required Javascript to the bottom of all your Drupal pages
0efd556a
MC
6 * to allow tracking by the Google Analytics statistics package.
7 *
834c2715 8 * @author: Mike Carter <www.ixis.co.uk/contact>
0efd556a
MC
9 */
10
c7f8ab18 11define('GA_TRACKFILES', 'pdf|zip|mp3');
430ff5f8 12
0efd556a
MC
13function googleanalytics_help($section) {
14 switch ($section) {
430ff5f8 15 case 'admin/settings/googleanalytics':
0efd556a
MC
16 return t('Google Analytics is a free statistics package based on the excellent Urchin system.');
17 }
18}
19
68102149 20function googleanalytics_perm() {
21 return array('administer google analytics');
22}
23
834c2715
MC
24function googleanalytics_menu($maycache) {
25 $items = array();
26 if ($maycache) {
27 $items[] = array(
28 'path' => 'admin/settings/googleanalytics',
5215f4f7 29 'title' => t('Google Analytics'),
25751755 30 'description' => t('Configure the settings used to generate your Google Analytics tracking code.'),
834c2715 31 'callback' => 'drupal_get_form',
3322ccb0 32 'callback arguments' => 'googleanalytics_admin_settings_form',
68102149 33 'access' => user_access('administer google analytics'),
834c2715
MC
34 'type' => MENU_NORMAL_ITEM,
35 );
36 }
37 return $items;
38}
39
a9eeae9c
MC
40/**
41 * Implementation of hook_footer() to insert Javascript at the end of the page
42 */
43function googleanalytics_footer($main = 0) {
44 global $user;
45
46 $id = variable_get('googleanalytics_account', '');
47
48 // Check if we should track the currently active user's role
bed62aa8 49 $track = _googleanalytics_track($user);
a9eeae9c 50
d18f90cc 51 // Don't track page views in the admin sections
ff2aacac 52 if ($id && (arg(0) != 'admin') && $track == TRUE) {
abae6098 53
abae6098 54 // Are we on a secure page?
722c7cfe 55 $prefix = '://www';
ff2aacac 56 if (isset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS']) && (strtolower($_SERVER['HTTPS']) != 'off')) {
abae6098
MC
57 $prefix = 's://ssl';
58 }
5215f4f7 59
9de7dbf1
MC
60 // Use the old version of Google Analytics?
61 $legacy_version = variable_get('googleanalytics_legacy_version', TRUE);
722c7cfe 62 $scope = variable_get('googleanalytics_js_scope', 'footer');
63
64 // Should a local cached copy of urchin.js or ga.js be used?
65 $js_file = ($legacy_version) ? 'urchin.js' : 'ga.js';
66 $url = 'http'. $prefix .'.google-analytics.com/'. $js_file;
67
68 if (variable_get('googleanalytics_cache', 0) && (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PUBLIC) && $source = _googleanalytics_cache($url)) {
69 drupal_add_js($source, 'module', $scope);
70 }
71 else {
ed7a080c 72 $script = 'document.write(unescape("%3Cscript src=\''. $url .'\' type=\'text/javascript\'%3E%3C/script%3E"));';
722c7cfe 73 drupal_add_js($script, 'inline', $scope);
74 }
75
76 // Download tracking
77 $path = drupal_get_path('module', 'googleanalytics');
78 if (variable_get('googleanalytics_trackfiles', TRUE) && $trackfiles_extensions = variable_get('googleanalytics_trackfiles_extensions', GA_TRACKFILES)) {
79 drupal_add_js(array('googleanalytics' => array('trackDownload' => $trackfiles_extensions, 'LegacyVersion' => $legacy_version)), 'setting', 'header');
80 drupal_add_js($path .'/downloadtracker.js', 'module', $scope);
81 }
9de7dbf1 82
d2c2fa2c 83 // Add User profile segmentation values
ff2aacac 84 if (is_array($profile_fields = variable_get('googleanalytics_segmentation', '')) && ($user->uid > 0)) {
d2c2fa2c 85
7c7ff600 86 $p = module_invoke('profile', 'load_profile', $user);
d2c2fa2c
MC
87
88 $fields = array();
ff2aacac 89 foreach ($profile_fields as $field => $title) {
d2c2fa2c
MC
90 $value = $user->$field;
91
ff2aacac 92 if (is_array($value)) {
efd252ae 93 $value = implode(',', $value);
d2c2fa2c
MC
94 }
95
abf9c24d 96 $fields[$field] = $value;
d2c2fa2c 97 }
a280452a 98
bed62aa8 99 // Only show segmentation variable if there are specified fields.
100 $segmentation = '';
ff2aacac 101 if (count($fields) > 0) {
9de7dbf1 102 if ($legacy_version) {
abf9c24d 103 $segmentation = '__utmSetVar('. drupal_to_js(implode(':', $fields)) .');';
9de7dbf1 104 } else {
abf9c24d 105 $segmentation = 'pageTracker._setVar('. drupal_to_js(implode(':', $fields)) .');';
9de7dbf1 106 }
a280452a 107 }
9de7dbf1 108 }
5215f4f7 109
bed62aa8 110 // Site search tracking support.
111 $url_custom = '';
a00742c0 112 if (module_exists('search') && variable_get('googleanalytics_site_search', FALSE) && arg(0) == 'search') {
bed62aa8 113 $keys = search_get_keys();
d94e8c78 114 $url_custom = drupal_to_js(url('search/'. arg(1), 'search='. trim($keys)));
bed62aa8 115 }
116
feca20a6 117 // Track access denied (403) and file not found (404) pages.
d94e8c78 118 if (function_exists('drupal_get_headers')) {
119 $headers = drupal_get_headers();
feca20a6 120 if (strstr($headers, 'HTTP/1.1 403 Forbidden')) {
d94e8c78 121 if ($legacy_version) {
122 // See http://www.google.com/support/analytics/bin/answer.py?answer=86928
feca20a6 123 $url_custom = '"/403.html?page=" + _udl.pathname + _udl.search';
d94e8c78 124 }
125 else {
126 // See http://www.google.com/support/analytics/bin/answer.py?answer=86927
feca20a6 127 $url_custom = '"/403.html?page=" + document.location.pathname + document.location.search + "&from=" + document.referrer';
128 }
129 }
130 elseif (strstr($headers, 'HTTP/1.1 404 Not Found')) {
131 if ($legacy_version) {
132 $url_custom = '"/404.html?page=" + _udl.pathname + _udl.search';
133 }
134 else {
d94e8c78 135 $url_custom = '"/404.html?page=" + document.location.pathname + document.location.search + "&from=" + document.referrer';
136 }
137 }
962363af 138 }
22fe8c34 139
722c7cfe 140 // Add any custom code snippets if specified
141 $codesnippet = variable_get('googleanalytics_codesnippet', '');
142
9de7dbf1 143 // Should the legacy code be used?
722c7cfe 144 $script = '';
9de7dbf1 145 if ($legacy_version) {
abf9c24d 146 $script .= '_uacct = '. drupal_to_js($id) .';';
bed62aa8 147 if (!empty($segmentation)) {
148 $script .= $segmentation;
149 }
150 if (!empty($codesnippet)) {
151 $script .= $codesnippet;
152 }
153 $script .= 'urchinTracker('. $url_custom .');';
9de7dbf1 154 }
bed62aa8 155 else {
abf9c24d 156 $script .= 'var pageTracker = _gat._getTracker('. drupal_to_js($id) .');';
bed62aa8 157 $script .= 'pageTracker._initData();';
158 if (!empty($segmentation)) {
159 $script .= $segmentation;
160 }
161 if (!empty($codesnippet)) {
162 $script .= $codesnippet;
163 }
164 $script .= 'pageTracker._trackPageview('. $url_custom .');';
bed62aa8 165 }
722c7cfe 166
167 drupal_add_js($script, 'inline', 'footer');
0efd556a
MC
168 }
169}
170
0efd556a 171/**
834c2715 172 * Implementation of hook_admin_settings() for configuring the module
0efd556a 173 */
3322ccb0 174function googleanalytics_admin_settings_form() {
74888994 175 $form['account'] = array(
f241720d
MC
176 '#type' => 'fieldset',
177 '#title' => t('Analytics Account Settings'),
178 '#collapsible' => FALSE,
179 );
74888994
MC
180
181 $form['account']['googleanalytics_account'] = array(
f241720d 182 '#type' => 'textfield',
3322ccb0 183 '#title' => t('Google Analytics account number'),
ff2aacac 184 '#default_value' => variable_get('googleanalytics_account', 'UA-'),
f241720d
MC
185 '#size' => 15,
186 '#maxlength' => 20,
187 '#required' => TRUE,
3322ccb0 188 '#description' => t('The user account number (UA-xxxxxx-x) is unique to the websites domain. You can obtain a user account from the <a href="@url">Google Analytics</a> website.', array('@url' => 'http://www.google.com/analytics/'))
f241720d 189 );
74888994 190
9de7dbf1
MC
191 $form['account']['googleanalytics_legacy_version'] = array(
192 '#type' => 'checkbox',
193 '#title' => t('Legacy Google Analytics'),
194 '#default_value' => variable_get("googleanalytics_legacy_version", TRUE),
195 '#description' => t('This will enable Legacy Google Analytics instead of most recent.<br /> Please note that Legacy Google Analytics will not receive feature updates and is not compatible with new features.'),
196 );
197
ff2aacac 198 // Render the role overview.
2cc17535 199 $form['roles'] = array(
f241720d
MC
200 '#type' => 'fieldset',
201 '#title' => t('User Role Tracking'),
202 '#collapsible' => TRUE,
9de7dbf1 203 '#description' => t('Define what user roles should be tracked by Google Analytics. <strong>Note:</strong> Drupal Admin pages are never tracked.'),
2cc17535
MC
204 );
205
ff2aacac 206 $form['roles']['googleanalytics_track__user1'] = array(
85e14433 207 '#type' => 'checkbox',
ff2aacac 208 '#title' => t('Admin (user 1)'),
9de7dbf1
MC
209 '#default_value' => variable_get('googleanalytics_track__user1', FALSE),
210 );
85e14433 211
19001973 212 $roles = _googleanalytics_user_roles();
213 foreach ($roles as $rid => $rname) {
214 $form['roles']['googleanalytics_track_'. $rid] = array(
ff2aacac 215 '#type' => 'checkbox',
19001973 216 '#title' => check_plain($rname),
217 '#default_value' => variable_get('googleanalytics_track_'. $rid, FALSE),
2cc17535
MC
218 );
219 }
220
d2c2fa2c 221 $form['segmentation'] = array(
ff2aacac 222 '#type' => 'fieldset',
223 '#title' => t('User Segmentation'),
224 '#collapsible' => TRUE,
225 '#description' => t('If your users have profile fields completed, you can track your logged in users based on a defined profile field.')
d2c2fa2c
MC
226 );
227
ff2aacac 228 if (!module_exists('profile')) {
d2c2fa2c 229 $form['segmentation']['profile'] = array(
edc7799a 230 '#type' => 'markup',
ff2aacac 231 '#value' => t('You need to activate the !profile to use this feature.', array('!profile' => l(t('Profile module'), 'admin/build/modules'))),
edc7799a
MC
232 '#prefix' => '<p>',
233 '#suffix' => '</p>'
234 );
ff2aacac 235 }
236 else {
d2c2fa2c 237 // Compile a list of fields to show.
ff2aacac 238 $fields = array(
239 'uid' => t('User ID'),
240 'name' => t('Username'),
241 'roles' => t('User Roles')
242 );
d2c2fa2c
MC
243 $result = db_query('SELECT name, title, type, weight FROM {profile_fields} ORDER BY weight');
244 while ($record = db_fetch_object($result)) {
245 $fields[$record->name] = $record->title;
246 }
247
ff2aacac 248 $form['segmentation']['googleanalytics_segmentation'] = array(
d2c2fa2c
MC
249 '#type' => 'select',
250 '#title' => t('Track'),
7c7ff600 251 '#description' => t('Selecting one or more values allows you to track users by profile values rather than simply an IP address. To select multiple items, hold down CTRL whilst selecting fields.'),
ff2aacac 252 '#default_value' => variable_get('googleanalytics_segmentation', ''),
d2c2fa2c
MC
253 '#options' => $fields,
254 '#size' => 10,
255 '#multiple' => TRUE
256 );
257 }
5215f4f7 258
1b65d6f6 259 $form['linktracking'] = array(
260 '#type' => 'fieldset',
261 '#title' => t('Link tracking'),
262 '#collapsible' => TRUE,
263 '#collapsed' => FALSE,
264 );
265 $form['linktracking']['googleanalytics_trackfiles'] = array(
266 '#type' => 'checkbox',
267 '#title' => t('Track files'),
268 '#default_value' => variable_get('googleanalytics_trackfiles', TRUE),
269 '#description' => t('Enables tracking of files based on below file extensions list.')
270 );
271 $form['linktracking']['googleanalytics_trackfiles_extensions'] = array(
f241720d
MC
272 '#type' => 'textfield',
273 '#title' => t('File Extensions To Track'),
1b65d6f6 274 '#default_value' => variable_get('googleanalytics_trackfiles_extensions', GA_TRACKFILES),
f58e4f22 275 '#description' => t('A pipe separated list of file extensions that should be tracked when clicked. Example: !extensions', array('!extensions' => GA_TRACKFILES))
f241720d
MC
276 );
277
d2c2fa2c 278 $form['advanced'] = array(
ff2aacac 279 '#type' => 'fieldset',
280 '#title' => t('Advanced'),
281 '#collapsible' => TRUE,
282 '#collapsed' => TRUE,
22fe8c34 283 '#description' => t('You can add custom Google Analytics code here.')
d2c2fa2c 284 );
962363af
MC
285
286 $form['advanced']['googleanalytics_cache'] = array(
287 '#type' => 'checkbox',
bed62aa8 288 '#title' => t('Cache tracking code file locally'),
191b62f7 289 '#description' => t("If checked, the tracking code file is received from Google Analytics and cached locally. It is updated daily from Google's servers to ensure updates to tracking code are reflected in the local copy. Do not activate this until after Google Analytics has confirmed your tracker."),
962363af
MC
290 '#default_value' => variable_get('googleanalytics_cache', 0),
291 );
292 if (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PRIVATE) {
293 $form['advanced']['googleanalytics_cache']['#disabled'] = TRUE;
294 $form['advanced']['googleanalytics_cache']['#description'] .= ' '. t('<a href="!url">Public file transfers</a> must be enabled to allow local caching.', array('!url' => url('admin/settings/file-system', drupal_get_destination())));
295 }
296
bed62aa8 297 $site_search_dependencies = '<div class="admin-dependencies">';
298 $site_search_dependencies .= t('Depends on: !dependencies', array('!dependencies' => (module_exists('search') ? 'Search'. t(' (<span class="admin-enabled">enabled</span>)') : 'Search'. t(' (<span class="admin-disabled">disabled</span>)'))));
299 $site_search_dependencies .= '</div>';
300
301 // Google already have many translations, if not - they display a note to change the language.
27ce6487 302 global $locale;
bed62aa8 303 $form['advanced']['googleanalytics_site_search'] = array(
304 '#type' => 'checkbox',
305 '#title' => t('Do Track Internal Search'),
ae036902 306 '#description' => t('If checked, internal search keywords are tracked. You must configure your Google account to use the internal query parameter <em>search</em>. For more information see <a href="!url">How do I set up Site Search for my profile</a>.', array('!url' => 'http://www.google.com/support/analytics/bin/answer.py?hl='. $locale .'&amp;answer=75817')) . $site_search_dependencies,
bed62aa8 307 '#default_value' => variable_get('googleanalytics_site_search', FALSE),
308 '#disabled' => (module_exists('search') ? FALSE : TRUE),
309 );
310
d2c2fa2c 311 $form['advanced']['googleanalytics_codesnippet'] = array(
ff2aacac 312 '#type' => 'textarea',
a7c0e641 313 '#title' => t('Custom JavaScript Code'),
ff2aacac 314 '#default_value' => variable_get('googleanalytics_codesnippet', ''),
d96499f1 315 '#rows' => 5,
316 '#description' => t('You can add custom Google Analytics <a href="@snippets">code snippets</a> here. These will be added to every page that Google Analytics appears on. <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'))
ff2aacac 317 );
d2c2fa2c 318
722c7cfe 319 $form['advanced']['googleanalytics_js_scope'] = array(
320 '#type' => 'select',
321 '#title' => t('JavaScript scope'),
322 '#description' => t("<strong>Warning:</strong> Google recommends adding the external JavaScript files to footer for performance reasons."),
323 '#options' => array(
324 'footer' => t('Footer'),
325 'header' => t('Header'),
326 ),
327 '#default_value' => variable_get('googleanalytics_js_scope', 'footer'),
328 );
329
834c2715 330 return system_settings_form($form);
570957cf
MC
331}
332
3322ccb0 333function googleanalytics_admin_settings_form_validate($form_id, $form_values) {
334 if (!preg_match('/^UA-\d{4,}-\d+$/', $form_values['googleanalytics_account'])) {
335 form_set_error('googleanalytics_account', t('A valid Google Analytics account number is case sensitive and formated like UA-xxxxxx-x.'));
336 }
337}
338
570957cf
MC
339/**
340 * Implementation of hook_requirements().
341 */
342function googleanalytics_requirements($phase) {
343 $requirements = array();
344
345 if ($phase == 'runtime') {
346 // Raise warning if Google user account has not been set yet.
3322ccb0 347 if (!preg_match('/^UA-\d{4,}-\d+$/', variable_get('googleanalytics_account', 'UA-'))) {
570957cf
MC
348 $requirements['googleanalytics'] = array(
349 'title' => t('Google Analytics module'),
350 '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/settings/googleanalytics'))),
351 'severity' => REQUIREMENT_ERROR,
352 'value' => t('Not configured'),
353 );
354 }
355 }
356
357 return $requirements;
962363af
MC
358}
359
962363af
MC
360/**
361 * Implementation of hook_cron().
362 */
363function googleanalytics_cron() {
df8370fd 364 // Regenerate the google analytics urchin.js or ga.js every day.
365 if (time() - variable_get('googleanalytics_last_cache', 0) >= 86400) {
bed62aa8 366 // Legacy google analytics version.
962363af 367 file_delete(file_directory_path() .'/googleanalytics/urchin.js');
bed62aa8 368
369 // New google analytics version.
370 file_delete(file_directory_path() .'/googleanalytics/ga.js');
df8370fd 371 variable_set('googleanalytics_last_cache', time());
962363af
MC
372 }
373}
374
962363af
MC
375/**
376 * Download and cache the urchin.js file locally.
377 * @param $location
378 * The full URL to the external javascript file.
379 * @return mixed
380 * The path to the local javascript file on success, boolean FALSE on failure.
381 */
bed62aa8 382function _googleanalytics_cache($location = 'http://www.google-analytics.com/urchin.js') {
ff2aacac 383 $directory = file_directory_path() .'/googleanalytics';
962363af
MC
384 $file_destination = $directory .'/'. basename($location);
385 if (!file_exists($file_destination)) {
386 $result = drupal_http_request($location);
387 if ($result->code == 200) {
388 // Check that the files directory is writable
389 if (file_check_directory($directory, FILE_CREATE_DIRECTORY)) {
390 return file_save_data($result->data, $directory .'/'. basename($location), FILE_EXISTS_REPLACE);
391 }
392 }
393 }
394 else {
395 return $file_destination;
396 }
397}
bed62aa8 398
399/**
400 *
401 * @param $account
402 * A user object containing an array of roles to check
403 * @return boolean
404 * A decision on if the current user is being tracked by GAnalytics
405 */
406function _googleanalytics_track($account) {
407 // By default we don't track users.
408 $track = FALSE;
409
410 foreach (array_keys($account->roles) as $role) {
411 // Add the tracking code if user is member of one role that should be tracked.
412 if (variable_get('googleanalytics_track_'. $role, FALSE)) {
413 $track = TRUE;
414 }
415 }
416
417 // Handle behavior for administrative user 1.
418 if ($account->uid == 1 && variable_get('googleanalytics_track__user1', FALSE)) {
419 // Enable tracking of user 1 if tracking for "authenticated user" is disabled.
420 $track = TRUE;
421 }
422 elseif ($account->uid == 1 && !variable_get('googleanalytics_track__user1', FALSE)) {
423 // User 1 is a member of "authenticated user". Disable user tracking
424 // if user 1 shouldn't, but "authenticated user" should be tracked.
425 $track = FALSE;
426 }
427
428 return $track;
429}
19001973 430
431/**
432 * D6 backport orders core standard roles on top and translate core roles.
433 */
434function _googleanalytics_user_roles() {
435 // System roles take the first two positions.
436 $roles = array(
437 DRUPAL_ANONYMOUS_RID => NULL,
438 DRUPAL_AUTHENTICATED_RID => NULL,
439 );
440
441 $result = db_query('SELECT * FROM {role} ORDER BY name');
442
443 while ($role = db_fetch_object($result)) {
444 switch ($role->rid) {
445 // We only translate the built in role names
446 case DRUPAL_ANONYMOUS_RID:
447 $roles[$role->rid] = t($role->name);
448 break;
449 case DRUPAL_AUTHENTICATED_RID:
450 $roles[$role->rid] = t($role->name);
451 break;
452 default:
453 $roles[$role->rid] = $role->name;
454 }
455 }
456
457 // Filter to remove unmatched system roles.
458 return array_filter($roles);
459}