You are here

user_stats.module in User Stats 6

Same filename and directory in other branches
  1. 5 user_stats.module
  2. 7 user_stats.module

User Stats provides commonly requested user statistics for themers. These are:

  • days registered;
  • join date;
  • days since last login;
  • days since last post;
  • post count;
  • login count;
  • user online/offline;
  • IP address;

Note for hackers: function parameters should go in the order $op/$type, $uid, $data (where applicable).

File

user_stats.module
View source
<?php

/**
 * @file
 * User Stats provides commonly requested user statistics for themers.
 * These are:
 *  - days registered;
 *  - join date;
 *  - days since last login;
 *  - days since last post;
 *  - post count;
 *  - login count;
 *  - user online/offline;
 *  - IP address;
 *
 * Note for hackers: function parameters should go in the order
 * $op/$type, $uid, $data (where applicable).
 */

/**
 * Implementation of hook_perm().
 */
function user_stats_perm() {
  return array(
    'administer user stats',
    'View statistics',
    'View IP addresses',
  );
}

/**
 * Implementation of hook_menu().
 */
function user_stats_menu() {
  $items = array();

  // Admin settings
  $items['admin/settings/user_stats'] = array(
    'title' => 'User Stats settings',
    'description' => 'Configuration of user stats module options.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'user_stats_admin_settings',
    ),
    'access arguments' => array(
      'administer user stats',
    ),
    'file' => 'user_stats.admin.inc',
    'type' => MENU_NORMAL_ITEM,
  );
  $items['admin/settings/user_stats/reset_post_count'] = array(
    'title' => 'reset user post stats',
    'page callback' => 'user_stats_reset_post_count',
    'access arguments' => array(
      'administer user stats',
    ),
    'file' => 'user_stats.admin.inc',
    'type' => MENU_CALLBACK,
  );
  $items['admin/settings/user_stats/reset_login_count'] = array(
    'title' => 'reset user login stats',
    'page callback' => 'user_stats_reset_login_count',
    'access arguments' => array(
      'administer user stats',
    ),
    'file' => 'user_stats.admin.inc',
    'type' => MENU_CALLBACK,
  );
  return $items;
}

/**
 * Returns user stats.
 *
 * @param $type
 *   The statistic to return. Possible values are:
 *   - "ip_address"
 *   - "join_date"
 *   - "login_count"
 *   - "login_days"
 *   - "post_count"
 *   - "post_days"
 *   - "reg_days"
 *   - "online"
 *   - "profile"
 * @param $uid
 *   The user id who's stats should be retrieved.
 *
 * @return
 *   The statistic requested. Every statistic except join_date, online and IP address is a numeric.
 *   Join date is a string, whilst online is a boolean and IP Address a string.
 *   Note: if $type = "post_days" and the user hasn't posted any content (of the
 *   counted types) then 'n/a' is returned.
 */
function user_stats_get_stats($type, $uid) {

  // Sometimes $uid can be NULL (comment previews for example).
  if (!is_numeric($uid)) {
    return;
  }

  // IP address is really a bit of feature creep.
  // At some point in the future, this could be split off into its own module.
  if ($type == 'ip_address') {
    if (!user_access('View IP addresses')) {
      return FALSE;
    }

    // Check cache.
    if (user_stats_cache_get($type, $uid) === FALSE) {
      $query = db_query("SELECT ip_address\n        FROM {user_stats_ips} WHERE uid = %d\n        ORDER BY first_seen_timestamp LIMIT 1", $uid);
      user_stats_cache_set($type, $uid, db_result($query));
    }
    return user_stats_cache_get($type, $uid);
  }

  // Everything else is under the 'View statistics' permission.
  if (!user_access('View statistics')) {
    return FALSE;
  }

  // Check cache first.
  if (user_stats_cache_get($type, $uid) !== FALSE) {
    return user_stats_cache_get($type, $uid);
  }
  switch ($type) {
    case 'join_date':
      $query = db_query("SELECT created FROM {users} WHERE uid = %d", $uid);
      $data = db_result($query);
      break;
    case 'login_count':
      if (!variable_get('user_stats_count_logins', TRUE)) {
        $data = 'n/a';
      }
      else {
        if (user_stats_isset($type, $uid)) {
          $query = db_query("SELECT value FROM {user_stats_values}\n          WHERE name = 'login_count' AND uid = %d", $uid);
          $data = db_result($query);
        }
        else {
          return 0;
        }
      }
      break;
    case 'login_days':
      $user_access = db_result(db_query("SELECT access FROM {users} WHERE uid = %d", $uid));
      $data = floor((time() - $user_access) / 86400);
      break;
    case 'post_count':
      if (!variable_get('user_stats_count_posts', TRUE) && !variable_get('user_stats_count_comments', TRUE)) {
        $data = 'n/a';
      }
      else {
        if (!user_stats_isset('post_count', $uid)) {
          user_stats_post_count_update('reset', $uid);
        }
      }
      $query = db_query("SELECT value FROM {user_stats_values}\n        WHERE name = 'post_count' AND uid = %d", $uid);
      $data = db_result($query);
      break;
    case 'post_days':
      $last_post = _user_stats_last_post($uid);
      if ($last_post !== FALSE) {
        $data = floor((time() - $last_post) / 86400);
      }
      else {
        $data = 'n/a';
      }
      break;
    case 'reg_days':
      $query = db_query("SELECT value FROM {user_stats_values}\n        WHERE name = 'last_known_age' AND uid = %d", $uid);
      $data = db_result($query);
      if (!$data) {
        $data = 0;
      }
      break;
    case 'reg_days_onthefly':
      $user_created = db_result(db_query("SELECT created FROM {users} WHERE uid = %d", $uid));
      $data = floor((time() - $user_created) / 86400);
      break;
    case 'online':
      $user_access = db_result(db_query("SELECT timestamp FROM {sessions} WHERE uid = %d", $uid));
      $data = time() - $user_access < variable_get('user_block_seconds_online', 900) ? TRUE : FALSE;
      break;
    default:

      // Check for custom statistics
      $custom_stats = array();
      $module_list = module_implements('default_user_stats');
      foreach ($module_list as $module) {
        $custom_stats = array_merge($custom_stats, module_invoke($module, 'default_user_stats'));
      }
      if (array_key_exists($type, $custom_stats)) {
        module_load_include('module', 'qna');
        $data = call_user_func($custom_stats[$type], $uid);
        break;
      }
      else {

        // Raise an error if the statistic doesn't exist.
        $err_message = 'Statistic "' . check_plain($type) . '" does not exist.';
        trigger_error($err_message, E_USER_WARNING);
        return;
      }
  }
  user_stats_cache_set($type, $uid, $data);
  return user_stats_cache_get($type, $uid);
}

/**
 * Return data from the non-persistent User Stats cache. Single values
 * are returned according to type of statistic and unique user id.
 *
 * @param $type
 *   The type of statistic to retrieve, this corresponds to the statistic
 *   types used by user_stats_get_stats().
 * @param $uid
 *   Unique ID of the user who's statistic is being retrieved.
 *
 * @return
 *   A single value, representing the statistic $type where the unique user id
 *   is $uid. Or FALSE if there is no value in the cache for this combination
 *   of $type and $uid.
 *
 * @see user_stats_get_stats().
 * @see user_stats_cache_set().
 */
function user_stats_cache_get($type, $uid) {
  $user_stats_cache = user_stats_cache_set();
  if (isset($user_stats_cache[$uid][$type])) {
    return $user_stats_cache[$uid][$type];
  }
  else {
    return FALSE;
  }
}

/**
 * Store a value in the non-persistent User Stats cache.
 *
 * If the function is called with no arguments, the entire cache is returned
 * without being cleared.
 *
 * The User Stats cache is a static array, which is why we call it
 * non-persistent. The array structure is:
 * $user_stats_cache[$uid][$type] = $value.
 *
 * @param $type
 *   The type of statistic being stored, this corresponds to the statistic
 *   types used by user_stats_get_stats(), and one extra used to reset the
 *   cache: 'reset'.
 * @param $uid
 *   Unique ID of the user who's statistic is being stored. If the type
 *   is set to 'reset', this user id will have the cache values associated with
 *   it reset. Alternatively, if $type is set to 'reset' and this is -1, the
 *   entire cache will be reset.
 *
 * @return
 *   Array of the entire cache, or NULL if the cache has been reset.
 *
 * @see user_stats_get_stats().
 * @see user_stats_cache_get().
 */
function user_stats_cache_set($type = NULL, $uid = 0, $data = NULL) {
  static $user_stats_cache = array();

  // Flush entire cache.
  if ($uid == -1 && $type == 'reset') {
    unset($user_stats_cache);
    return;
  }
  else {
    if ($uid > -1 && $type == 'reset') {
      unset($user_stats_cache[$uid]);
      return;
    }
  }

  // Set cache data. Check against NULL since a zero (in $data at least)
  // is valid.
  if ($type !== NULL && $data !== NULL) {
    $user_stats_cache[$uid][$type] = $data;
  }
  return $user_stats_cache;
}

/**
 * Drupal hook implementations.
 */

/**
 * Implementation of hook_nodeapi().
 *
 * Increments post count on insert or publish.
 * Decrements post count on delete or unpublish.
 * Checks and updates users without post counts on view.
 *
 * @param &$node
 *   The node the action is being performed on.
 * @param $op
 *   The operation being performed. We are interested in insert, delete, update and view.
 */
function user_stats_nodeapi(&$node, $op) {

  // We stop before any work is done if the $op isn't one of the ones we need
  if (!in_array($op, array(
    'insert',
    'delete',
    'update',
  ))) {
    return;
  }
  $post_count_content_types = variable_get('user_stats_included_content_types', array());
  if ((empty($post_count_content_types) || in_array($node->type, $post_count_content_types)) && variable_get('user_stats_count_posts', TRUE)) {
    switch ($op) {
      case 'insert':
        if ($node->status) {
          user_stats_post_count_update('increment', $node->uid);
        }
        break;
      case 'delete':

        // Node must be published as unpublished nodes would have already been
        // removed from user's post count.
        if ($node->status) {
          user_stats_post_count_update('decrement', $node->uid);
        }
        break;
      case 'update':

        // Can't think of any other way of doing this than resetting the user.
        user_stats_post_count_update('reset', $node->uid);
        break;
    }
  }

  // Do IP Address update.
  switch ($op) {
    case 'insert':
    case 'update':
      global $user;

      // User IP addresses are only interesting if they are posting the content.
      if ($node->uid == $user->uid) {
        user_stats_ip_address_update($user->uid, ip_address());
      }
  }
}

/**
 * Implementation of hook_comment().
 *
 * Increments post count on insert or publish.
 * Decrements post count on delete or unpublish.
 * Updates users with no post count on view.
 *
 * @param $a1
 *   Either the form values being submitted, the module form to be displayed,
 *   or the comment object.
 * @param $op
 *   What kind of action is being performed.
 */
function user_stats_comment(&$a1, $op) {

  // We stop before any work is done if the $op isn't one of the ones we need.
  if (!in_array($op, array(
    'insert',
    'delete',
    'update',
  ))) {
    return;
  }

  // check to see if comments should be counted at all.
  if (variable_get('user_stats_count_comments', TRUE)) {
    $comment = (object) $a1;

    // Bug [#479394] - Anonymous User Comment causes 'UID is not a number'
    // errors. User Stats always expects UIDs to be numeric.
    if ($comment->uid == NULL) {
      $comment->uid = 0;
    }
    $post_count_content_types = variable_get('user_stats_included_content_types', array());
    $node = node_load(array(
      'nid' => $comment->nid,
    ));
    if (empty($post_count_content_types) || in_array($node->type, $post_count_content_types)) {
      switch ($op) {
        case 'insert':
          if ($comment->status == COMMENT_PUBLISHED) {
            user_stats_post_count_update('increment', $comment->uid);
          }
          break;
        case 'delete':
          if ($comment->status == COMMENT_PUBLISHED) {
            user_stats_post_count_update('decrement', $comment->uid);
          }
          break;
        case 'update':
          user_stats_post_count_update('reset', $comment->uid);
          break;
      }
    }
  }

  // Do IP address update.
  global $user;
  if ($op == 'insert' && $comment->uid == $user->uid) {

    // User IP addresses are only interesting if they are posting the content.
    user_stats_ip_address_update($user->uid, ip_address());
  }
}

/**
 * Implementation of hook_cron().
 *
 * We slowly work through all users without a post count
 * updating them.
 */
function user_stats_cron() {
  if (variable_get('user_stats_rebuild_stats', TRUE) && (variable_get('user_stats_count_posts', TRUE) || variable_get('user_stats_count_comments', TRUE))) {
    $sql = "SELECT uid FROM {users} WHERE uid NOT IN\n      (SELECT uid FROM {user_stats_values} WHERE name = 'post_count')";

    // Update 25 users per cron run.
    $result = db_query_range($sql, 0, variable_get('user_stats_user_per_cron', '25'));
    $users_updated = FALSE;
    while ($update_user = db_fetch_object($result)) {
      user_stats_post_count_update('reset', $update_user->uid);
      $users_updated = TRUE;
    }

    // If all users have been updated we'll avoid running this expensive
    // query again by setting the following flag!
    if (!$users_updated) {
      variable_set('user_stats_rebuild_stats', FALSE);
    }
  }

  // Fire rules day_older event.
  // This may seem grossly inefficient, but testing showed that, even firing
  // the event for ~100 users, takes less than a second to run when there are
  // no rules using this event. With a rule (that adds a role if the user has
  // been a member for over 1,000 days) cron took an extra ~40 seconds to run.
  // Basically, this has no potential to harm a site's performance, unless a
  // rule is configured.
  // Having said this: if there's a better way, please raise a bug report!
  if (module_exists('rules')) {

    // We're selecting the user, their calculated age, and their "last known age"
    // as calculated the last time this function was run.
    $sql = "SELECT u.uid, FLOOR((%d-created)/(60*60*24)) AS age, lka.value AS\n    last_known_age FROM {users} u ";
    $sql .= "LEFT JOIN {user_stats_values} lka ON lka.uid = u.uid AND\n    lka.name = 'last_known_age'";

    // ((last cron - created) - (time() - created)) > one day
    $sql .= "WHERE (FLOOR((%d-created)/(60*60*24))-FLOOR((%d-created)/(60*60*24)))>0\n      AND u.uid>0";
    $result = db_query($sql, time(), time(), variable_get('cron_last', time()));
    $reset_user_count = 0;
    while ($update_user = db_fetch_object($result)) {

      // If the user's calculated age is the same as their "last known age", that means
      // This user has been already updated today, and processed at some point by this
      // script. Maybe cron failed or something, and that's why they're being loaded
      // again? Regardless, the event was already triggered for them - we're not going
      // to trigger it again.
      if ($update_user->age != $update_user->last_known_age) {

        // We need to ensure the user_stats "last_known_age" value for this user is
        // accurate because that is what the Rules event trigger will actually provide
        // as an argument as the official "user's age".
        if (isset($update_user->last_known_age)) {

          // Update existing last known age
          db_query("UPDATE {user_stats_values} SET value = %d\n          WHERE name = 'last_known_age' AND uid = %d", $update_user->age, $update_user->uid);
        }
        else {

          // If there isn't a value insert it.
          db_query("INSERT INTO {user_stats_values} (name, uid, value)\n          VALUES ('last_known_age', %d, %d)", $update_user->uid, $update_user->age);
        }
        rules_invoke_event('user_stats_day_older', $update_user->uid);
      }
    }
  }
  if (variable_get('user_stats_track_ips', TRUE)) {

    // Delete items from the IP log that are past expiry.
    db_query("DELETE FROM {user_stats_ips} WHERE first_seen_timestamp < %d", time() - variable_get('user_stats_flush_ips_timer', 31536000));
  }
}

/**
 * Implementation of hook_user().
 */
function user_stats_user($op, &$edit, &$account) {

  // Update login count.
  if ($op == 'login' && variable_get('user_stats_count_logins', TRUE)) {
    user_stats_login_count_update('increment', $account->uid);
  }

  // Update IP Address.
  if ($op == 'login' || $op == 'logout') {
    user_stats_ip_address_update($account->uid, ip_address());
  }
}

/**
 * Helper function to get the last post created by the user.
 *
 * @param $account
 *   User object.
 *
 * @return
 *   Unix timestamp: date of the last post (node or comment).
 */
function _user_stats_last_post($uid) {
  $sql = "SELECT MAX(created) FROM {node} WHERE status=%d AND uid=%d";
  $all_content_types = node_get_types();
  $post_count_content_types = variable_get('user_stats_included_content_types', array());
  $where = "";

  // If some, but not all, content types have been selected in the admin
  // interface add a WHERE clause to select only them.
  if (!empty($post_count_content_types) && array_keys($all_content_types) != array_keys($post_count_content_types)) {
    $content_types = "'" . implode("','", $post_count_content_types) . "'";
    $where = ' AND type IN (' . $content_types . ')';
  }
  $sql .= $where;
  $max_node = db_result(db_query($sql, 1, $uid));
  $sql = "SELECT MAX(timestamp) FROM {comments} c ";
  $where = " WHERE c.status=%d AND c.uid=%d ";
  $join = "";
  if (!empty($post_count_content_types) && array_keys($all_content_types) != array_keys($post_count_content_types)) {
    $join = " INNER JOIN {node} n ON c.nid=n.nid ";
    $where .= 'AND n.type IN (' . $content_types . ')';
  }
  $sql .= $join . $where;
  $max_comments = db_result(db_query($sql, COMMENT_PUBLISHED, $uid));
  if (is_null($max_node) && is_null($max_comments)) {
    return FALSE;
  }
  else {
    if ($max_node > $max_comments) {
      return $max_node;
    }
    else {
      if ($max_node <= $max_comments) {
        return $max_comments;
      }
    }
  }
}

/**
 * Implementation of hook_views_api().
 *
 * Other Views hooks in user_stats.views.inc.
 */
function user_stats_views_api() {
  return array(
    'api' => '2.0',
    'path' => drupal_get_path('module', 'user_stats') . '/views',
  );
}

/**
 * Actions/Rules hooks and implementing functions.
 *
 * (we don't do Triggers as the API doesn't seem complete -- having to use
 * _trigger_get_hook_aids() for example). Patches welcome for this, as long
 * as they do not use private member functions!
 *
 * Most Rules hooks are in user_stats.rules.inc.
 */

/**
 * Implementation of hook_action_info().
 */
function user_stats_action_info() {
  return array(
    'user_stats_post_count_reset_action' => array(
      'description' => t('Reset user post count'),
      'type' => 'user',
      'configurable' => FALSE,
      'hooks' => array(
        'nodeapi' => array(
          'delete',
          'insert',
          'update',
          'view',
        ),
        'comment' => array(
          'view',
          'insert',
          'update',
          'delete',
        ),
        'user' => array(
          'login',
          'logout',
        ),
      ),
    ),
    'user_stats_login_count_reset_action' => array(
      'description' => t('Reset user login count'),
      'type' => 'user',
      'configurable' => FALSE,
      'hooks' => array(
        'nodeapi' => array(
          'delete',
          'insert',
          'update',
          'view',
        ),
        'comment' => array(
          'view',
          'insert',
          'update',
          'delete',
        ),
        'user' => array(
          'login',
          'logout',
        ),
      ),
    ),
  );
}

/**
 * Implementation of a Drupal action.
 * Resets a user's post count.
 */
function user_stats_post_count_reset_action(&$object, $context = array()) {
  if (isset($object->uid)) {
    $uid = $object->uid;
  }
  elseif (isset($context['uid'])) {
    $uid = $context['uid'];
  }
  else {
    global $user;
    $uid = $user->uid;
  }
  user_stats_post_count_update('reset', $uid);
}

/**
 * Implementation of a Drupal action.
 * Resets a user's login count.
 */
function user_stats_login_count_reset_action(&$object, $context = array()) {
  if (isset($object->uid)) {
    $uid = $object->uid;
  }
  elseif (isset($context['uid'])) {
    $uid = $context['uid'];
  }
  else {
    global $user;
    $uid = $user->uid;
  }
  user_stats_login_count_update('reset', $uid);
}

/**
 * Implementation of hook_user_stats().
 *
 * Invoke the Rules module.
 */
function user_stats_user_stats($type, $op, $uid, $value) {
  if (module_exists('rules')) {
    rules_invoke_event('user_stats_' . $type . '_' . $op, $uid, $value);
  }
}

/**
 * Token hook implementations
 */

/**
 * Implementation of hook_token_values().
 */
function user_stats_token_values($type, $object = NULL) {
  switch ($type) {
    case 'user':
    case 'all':
      if (isset($object)) {

        // Think this is sometimes an array (please raise this as an issue if wrong).
        $object = (object) $object;
        $uid = $object->uid;
      }
      else {
        global $user;
        $uid = $user->uid;
      }

      // Check_plain added as per Greggles suggestion: http://drupal.org/node/166305#comment-665874
      $values['reg-days'] = check_plain(user_stats_get_stats('reg_days', $uid));
      $values['login-days'] = check_plain(user_stats_get_stats('login_days', $uid));
      $values['post-days'] = check_plain(user_stats_get_stats('post_days', $uid));
      $values['post-count'] = check_plain(user_stats_get_stats('post_count', $uid));
      $values['ip-address'] = check_plain(user_stats_get_stats('ip_address', $uid));
      $values['login-count'] = check_plain(user_stats_get_stats('login_count', $uid));
      return $values;
  }
}

/**
 * Implementation of hook_token_list().
 */
function user_stats_token_list($type = 'all') {
  if ($type == 'user' || $type == 'all') {
    $tokens['user']['reg-days'] = t('Number of days since the user registered');
    $tokens['user']['login-days'] = t('Number of days since the user logged in');
    $tokens['user']['post-days'] = t('Number of days since the user posted');
    $tokens['user']['post-count'] = t("User's post count");
    $tokens['user']['ip-address'] = t("User's IP address");
    $tokens['user']['login-count'] = t("User's login count");
    return $tokens;
  }
}

/**
 * Checks whether a statistic is set for a given user.
 *
 * @param $uid
 *   User ID of the user who's statistics should be checked.
 * @param $statistic
 *   What statistic to check.
 */
function user_stats_isset($statistic, $uid) {
  $result = db_result(db_query("SELECT COUNT(*) FROM {user_stats_values}\n    WHERE uid = %d AND name = '%s'", $uid, $statistic));
  if ($result > 0) {
    return TRUE;
  }
  return FALSE;
}

/**
 * Manage the login count of a given user.
 *
 * @param $uid
 *   Unique id of the user who's record should be updated.
 * @param $op
 *   Whether the user login count should be incremented, decremented, or reset.
 *   Possible values are:
 *    - 'increment'
 *    - 'decrement'
 *    - 'reset'
 */
function user_stats_login_count_update($op, $uid) {
  if (!is_numeric($uid)) {
    return;
  }
  switch ($op) {
    case 'increment':
      if (user_stats_isset('login_count', $uid)) {

        // Update existing value.
        db_query("UPDATE {user_stats_values} SET value = value + 1\n          WHERE name = 'login_count' AND uid = %d", $uid);
      }
      else {

        // If there isn't a value insert it.
        db_query("INSERT INTO {user_stats_values} (name, uid, value)\n          VALUES ('login_count', %d, 1)", $uid);
      }
      break;
    case 'decrement':
      if (user_stats_isset('login_count', $uid)) {

        // Update existing value.
        db_query("UPDATE {user_stats_values} SET value = value - 1\n          WHERE name = 'login_count' AND uid = %d", $uid);
      }
      else {

        // If there isn't a value insert it.
        db_query("INSERT INTO {user_stats_values} (name, uid, value)\n          VALUES ('login_count', %d, 0)", $uid);
      }
      break;
    case 'reset':
      db_query("DELETE FROM {user_stats_values}\n        WHERE name = 'login_count' AND uid = %d", $uid);
      break;
  }

  // Flush token cache.
  if (module_exists('token')) {
    token_get_values('user', NULL, TRUE);
  }

  // Flush internal cache.
  user_stats_cache_set('reset', $uid);

  // Allow modules to react to a statistic change.
  module_invoke_all('user_stats', 'login_count', $op, $uid, user_stats_get_stats('login_count', $uid));
}

/**
 * Manage the post count of a given user.
 *
 * @param $uid
 *   Unique id of the user who's record should be updated.
 * @param $op
 *   Whether the user post count should be incremented, decremented, or reset.
 *   The default is to increment. Possible values are:
 *   'increment'
 *   'decrement'
 *   'reset'
 */
function user_stats_post_count_update($op, $uid) {
  if (!is_numeric($uid)) {
    return;
  }
  switch ($op) {
    case 'increment':
      if (user_stats_isset('post_count', $uid)) {
        db_query("UPDATE {user_stats_values} SET value = value + 1\n          WHERE name = 'post_count' AND uid = %d", $uid);

        // Flush internal cache.
        user_stats_cache_set('reset', $uid);
      }
      else {
        user_stats_post_count_update('reset', $uid);
      }
      break;
    case 'decrement':
      if (user_stats_isset('post_count', $uid)) {
        db_query("UPDATE {user_stats_values} SET value = value - 1\n          WHERE name = 'post_count' AND uid = %d", $uid);

        // Flush internal cache.
        user_stats_cache_set('reset', $uid);
      }
      else {
        user_stats_post_count_update('reset', $uid);
      }
      break;
    case 'reset':
      $total_count = 0;
      if (variable_get('user_stats_count_posts', TRUE)) {
        $sql = "SELECT COUNT(*) FROM {node} WHERE uid = %d AND status = 1";
        $post_count_content_types = variable_get('user_stats_included_content_types', array());
        if (!empty($post_count_content_types)) {
          $content_types = "'" . implode("','", $post_count_content_types) . "'";
          $where = ' AND type IN (' . $content_types . ')';
          $sql .= $where;
        }
        $node_count = db_result(db_query($sql, $uid));
        $total_count += $node_count;
      }
      if (variable_get('user_stats_count_comments', TRUE)) {
        $sql = "SELECT COUNT(*) FROM {comments} c\n          INNER JOIN {node} n ON c.nid = n.nid\n          WHERE c.uid = %d AND c.status = 0 AND n.status = 1";
        if (!empty($post_count_content_types)) {
          $where = ' AND n.type IN (' . $content_types . ')';
          $sql .= $where;
        }
        $comments_count = db_result(db_query($sql, $uid));
        $total_count += $comments_count;
      }
      db_query("DELETE FROM {user_stats_values}\n        WHERE name = 'post_count' AND uid = %d", $uid);
      db_query("INSERT INTO {user_stats_values} (name, uid, value)\n        VALUES ('post_count', %d, %d)", $uid, $total_count);

      // Prime the cache, this will be used by module_invoke_all() below.
      user_stats_cache_set('post_count', $uid, $total_count);
      break;
  }

  // Flush token cache
  if (module_exists('token')) {
    token_get_values('user', NULL, TRUE);
  }

  // Allow modules to react to a statistic change.
  module_invoke_all('user_stats', 'post_count', $op, $uid, user_stats_get_stats('post_count', $uid));
}

/**
 * Update the IP address of a given user.
 *
 * The IP address is not updated if it is the same as the last recorded IP,
 * however, if the user has IP address A, then switches to IP address B
 * and back to A again, A will be recorded twice. This is to keep an accurate
 * log of IP addresses used by users.
 *
 * @param $uid
 *   User ID of user who's IP is being updated.
 * @param $ip_address
 *   IP address to assign to user.
 */
function user_stats_ip_address_update($uid, $ip_address) {
  if (!is_numeric($uid)) {
    return;
  }

  // Don't bother recording IPs of anonymous users, and don't record any
  // addresses if the config form tells us not to.
  if ($uid == 0 || !variable_get('user_stats_track_ips', TRUE)) {
    return;
  }
  $query = db_query_range("SELECT ip_address\n    FROM {user_stats_ips} WHERE uid = %d\n    ORDER BY first_seen_timestamp DESC", $uid, 0, 1);
  if ($ip_address != db_result($query)) {

    // Reset internal cache.
    user_stats_cache_set('reset', $uid);
    db_query("INSERT INTO {user_stats_ips} (uid, ip_address, first_seen_timestamp)\n      VALUES (%d, '%s', %d)", $uid, $ip_address, time());

    // Allow modules to react to an IP address change.
    module_invoke_all('user_stats', 'ip_address', 'insert', $uid, $ip_address);
  }
}

/**
 * Resets statistics. Full stop.
 *
 * @param $statistic
 *   The name of the statistic to be reset.
 *   Corresponds with {user_stats_values}.name.
 */
function user_stats_reset_counts($statistic) {
  db_query("DELETE FROM {user_stats_values} WHERE name = '%s'", $statistic);
}

Functions

Namesort descending Description
user_stats_action_info Implementation of hook_action_info().
user_stats_cache_get Return data from the non-persistent User Stats cache. Single values are returned according to type of statistic and unique user id.
user_stats_cache_set Store a value in the non-persistent User Stats cache.
user_stats_comment Implementation of hook_comment().
user_stats_cron Implementation of hook_cron().
user_stats_get_stats Returns user stats.
user_stats_ip_address_update Update the IP address of a given user.
user_stats_isset Checks whether a statistic is set for a given user.
user_stats_login_count_reset_action Implementation of a Drupal action. Resets a user's login count.
user_stats_login_count_update Manage the login count of a given user.
user_stats_menu Implementation of hook_menu().
user_stats_nodeapi Implementation of hook_nodeapi().
user_stats_perm Implementation of hook_perm().
user_stats_post_count_reset_action Implementation of a Drupal action. Resets a user's post count.
user_stats_post_count_update Manage the post count of a given user.
user_stats_reset_counts Resets statistics. Full stop.
user_stats_token_list Implementation of hook_token_list().
user_stats_token_values Implementation of hook_token_values().
user_stats_user Implementation of hook_user().
user_stats_user_stats Implementation of hook_user_stats().
user_stats_views_api Implementation of hook_views_api().
_user_stats_last_post Helper function to get the last post created by the user.