You are here

autologout.module in Automated Logout 7.4

Used to automagically log out a user after a preset time.

File

autologout.module
View source
<?php

/**
 * @file
 * Used to automagically log out a user after a preset time.
 */

/**
 * Implements hook_permission().
 */
function autologout_permission() {
  return array(
    'change own logout threshold' => array(
      'title' => t('Change own logout threshold'),
      'description' => t('Selected users will be able to edit their own logout threshold.'),
    ),
    'administer autologout' => array(
      'title' => t('Administer Autologout'),
      'description' => t('Administer the autologout settings.'),
    ),
  );
}

/**
 * Implements hook_menu().
 */
function autologout_menu() {
  $items = array();
  $items['admin/config/people/autologout'] = array(
    'title' => 'Auto Logout',
    'description' => 'Administer Auto Logout settings.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'autologout_settings',
    ),
    'access arguments' => array(
      'administer autologout',
    ),
    'file' => 'autologout.admin.inc',
    'type' => MENU_NORMAL_ITEM,
  );
  $items['autologout_ahah_logout'] = array(
    'title' => 'JS Logout',
    'page callback' => 'autologout_ahah_logout',
    'access callback' => 'user_is_logged_in',
    'type' => MENU_CALLBACK,
  );
  $items['autologout_ahah_set_last'] = array(
    'title' => 'JS Logout AHAH Set Last',
    'page callback' => 'autologout_ahah_set_last',
    'access callback' => 'user_is_logged_in',
    'type' => MENU_CALLBACK,
    'theme callback' => 'ajax_base_page_theme',
    'delivery callback' => 'ajax_deliver',
  );
  $items['autologout_ajax_get_time_left'] = array(
    'title' => 'JS Logout AJAX Get Time Until Logout',
    'page callback' => 'autologout_ahah_get_remaining_time',
    'access callback' => 'user_is_logged_in',
    'type' => MENU_CALLBACK,
    'theme callback' => 'ajax_base_page_theme',
    'delivery callback' => 'ajax_deliver',
  );
  return $items;
}

/**
 * Implements hook_block_info().
 */
function autologout_block_info() {
  $blocks = array();
  $blocks['info'] = array(
    'info' => t('Automated Logout info'),
  );
  return $blocks;
}

/**
 * Implements hook_block_view().
 */
function autologout_block_view($delta = '') {
  $block = array();
  if (_autologout_prevent()) {

    // Don't display the block if the user is not going
    // to be logged out on this page.
    return;
  }
  $block['subject'] = t('Autologout warning block');
  if (_autologout_refresh_only()) {
    $block['content'] = t('Autologout does not apply on the current page, you will be kept logged in whilst this page remains open.');
  }
  elseif (module_exists('jstimer') && module_exists('jst_timer')) {
    $block['content'] = array(
      drupal_get_form('autologout_create_block_form'),
    );
  }
  else {
    $timeout = (int) variable_get('autologout_timeout', 1800);
    $block['content'] = t('You will be logged out in !time if this page is not refreshed before then.', array(
      '!time' => format_interval($timeout),
    ));
  }
  return $block;
}

/**
 * Drupal reset timer form on timer block.
 */
function autologout_create_block_form() {
  $markup = autologout_create_timer();
  $form['autologout_reset'] = array(
    '#type' => 'button',
    '#value' => t('Reset Timeout'),
    '#weight' => 1,
    '#limit_validation_errors' => FALSE,
    '#executes_submit_callback' => FALSE,
    '#ajax' => array(
      'callback' => 'autologout_ahah_set_last',
    ),
  );
  $form['timer'] = array(
    '#markup' => $markup,
  );
  return $form;
}

/**
 * Get the timer HTML markup.
 *
 * @return string
 *   HTML to insert a countdown timer.
 */
function autologout_create_timer() {
  $time_remaining = _autologout_get_remaining_time();
  $timeformat = filter_xss_admin(variable_get('autologout_jstimer_format', '%hours%:%mins%:%secs%'));
  return theme('autologout_block', array(
    'time_remaining' => $time_remaining,
    'timeformat' => $timeformat,
  ));
}

/**
 * Implements hook_block_configure().
 */
function autologout_block_configure($delta = '') {
  $block = array();
  if (module_exists('jstimer')) {
    if (!module_exists('jst_timer')) {
      drupal_set_message(t('The "Widget: timer" module must also be enabled for the dynamic countdown to work in the automated logout block.'), 'error');
    }
    if (variable_get('jstimer_js_load_option', 0) != 1) {
      drupal_set_message(t("The Javascript timer module's 'Javascript load options' setting should be set to 'Every page' for the dynamic countdown to work in the automated logout block."), 'error');
    }
  }
  return $block;
}

/**
 * Implements hook_help().
 */
function autologout_help($path, $arg) {
  $seconds = _autologout_get_user_timeout();
  $message = NULL;
  switch ($path) {
    case 'admin/help#autologout':
      $message = '<p>' . t("This module allows you to force site users to be logged out after a given amount of time due to inactivity after first being presented with a confirmation dialog. Your current logout threshold is %seconds seconds.", array(
        '%seconds' => $seconds,
      )) . '</p>';
      break;
  }
  return $message;
}

/**
 * Implements hook_theme().
 */
function autologout_theme() {
  return array(
    'autologout_render_table' => array(
      'render element' => 'element',
    ),
    'autologout_block' => array(
      'variables' => array(
        'time_left' => NULL,
        'time_format' => NULL,
      ),
    ),
  );
}

/**
 * Custom themeing function, to display roles as a table with checkboxes and
 * textfields for logout threshold.
 */
function theme_autologout_render_table($variables) {
  $output = "";
  if ($variables) {
    $element = $variables['element'];
  }
  $header = array(
    'enable' => t('Enable'),
    'name' => t('Role Name'),
    'timeout' => t('Timeout (seconds)'),
  );
  $rows = array();
  foreach (user_roles(TRUE) as $key => $role) {
    $rows[] = array(
      'enable' => drupal_render($element['autologout_roles']['autologout_role_' . $key]),
      'name' => t($role),
      'timeout' => drupal_render($element['autologout_roles']['autologout_role_' . $key . '_timeout']),
    );
  }
  $table = theme('table', array(
    'header' => $header,
    'rows' => $rows,
  ));
  $output = $table;
  return $output;
}

/**
 * Returns HTML for the autologout block.
 */
function theme_autologout_block($variables) {
  $time_remaining = $variables['time_remaining'];
  $timeformat = $variables['timeformat'];
  return "<div id='timer'><span class='jst_timer'>\n    <span class='interval' style='display: none;'>{$time_remaining}</span>\n    <span class='format_txt' style='display:none;'>{$timeformat}</span>\n    </span></div>";
}

/**
 * Checks to see if timeout threshold is outside max/min values. Only done here
 * to centrilize and stop repeated code. Hard coded min, configurable max.
 *
 * @param int $timeout
 *   The timeout value in seconds to validate
 * @param int $max_timeout
 *   (optional) A maximum timeout. If not set the current system
 *   default maximum is used.
 *
 * @return bool
 */
function autologout_timeout_validate($timeout, $max_timeout = NULL) {
  $validate = FALSE;
  if (is_null($max_timeout)) {
    $max_timeout = variable_get('autologout_max_timeout', '172800');
  }
  if (!is_numeric($timeout) || $timeout < 0 || $timeout > 0 && $timeout < 60 || $timeout > $max_timeout) {

    // Less then 60, greater then max_timeout and is numeric.
    // 0 is allowed now as this means no timeout.
    $validate = FALSE;
  }
  else {
    $validate = TRUE;
  }
  return $validate;
}

/**
 * Adds a field to user/edit to change that users logout.
 */
function autologout_form_user_profile_form_alter(&$form, $form_state) {
  if ($form['#user_category'] != 'account') {

    // Only include the timeout on main user profile.
    return;
  }
  global $user;
  $current_uid = $user->uid;
  $userid = $form_state['user']->uid;
  $access = FALSE;

  // If user has access to change, and they are changing their own and only
  // thier own timeout. Or they are an admin.
  if (user_access('change own logout threshold') && $current_uid == $userid || user_access('administer autologout')) {
    $access = TRUE;
  }
  if ($access) {
    $form['autologout_user_' . $userid] = array(
      '#type' => 'textfield',
      '#title' => t('Your current logout threshold'),
      '#default_value' => variable_get('autologout_user_' . $userid, ""),
      '#size' => 8,
      '#description' => t('How many seconds to give a user to respond to the logout dialog before ending their session.'),
      '#element_validate' => array(
        '_autologout_user_uid_timeout_validate',
      ),
    );
    $form['#submit'][] = 'autologout_user_profile_submit';
  }
}

/**
 * Form validation.
 */
function _autologout_user_uid_timeout_validate($element, &$form_state) {
  $max_timeout = variable_get('autologout_max_timeout', 172800);
  $timeout = $element['#value'];

  // Set error if it has a value that isn't strictly a number between 60 and max.
  if ($timeout != "" && ($timeout < 60 || $timeout > $max_timeout || !is_numeric($timeout))) {
    form_error($element, t('The timeout must be an integer greater than 60, and less then %max.', array(
      '%max' => $max_timeout,
    )));
  }
}

/**
 * Handle submission of timeout threshold in user/edit.
 */
function autologout_user_profile_submit(&$form, &$form_state) {
  global $user;
  $current_uid = $user->uid;
  $userid = $form_state['user']->uid;
  $access = FALSE;
  if (user_access('change own logout threshold') && $current_uid == $userid || user_access('administer autologout')) {
    $access = TRUE;
  }

  // Access is reused here as a security measure. Not only will the element not
  // display but wont submit without access.
  if ($access) {
    $userid = $form_state['user']->uid;
    $val = trim($form_state['values']['autologout_user_' . $userid]);
    if (empty($val)) {
      variable_del('autologout_user_' . $userid);
    }
    else {
      variable_set('autologout_user_' . $userid, $val);
    }
  }
}

/**
 * Implements hook_init().
 */
function autologout_init() {
  global $user;
  if (empty($user->uid)) {
    if (!empty($_GET['autologout_timeout']) && $_GET['autologout_timeout'] == 1 && empty($_POST)) {
      _autologout_inactivity_message();
    }
    return;
  }

  // Check if JS should be included on this request.
  if (_autologout_prevent()) {
    return;
  }

  // Check if anything wants to be refresh only. This URL would
  // include the javascript but will keep the login alive whilst
  // that page is opened.
  $refresh_only = _autologout_refresh_only();
  $now = time();
  $timeout = _autologout_get_user_timeout();
  $timeout_padding = variable_get('autologout_padding', 20);
  $autologout_redirect_url = variable_get('autologout_redirect_url', 'user/login');
  $redirect_url = empty($autologout_redirect_url) ? current_path() : $autologout_redirect_url;
  $redirect_query = autologout_get_current_path_as_destination() + array(
    'autologout_timeout' => 1,
  );
  $no_dialog = variable_get('autologout_no_dialog', FALSE);
  $use_alt_logout_method = variable_get('autologout_use_alt_logout_method', FALSE);
  drupal_add_library('system', 'ui.dialog');

  // Provide a hook to allow the redirect URL and query to be customised
  drupal_alter('autologout_redirect_url', $redirect_url, $redirect_query);

  // Get all settings JS will need for dialog.
  $msg = t(variable_get('autologout_message', 'We are about to log you out for inactivity. If we do, you will lose any unsaved work. Do you need more time?'));
  $settings = array(
    'timeout' => $refresh_only ? $timeout * 500 : $timeout * 1000,
    'timeout_padding' => $timeout_padding * 1000,
    'message' => filter_xss($msg),
    'redirect_url' => url($redirect_url, array(
      'query' => $redirect_query,
    )),
    'title' => t('@name Alert', array(
      '@name' => variable_get('site_name', 'Drupal'),
    )),
    'refresh_only' => $refresh_only,
    'no_dialog' => $no_dialog,
    'use_alt_logout_method' => $use_alt_logout_method,
  );

  // If this is an AJAX request, then the logout redirect url should still be
  // referring to the page that generated this request
  if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {
    global $base_url;
    $relative_url = str_replace($base_url . '/', '', $_SERVER['HTTP_REFERER']);
    $settings['redirect_url'] = url($redirect_url, array(
      'query' => array(
        'destination' => urlencode($relative_url),
      ),
      'autologout_timeout' => 1,
    ));
  }
  drupal_add_library('system', 'drupal.ajax');
  drupal_add_js(array(
    'autologout' => $settings,
  ), 'setting');
  drupal_add_js(drupal_get_path('module', 'autologout') . "/autologout.js");

  // We need a backup plan if JS is disabled.
  if (!$refresh_only && isset($_SESSION['autologout_last'])) {

    // If time since last access is > than the timeout + padding, log them out.
    if ($now - $_SESSION['autologout_last'] >= $timeout + (int) $timeout_padding) {
      _autologout_logout();

      // User has changed so force Drupal to remake decisions based on user.
      global $theme, $theme_key;
      drupal_static_reset();
      $theme = NULL;
      $theme_key = NULL;
      menu_set_custom_theme();
      drupal_theme_initialize();
      _autologout_inactivity_message();
    }
    else {
      $_SESSION['autologout_last'] = $now;
    }
  }
  else {
    $_SESSION['autologout_last'] = $now;
  }
}

/**
 * Builds a destination query parameter for the current page.
 *
 * This is identical to drupal_get_destination(), except that we always encode
 * any existing destination query-string parameter, rather than honour it as an
 * override.
 *
 * This causes the user to be returned to the exact same URL they were on when
 * they were auto-logged-off (with destination parameter untouched), rather than
 * being directed straight to their destination.
 */
function autologout_get_current_path_as_destination() {
  $path = current_path();
  $query = drupal_http_build_query(drupal_get_query_parameters());
  if ($query != '') {
    $path .= '?' . $query;
  }
  return array(
    'destination' => $path,
  );
}

/**
 * Implements hook_autologout_prevent().
 */
function autologout_autologout_prevent() {
  global $user;

  // Don't include autologout JS checks on ajax callbacks.
  $paths = array(
    'system',
    'autologout_ajax_get_time_left',
    'autologout_ahah_logout',
    'autologout_ahah_set_last',
  );
  if (in_array(arg(0), $paths)) {
    return TRUE;
  }

  // If user is anonymous or has no timeout set.
  if (empty($user->uid) || !_autologout_get_user_timeout()) {
    return TRUE;
  }

  // If the user has checked remember_me via the remember_me module.
  if (!empty($user->data['remember_me'])) {
    return TRUE;
  }

  // If user IP address is in the whitelist.
  if (variable_get('autologout_whitelisted_ip_addresses', FALSE)) {
    $ip_address_whitelist = array_map('trim', explode("\n", trim(variable_get('autologout_whitelisted_ip_addresses', array()))));
    if ($ip_address_whitelist && in_array(ip_address(), $ip_address_whitelist)) {
      return TRUE;
    }
  }
}

/**
 * Implements hook_autologout_refresh_only().
 */
function autologout_autologout_refresh_only() {

  // Check to see if an open admin page will keep login alive.
  if (!variable_get('autologout_enforce_admin', FALSE) && path_is_admin(current_path())) {
    return TRUE;
  }
}

/**
 * Implements hook_page_build().
 *
 * Add a form element to every page which is used to detect if the page was
 * loaded from browser cache. This happens when the browser's back button is
 * pressed for example. The JS will set the value of the hidden input element
 * to 1 after initial load. If this is 1 on subsequent loads, the page was
 * loaded from cache and an autologout timeout refresh needs to be triggered.
 */
function autologout_page_build(&$page) {
  if (!_autologout_prevent()) {
    $page['page_bottom']['autologout'] = array(
      '#markup' => '<form id="autologout-cache-check"><input type="hidden" id="autologout-cache-check-bit" value="0" /></form>',
    );
  }
}

/**
 * AJAX callback that returns the time remaining for this user is logged out.
 */
function autologout_ahah_get_remaining_time() {
  $time_remaining_ms = _autologout_get_remaining_time() * 1000;

  // Reset the timer.
  $markup = autologout_create_timer();
  $commands = array();
  $commands[] = ajax_command_replace('#timer', $markup);
  $commands[] = ajax_command_settings(array(
    'time' => $time_remaining_ms,
  ));
  return array(
    '#type' => 'ajax',
    '#commands' => $commands,
  );
}

/**
 * Ajax callback to reset the last access session variable.
 */
function autologout_ahah_set_last() {
  $_SESSION['autologout_last'] = time();

  // Let others act.
  global $user;
  module_invoke_all('auto_logout_session_reset', $user);

  // Reset the timer.
  $markup = autologout_create_timer();
  $commands = array();
  $commands[] = ajax_command_replace('#timer', $markup);
  return array(
    '#type' => 'ajax',
    '#commands' => $commands,
  );
}

/**
 * AJAX callback that performs the actual logout and redirects the user.
 */
function autologout_ahah_logout($type = 'ajax') {
  _autologout_logout();
  if ($type !== 'ajax') {
    $redirect_url = variable_get('autologout_redirect_url', 'user/login');
    drupal_goto($redirect_url);
  }
  drupal_exit();
}

/**
 * Get the time remaining before logout.
 *
 * @return int
 *   Number of seconds remaining.
 */
function _autologout_get_remaining_time() {
  $timeout = _autologout_get_user_timeout();
  $time_passed = isset($_SESSION['autologout_last']) ? time() - $_SESSION['autologout_last'] : 0;
  return $timeout - $time_passed;
}

/**
 * Go through every role to get timeout value, default is the global timeout.
 */
function _autologout_get_role_timeout() {
  $default_timeout = variable_get('autologout_timeout', 1800);
  $roles = user_roles(TRUE);
  $role_timeout = array();

  // Go through roles, get timeouts for each and return as array.
  foreach ($roles as $rid => $role) {
    if (variable_get('autologout_role_' . $rid, FALSE)) {
      $timeout_role = variable_get('autologout_role_' . $rid . '_timeout', $default_timeout);
      $role_timeout[$rid] = $timeout_role;
    }
  }
  return $role_timeout;
}

/**
 * Get a user's timeout in seconds.
 *
 * @param int $uid
 *   (Optional) Provide a user's uid to get the timeout for.
 *   Default is the logged in user.
 *
 * @return int
 *   The number of seconds the user can be idle for before being
 *   logged out. A value of 0 means no timeout.
 */
function _autologout_get_user_timeout($uid = NULL) {
  if (is_null($uid)) {

    // If $uid is not provided, use the logged in user.
    global $user;
  }
  else {
    $user = user_load($uid);
  }
  if ($user->uid == 0) {

    // Anonymous doesn't get logged out.
    return 0;
  }
  if (is_numeric($user_timeout = variable_get('autologout_user_' . $user->uid, FALSE))) {

    // User timeout takes precedence.
    drupal_alter('autologout_timeout', $user_timeout);
    return $user_timeout;
  }

  // Get role timeouts for user.
  if (variable_get('autologout_role_logout', FALSE)) {
    $user_roles = $user->roles;
    $output = array();
    $timeouts = _autologout_get_role_timeout();
    foreach ($user_roles as $rid => $role) {
      if (isset($timeouts[$rid])) {
        $output[$rid] = $timeouts[$rid];
      }
    }

    // Assign the lowest timeout value to be session timeout value.
    if (!empty($output)) {

      // If one of the user's roles has a unique timeout, use this.
      $timeout = min($output);
      drupal_alter('autologout_timeout', $timeout);
      return $timeout;
    }
  }

  // If no user or role override exists, return the default timeout.
  $timeout = variable_get('autologout_timeout', 1800);
  drupal_alter('autologout_timeout', $timeout);
  return $timeout;
}

/**
 * Helper to perform the actual logout.
 */
function _autologout_logout() {
  global $user;
  if (variable_get('autologout_use_watchdog', FALSE)) {
    watchdog('Autologout', 'Session automatically closed for %name by autologout.', array(
      '%name' => $user->name,
    ));
  }

  // Destroy the current session.
  module_invoke_all('user_logout', $user);
  session_destroy();

  // Load the anonymous user.
  $user = drupal_anonymous_user();
}

/**
 * Helper to determine if a given user should be autologged out.
 */
function _autologout_logout_role($user) {
  if (variable_get('autologout_role_logout', FALSE)) {
    foreach ($user->roles as $key => $role) {
      if (variable_get('autologout_role_' . $key, FALSE)) {
        return TRUE;
      }
    }
  }
  return FALSE;
}

/**
 * Display the inactivity message if required.
 */
function _autologout_inactivity_message() {
  $message = variable_get('autologout_inactivity_message', 'You have been logged out due to inactivity.');
  drupal_alter('autologout_message', $message);
  if (!empty($message)) {
    drupal_set_message(filter_xss(t($message)), 'status', FALSE);
  }
}

/**
 * Determine if autologout should be prevented.
 *
 * @return bool
 *   TRUE if there is a reason not to autologout
 *   the current user on the current page.
 */
function _autologout_prevent() {
  foreach (module_invoke_all('autologout_prevent') as $prevent) {
    if (!empty($prevent)) {
      return TRUE;
    }
  }
  return FALSE;
}

/**
 * Determine if connection should be refreshed.
 *
 * @return bool
 *   TRUE if something about the current context
 *   should keep the connection open. FALSE and
 *   the standard countdown to autologout applies.
 */
function _autologout_refresh_only() {
  foreach (module_invoke_all('autologout_refresh_only') as $module_refresh_only) {
    if (!empty($module_refresh_only)) {
      return TRUE;
      break;
    }
  }
  return FALSE;
}

/**
 * Implements hook_user_login().
 *
 * Delete stale sessions for the user on login. This stops
 * session_limit module thinking the user has reached their
 * session limit.
 */
function autologout_user_login(&$edit, $account) {

  // Cleanup old sessions.
  $timeout = _autologout_get_user_timeout($account->uid);
  if (empty($timeout)) {

    // Users that don't get logged have their sessions left.
    return;
  }
  $timeout_padding = variable_get('autologout_padding', 10);
  $timestamp = time() - ($timeout + $timeout_padding);

  // Find all stale sessions.
  $results = db_select('sessions', 's')
    ->fields('s')
    ->condition('uid', $account->uid)
    ->condition('timestamp', $timestamp, '<')
    ->orderBy('timestamp', 'DESC')
    ->execute();
  $sids = array();
  foreach ($results as $session) {
    $sids[] = $session->sid;
  }
  if (!empty($sids)) {

    // Delete stale sessions at login.
    db_delete('sessions')
      ->condition('sid', $sids, 'IN')
      ->execute();
  }
}

Functions

Namesort descending Description
autologout_ahah_get_remaining_time AJAX callback that returns the time remaining for this user is logged out.
autologout_ahah_logout AJAX callback that performs the actual logout and redirects the user.
autologout_ahah_set_last Ajax callback to reset the last access session variable.
autologout_autologout_prevent Implements hook_autologout_prevent().
autologout_autologout_refresh_only Implements hook_autologout_refresh_only().
autologout_block_configure Implements hook_block_configure().
autologout_block_info Implements hook_block_info().
autologout_block_view Implements hook_block_view().
autologout_create_block_form Drupal reset timer form on timer block.
autologout_create_timer Get the timer HTML markup.
autologout_form_user_profile_form_alter Adds a field to user/edit to change that users logout.
autologout_get_current_path_as_destination Builds a destination query parameter for the current page.
autologout_help Implements hook_help().
autologout_init Implements hook_init().
autologout_menu Implements hook_menu().
autologout_page_build Implements hook_page_build().
autologout_permission Implements hook_permission().
autologout_theme Implements hook_theme().
autologout_timeout_validate Checks to see if timeout threshold is outside max/min values. Only done here to centrilize and stop repeated code. Hard coded min, configurable max.
autologout_user_login Implements hook_user_login().
autologout_user_profile_submit Handle submission of timeout threshold in user/edit.
theme_autologout_block Returns HTML for the autologout block.
theme_autologout_render_table Custom themeing function, to display roles as a table with checkboxes and textfields for logout threshold.
_autologout_get_remaining_time Get the time remaining before logout.
_autologout_get_role_timeout Go through every role to get timeout value, default is the global timeout.
_autologout_get_user_timeout Get a user's timeout in seconds.
_autologout_inactivity_message Display the inactivity message if required.
_autologout_logout Helper to perform the actual logout.
_autologout_logout_role Helper to determine if a given user should be autologged out.
_autologout_prevent Determine if autologout should be prevented.
_autologout_refresh_only Determine if connection should be refreshed.
_autologout_user_uid_timeout_validate Form validation.