You are here

better_statistics.module in Better Statistics 7

Same filename and directory in other branches
  1. 8 better_statistics.module
  2. 6 better_statistics.module

Drupal hook implementations for the Better Statistics module.

File

better_statistics.module
View source
<?php

/**
 * @file
 * Drupal hook implementations for the Better Statistics module.
 */

/**
 * Current and minimum Statistics API version. For internal use only!
 */
define('BETTER_STATISTICS_API_VERSION', 1.0);
define('BETTER_STATISTICS_API_MIN_VERSION', 1.0);

/**
 * Accesslog mode-type constants.
 */
define('BETTER_STATISTICS_ACCESSLOG_DISABLED', 0);
define('BETTER_STATISTICS_ACCESSLOG_DEFAULT', 1);
define('BETTER_STATISTICS_ACCESSLOG_MIXED', 2);
define('BETTER_STATISTICS_ACCESSLOG_CLIENT', 3);

/**
 * Content counter mode constants.
 */
define('BETTER_STATISTICS_ENTITY_VIEW_DISABLED', 0);
define('BETTER_STATISTICS_ENTITY_VIEW_DEFAULT', 1);
define('BETTER_STATISTICS_ENTITY_VIEW_CLIENT', 2);

/**
 * Implements hook_menu().
 */
function better_statistics_menu() {
  $items['statistics/ajax/%'] = array(
    'title' => 'Statistics AJAX handler',
    'page callback' => 'better_statistics_ajax_handler',
    'file' => 'better_statistics.ajax.inc',
    'page arguments' => array(
      2,
    ),
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
  );
  return $items;
}

/**
 * Implements hook_menu_alter().
 *
 * Redirects core's link to access log details page.
 */
function better_statistics_menu_alter(&$items) {
  $items['admin/reports/access/%']['page callback'] = 'better_statistics_access_log';
  $items['admin/reports/access/%']['module'] = 'better_statistics';
  $items['admin/reports/access/%']['file'] = 'better_statistics.admin.inc';
}

/**
 * Implements hook_schema_alter().
 *
 * Reflects the customizations chosen by the user in the schema.
 */
function better_statistics_schema_alter(&$schema) {

  // Fetch the fields defined by this module.
  $fields = variable_get('better_statistics_fields', better_statistics_get_default_fields());

  // For each field defined, add it to the schema.
  foreach ($fields as $field => $data) {
    $schema['accesslog']['fields'][$field] = $data['schema'];
  }
}

/**
 * Implements hook_module_implements_alter().
 *
 * Ensures this module fires before Statistics' exit hook so that we don't
 * double up on data. Note we're not removing Statistics' hook entirely because
 * Statistics also handles node count data.
 */
function better_statistics_module_implements_alter(&$implementations, $hook) {
  if ($hook == 'exit') {

    // Ensure our hook fires before statistics.
    $implementations['better_statistics'] = -1;
  }
}

/**
 * Implements hook_ctools_plugin_api_hook_name().
 *
 * Report to CTools that we use hook_statistics_api instead of
 * hook_ctools_plugin_api().
 */
function better_statistics_ctools_plugin_api_hook_name() {
  return 'statistics_api';
}

/**
 * Implements hook_exit().
 *
 * Gathers additional data and inserts our data into accesslog.
 */
function better_statistics_exit() {

  // If statistics access log is set to run, run our code.
  $mode = variable_get('statistics_enable_access_log', BETTER_STATISTICS_ACCESSLOG_DISABLED);
  $default_mode = $mode == BETTER_STATISTICS_ACCESSLOG_DEFAULT;
  $mixed_mode = $mode == BETTER_STATISTICS_ACCESSLOG_MIXED;
  $client_mode = $mode == BETTER_STATISTICS_ACCESSLOG_CLIENT;
  if ($default_mode || $mixed_mode) {

    // Force a recheck on cache status, just in case this function was called
    // before the X-Drupal-Cache header was set.
    $cache_status = better_statistics_served_from_cache(TRUE);

    // @see statistics_exit()
    drupal_bootstrap(DRUPAL_BOOTSTRAP_SESSION);
    include_once DRUPAL_ROOT . '/includes/unicode.inc';

    // Load all relevant module.statistics.inc files for hook invocations.
    better_statistics_load_active_incs();

    // Allow all modules to react before logging begins.
    module_invoke_all('better_statistics_prelog');

    // Log the request if the request is loggable.
    if (better_statistics_request_is_loggable()) {

      // Get all declared fields and their computed values.
      $fields = better_statistics_get_fields_data();

      // Allow modules to log statistics data.
      module_invoke_all('better_statistics_log', $fields);
    }
  }

  // Keep statistics from writing additional data to the accesslog.
  global $conf;
  $conf['statistics_enable_access_log'] = FALSE;

  // If client-side or mixed-mode are set, disable server-side node counter.
  if ($mixed_mode || $client_mode) {
    $conf['statistics_count_content_views'] = FALSE;
  }
}

/**
 * Implements hook_preprocess_html().
 *
 * If accesslog mode is set to client-side only or mixed mode, we add JS when
 * HTML output is being rendered so that client-side data collection can be used
 * for access statistics and/or node hit statistics.
 */
function better_statistics_preprocess_html(&$vars) {
  $default_js = '';
  $bs = variable_get('better_statistics_bs_var', 'bs');
  $bs_path = drupal_get_path('module', 'better_statistics');

  // If the accesslog is set to client-side or mixed mode, do some processing.
  $al_mode = variable_get('statistics_enable_access_log', BETTER_STATISTICS_ACCESSLOG_DISABLED);
  if ($al_mode == BETTER_STATISTICS_ACCESSLOG_MIXED || $al_mode == BETTER_STATISTICS_ACCESSLOG_CLIENT) {

    // Grab all declared JS from Better Statistics fields and add them here
    // at the JS_DEFAULT group level in such a way that they are all
    // aggregated together.
    $fields = variable_get('better_statistics_fields', better_statistics_get_default_fields());
    $options = array(
      'scope' => 'header',
      'group' => JS_DEFAULT,
      'every_page' => FALSE,
    );
    foreach ($fields as $field) {
      if (isset($field['js']['data'])) {
        $data = $field['js']['data'];
        unset($field['js']['data']);
        drupal_add_js($data, array_merge($field['js'], $options));
      }
    }

    // Add BS JS API accesslog method. Note that internal path should always
    // be the same for a page even when cached, so we manually add it here.
    $default_js .= $bs . "('set', 'path', '" . truncate_utf8($_GET['q'], 255) . "'); ";
    $default_js .= $bs . "('accesslog'); ";

    // Prevent access statistics from being collected in hook_exit() so that
    // duplicate statistics are never gathered when in mixed mode.
    global $conf;
    $conf['statistics_enable_access_log'] = BETTER_STATISTICS_ACCESSLOG_DISABLED;
  }

  // If enabled, add code to JS API implementation for the node counter.
  // @see statistics_exit()
  $ev_mode = variable_get('statistics_count_content_views', BETTER_STATISTICS_ENTITY_VIEW_DISABLED);
  if ($ev_mode == BETTER_STATISTICS_ENTITY_VIEW_CLIENT) {

    // We are counting content views.
    if (arg(0) == 'node' && is_numeric(arg(1)) && arg(2) == NULL) {

      // Add a call to the method.
      $default_js .= $bs . "('entity_view', 'node', " . arg(1) . "); ";

      // Prevent the node counter from being triggered in hook_exit() so that
      // duplicate statistics are never gathered when in mixed mode.
      global $conf;
      $conf['statistics_count_content_views'] = FALSE;
    }
  }

  // If any BS JS API methods have been added to the request, add the API.
  $api_added = better_statistics_add_js_api();

  // Add a default implementation of the BS JS API.
  if ($api_added && !empty($default_js)) {
    $default_js = preg_replace('!\\s+!', ' ', $default_js);
    drupal_add_js($default_js, array(
      'type' => 'inline',
      'group' => JS_THEME,
      'weight' => 25,
    ));
  }
}

/**
 * Implements hook_views_api().
 */
function better_statistics_views_api() {
  return array(
    'api' => 3,
    'path' => drupal_get_path('module', 'better_statistics') . '/views',
  );
}

/**
 * Implements hook_flush_caches().
 *
 * When all caches are flushed, detect any updates we need to make on active
 * fields. This is implemented not to declare a cache table for flushing, but to
 * react when all caches are flushed.
 */
function better_statistics_flush_caches() {
  module_load_include('inc', 'better_statistics', 'better_statistics.admin');
  _better_statistics_update_fields();
  _better_statistics_update_methods();
  return array();
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * @see _better_statistics_form_statistics_settings_form_alter()
 */
function better_statistics_form_statistics_settings_form_alter(&$form, &$form_state, $form_id) {
  module_load_include('inc', 'better_statistics', 'better_statistics.admin');
  _better_statistics_form_statistics_settings_form_alter($form, $form_state, $form_id);
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * @see _better_statistics_form_system_performance_settings_form_alter()
 */
function better_statistics_form_system_performance_settings_alter(&$form, &$form_state, $form_id) {
  module_load_include('inc', 'better_statistics', 'better_statistics.admin');
  _better_statistics_form_system_performance_settings_alter($form, $form_state, $form_id);
}

/**
 * Submit handler for the Better Statistics configuration form.
 *
 * @see _better_statistics_settings_form_submit()
 */
function better_statistics_settings_form_submit($form, &$form_state) {
  module_load_include('inc', 'better_statistics', 'better_statistics.admin');
  _better_statistics_settings_form_submit($form, $form_state);
}

/**
 * Implements hook_statistics_api().
 */
function better_statistics_statistics_api() {
  return array(
    // In your module, do not use this constant!
    'api' => BETTER_STATISTICS_API_VERSION,
    'path' => drupal_get_path('module', 'better_statistics'),
  );
}

/**
 * Returns default fields in the exact same form expected of fields exposed via
 * hook_better_statistics_fields().
 */
function better_statistics_get_default_fields() {
  static $default_fields;
  if (!isset($default_fields)) {

    // In some contexts, drupal_get_schema_unproccessed may be unavailable, so
    // here, we get the accesslog schema the same way it does.
    require_once DRUPAL_ROOT . '/modules/statistics/statistics.install';
    $schema = module_invoke('statistics', 'schema');

    // Expose Drupal Core's accesslog fields to our own API.
    $accesslog = $schema['accesslog'];
    foreach ($accesslog['fields'] as $field => $schema) {
      $default_fields[$field] = array(
        'schema' => $schema,
        'views_field' => FALSE,
        'callback' => 'better_statistics_get_field_value',
        'js' => array(
          'data' => dirname(drupal_get_filename('module', 'better_statistics')) . '/js/fields/core.js',
          'type' => 'file',
        ),
      );
    }
  }
  return $default_fields;
}

/**
 * Returns data for all valid, declared fields in preparation for DB insertion.
 */
function better_statistics_get_fields_data() {
  $fields = variable_get('better_statistics_fields', better_statistics_get_default_fields());
  $field_data = array();

  // Loop through all custom fields.
  foreach ($fields as $field => $data) {

    // Only insert data into this field if there's a callback to get data.
    if (is_callable($data['callback'])) {
      $field_data[$field] = $data['callback']($field);
    }
  }

  // The AID field should be removed in order to auto-increment.
  unset($field_data['aid']);
  return $field_data;
}

/**
 * Loads module.statistics.inc files for all active fields.
 */
function better_statistics_load_active_incs() {

  // Prevent this function from loading it multiple times.
  static $loaded;
  if (empty($loaded)) {
    $loaded = TRUE;

    // Include this module's API file because of how core fields are implemented.
    require_once dirname(__FILE__) . '/better_statistics.statistics.inc';

    // Loop through each file stored in the active incs list and include it.
    // These are stored in a variable rather than being assembled dynamically
    // due to the nature of cached pages and their bootstrap status.
    $incs = variable_get('better_statistics_active_incs', array());
    foreach ($incs as $file) {

      // If there's an inc file and it hasn't been loaded yet, load it.
      if (file_exists($file)) {
        require_once $file;
      }
    }
  }
}

/**
 * Determines the loggability of the current request.
 *
 * @return
 *   TRUE if the current page can be logged, FALSE otherwise.
 */
function better_statistics_request_is_loggable($allow_logging = NULL) {
  $allow_logging_static =& drupal_static(__FUNCTION__, TRUE);
  if (isset($allow_logging)) {
    $allow_logging_static = $allow_logging;
  }
  return $allow_logging_static;
}

/**
 * Determines whether or not the current request is being served from cache.
 *
 * Note that you may need to use === to determine the cache status of the page,
 * because some requests will return NULL, which evaluates to FALSE.
 *
 * @param boolean $recheck
 *   (optional) If set to TRUE, cache status will be manually re-checked.
 *
 * @return
 *   A boolean TRUE if the page is being served from cache, FALSE if the page is
 *   cacheable, but not served from cache. NULL is returned in the case when the
 *   page was never cacheable to begin with.
 */
function better_statistics_served_from_cache($recheck = FALSE) {
  static $cached;
  if (!isset($cached) || $recheck) {
    $headers = headers_list();
    $hit = array_search('X-Drupal-Cache: HIT', $headers);
    $miss = array_search('X-Drupal-Cache: MISS', $headers);

    // If neither a hit nor miss header are present, the page wasn't cacheable
    // to begin with. Return NULL.
    if ($hit !== FALSE) {
      $cached = TRUE;
    }
    elseif ($miss !== FALSE) {
      $cached = FALSE;
    }
    else {
      $cached = NULL;
    }
  }
  return $cached;
}

/**
 * Add the Better Statistics JS API file to the current request.
 *
 * @return
 *   TRUE if the BS JS API file was added successfully to the request, or FALSE
 *   if the API file was not added to the request.
 */
function better_statistics_add_js_api() {
  static $js_added = FALSE;
  $file =& drupal_static(__FUNCTION__, FALSE);
  $methods = variable_get('better_statistics_methods', array());

  // No need to run this if no methods have been added.
  if (!empty($methods)) {

    // If the file URI isn't yet known, attempt to build the file.
    if (!$file) {

      // Prepend the BS JS API init file to the methods array.
      $bs_path = drupal_get_path('module', 'better_statistics');
      array_unshift($methods, $bs_path . '/js/better_statistics.init.js');

      // Append the BS JS API exec file to the methods array.
      $methods[] = $bs_path . '/js/better_statistics.exec.js';

      // Prepare all files for use in drupal_build_js_cache().
      $js_files = array();
      foreach ($methods as $file) {
        $js_files[$file] = array(
          'preprocess' => TRUE,
          'data' => $file,
        );
      }

      // Build the JS cache file.
      $uri = drupal_build_js_cache($js_files);

      // If the file was created successfully, generate the URL.
      if ($uri) {
        $file = file_create_url($uri);
      }
    }

    // If the JS hasn't been added yet, add it to the request.
    if (!$js_added) {
      $bs = variable_get('better_statistics_bs_var', 'bs');
      $js = preg_replace('!\\s+!', ' ', "(function(w,d,s,u,v,a,m) {w['BetterStatsObj'] = v;\n        w[v] = w[v] ||\n        function() {(w[v].q = w[v].q || []).push(arguments)};\n        w[v]('set', 'i', 1 * new Date());\n        w[v]('set', 'basePath', '" . base_path() . "');\n        a=d.createElement(s),\n        m=d.getElementsByTagName(s)[0];\n        a.src=u;\n        a.async=1;\n        m.parentNode.insertBefore(a,m);})(window,document,'script','" . $file . "','" . $bs . "');");
      drupal_add_js($js, array(
        'type' => 'inline',
        'scope' => 'header',
        'group' => JS_LIBRARY,
        'weight' => -20,
      ));
      $js_added = TRUE;
    }
  }
  return $js_added;
}

// Declare API compatibility on behalf of core modules:
if (!function_exists('node_statistics_api')) {
  function node_statistics_api() {
    $api = better_statistics_statistics_api();
    $api['path'] = $api['path'] . '/modules';
    return $api;
  }
}
if (!function_exists('user_statistics_api')) {
  function user_statistics_api() {
    $api = better_statistics_statistics_api();
    $api['path'] = $api['path'] . '/modules';
    return $api;
  }
}
if (!function_exists('taxonomy_statistics_api')) {
  function taxonomy_statistics_api() {
    $api = better_statistics_statistics_api();
    $api['path'] = $api['path'] . '/modules';
    return $api;
  }
}

Functions

Namesort descending Description
better_statistics_add_js_api Add the Better Statistics JS API file to the current request.
better_statistics_ctools_plugin_api_hook_name Implements hook_ctools_plugin_api_hook_name().
better_statistics_exit Implements hook_exit().
better_statistics_flush_caches Implements hook_flush_caches().
better_statistics_form_statistics_settings_form_alter Implements hook_form_FORM_ID_alter().
better_statistics_form_system_performance_settings_alter Implements hook_form_FORM_ID_alter().
better_statistics_get_default_fields Returns default fields in the exact same form expected of fields exposed via hook_better_statistics_fields().
better_statistics_get_fields_data Returns data for all valid, declared fields in preparation for DB insertion.
better_statistics_load_active_incs Loads module.statistics.inc files for all active fields.
better_statistics_menu Implements hook_menu().
better_statistics_menu_alter Implements hook_menu_alter().
better_statistics_module_implements_alter Implements hook_module_implements_alter().
better_statistics_preprocess_html Implements hook_preprocess_html().
better_statistics_request_is_loggable Determines the loggability of the current request.
better_statistics_schema_alter Implements hook_schema_alter().
better_statistics_served_from_cache Determines whether or not the current request is being served from cache.
better_statistics_settings_form_submit Submit handler for the Better Statistics configuration form.
better_statistics_statistics_api Implements hook_statistics_api().
better_statistics_views_api Implements hook_views_api().

Constants