<?php
// $Id: masquerade.module,v 1.15.2.12 2008/09/25 12:09:34 deekayen Exp $

/**
 * @file masquerade.module
 *
 * The masquerade module allows administrators to masquerade as other user.
 */

/**
 * Implementation of hook_help().
 */
function masquerade_help($section) {
  switch ($section) {
    case 'admin/help#masquerade':
      return t("<p>The masquerade module adds a link on a user's profile page that allows permitted users to masquerade as that user. Upon masquerading, the link to 'switch back' to the original user will appear in the menu. While masquerading, the option to masquerade as another user will not appear. All masquerading transactions are logged, and $user->masquerading will be set; this could be displayed via theme.</p><p>In the masquerade settings a list of roles are presented; any checked role is considered an 'administrator' and requires the second level 'masquerade as admin' permission to masquerade as. User #1 is automatically considered an administrator, regardless of roles.</p>");
    case 'admin/settings/masquerade':
      return t('Only the users with <strong>masquerade as admin</strong> permission, will be able to masquerade as the users who belong to the roles selected below. User #1 is automatically considered an administrator, regardless of roles.');
  }
}

/**
 * Implementation of hook_perm().
 */
function masquerade_perm() {
  return array('masquerade as user', 'masquerade as admin');
}

/**
 * Implementation of hook_init().
 */
function masquerade_init() {
  global $user;

  // load from table uid + session id
  $uid = db_result(db_query("SELECT uid_from FROM {masquerade} WHERE sid = '%s' AND uid_as = %d", session_id(), $user->uid));
  // using if so that we get unset rather than false if not masqing
  if ($uid) {
    $GLOBALS['masquerading'] = $uid;
  }
  else {
    $GLOBALS['masquerading'] = null;
  }
}

/**
 * Implementation of hook_cron()
 *
 * Cleanup masquerade records where people didn't use the switch back link
 * that would have cleanly removed the user switch record.
 */
function masquerade_cron() {
  // see http://drupal.org/node/268487 before modifying this query
  db_query('DELETE {masquerade} FROM {masquerade} LEFT JOIN {sessions} ON {masquerade}.sid = {sessions}.sid WHERE {sessions}.sid IS NULL');
}

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

  if ($may_cache) {
    $default_test_user = user_load(array('name' => variable_get('masquerade_test_user', '')));
    if ($default_test_user->uid) {
      $items[] = array('path' => 'masquerade/switch/'. $default_test_user->uid,
        'title' => t('Masquerade as @testuser', array('@testuser' => $default_test_user->name)),
        'access' => !$GLOBALS['masquerading'] && (user_access('masquerade as user') || user_access('masquerade as admin')),
        'type' => MENU_NORMAL_ITEM,
      );
    }
    $items[] = array('path' => 'masquerade/autocomplete',
      'title' => t('Masquerade autocomplete'),
      'callback' => 'masquerade_autocomplete',
      'access' => $GLOBALS['masquerading'] || (user_access('masquerade as user') || user_access('masquerade as admin')),
      'type' => MENU_CALLBACK,
    );
    $items[] = array('path' => 'admin/settings/masquerade',
      'title' => t('Masquerade'),
      'description' => t('Masquerade module allows administrators to masquerade as other users.'),
      'callback' => 'drupal_get_form',
      'callback arguments' => 'masquerade_settings',
      'type' => MENU_NORMAL_ITEM,
    );
  }
  else {
    $items[] = array('path' => 'masquerade/unswitch',
      'title' => t('Switch back'),
      'callback' => 'masquerade_switch_back',
      'access' => $GLOBALS['masquerading'],
      'type' => MENU_NORMAL_ITEM,
    );
    $items[] = array('path' => 'masquerade/switch/'. arg(2),
      'title' => t('Switch user'),
      'callback' => 'masquerade_switch_user',
      'callback arguments' => array(arg(2)),
      'access' => !$GLOBALS['masquerading'] && (user_access('masquerade as user') || user_access('masquerade as admin')),
      'type' => MENU_CALLBACK,
    );
  }

  return $items;
}

/**
 * Implementation of hook_settings().
 */
function masquerade_settings() {
  // create a list of roles; all selected roles are considered administrative.
  $rids = array();
  $result = db_query("SELECT r.rid, r.name FROM {role} r ORDER BY r.name");
  while ($obj = db_fetch_object($result)) {
    $rids[$obj->rid] = $obj->name;
  }
  $test_user = user_load(array('name' => variable_get('masquerade_test_user', '')));
  $quick_switch_users = variable_get('masquerade_quick_switches', array());
  $user_list = db_query_range('SELECT name, uid FROM {users} WHERE uid > 0 ORDER BY name ASC', 0, 1000);
  while ($user_row = db_fetch_array($user_list)) {
    $user_options[$user_row['uid']] = $user_row['name'];
  }

  $form['masquerade_admin_roles'] = array(
    '#type' => 'checkboxes',
    '#title' => t("Roles that are considered 'administrator' for masquerading."),
    '#options' => $rids,
    '#default_value' => variable_get('masquerade_admin_roles', array()),
  );
  $form['masquerade_test_user'] = array(
    '#type' => 'textfield',
    '#title' => t('Default test user'),
    '#autocomplete_path' => 'masquerade/autocomplete',
    '#default_value' => check_plain($test_user->name),
    '#description' => t('Enter the username of an account you wish to switch easily between via a menu item. The name must be an exisiting user.'),
  );
  $form['masquerade_quick_switches'] = array(
    '#type' => 'select',
    '#title' => t('Quick switch users'),
    '#options' => $user_options,
    '#default_value' => $quick_switch_users,
    '#size' => 5,
    '#multiple' => TRUE,
    '#description' => t('Select the usernames for accounts to show as quick links in the Masquerade block.')
  );
  $form['masquerade_version_information'] = array(
    '#value' => masquerade_version(),
  );
  $form['#validate'] = array(
    'masquerade_settings_validate' => array(),
  );

  return system_settings_form($form);
}

function masquerade_settings_validate($form_id, $form_value) {
  $test_user = user_load(array('name' => $form_value['masquerade_test_user']));
  if (!$test_user) {
    form_set_error('masquerade_test_user', t('No such user exists. Please enter a valid username.'));
  }
}

/**
 * Implementation of hook_user().
 */
function masquerade_user($op, &$edit, &$edit_user, $category = NULL) {
  switch ($op) {

    case 'logout':
      if ($edit_user->masquerading) {
        global $user;
        cache_clear_all($user->uid, 'cache_menu', true);
        $real_user = user_load(array('uid' => $user->masquerading));
        watchdog('masquerade', t("User %user no longer masquerading as %masq_as.", array('%user' => $real_user->name, '%masq_as' => $user->name)));
        db_query("DELETE FROM {masquerade} WHERE sid = '%s' AND uid_as = %d", session_id(), $edit_user->uid);
      }
      break;

    case 'view':
      // check if user qualifies as admin
      $roles = array_keys(array_filter(variable_get('masquerade_admin_roles', array())));
      $perm = $edit_user->uid == 1 || array_intersect(array_keys((array)$edit_user->roles), $roles) ?
        'masquerade as admin' :
        'masquerade as user';

      global $user;
      if (user_access($perm) && !$edit_user->masquerading && $user->uid != $edit_user->uid) {
        $items['masquerade'] = array(
          'value' => l(t('Masquerade as !user', array('!user' => $edit_user->name)), 'masquerade/switch/'. $edit_user->uid, array('destination' => $_GET['q'])),
          'class' => 'masquerade',
        );
        return array(t('Masquerade') => $items);
      }
      break;
  }
}

/**
 * Implementation of hook_block().
 */
function masquerade_block($op = 'list', $delta = 0, $edit = array()) {
  switch ($op) {
    case 'list':
      $blocks[0]['info'] =  t('Masquerade');
      return $blocks;
    case 'view':
      if ($GLOBALS['masquerading'] || (user_access('masquerade as user') || user_access('masquerade as admin'))) {
        switch ($delta) {
          case 0:
            $block['subject'] = t('Masquerade');
            $block['content'] = drupal_get_form('masquerade_block_1', $form);
            break;
        }
      return $block;
    }
  }
}

/**
 * Masquerade block form.
 */
function masquerade_block_1($record) {
  $attributes = $GLOBALS['masquerading'] ? array('disabled' => 'disabled') : array();
  if ($GLOBALS['masquerading']) {
    global $user;
    $quick_switch_link[] = l(t('Switch back'), 'masquerade/unswitch', array());
    $markup_value = t('You are masquerading as:<br />%masq_as', array('%masq_as' => $user->name)) . theme('item_list', $quick_switch_link);
  }
  else {
    $masquerade_switches = variable_get('masquerade_quick_switches', array());
    foreach ($masquerade_switches as $switch_user) {
      if ($switch_user != $GLOBALS['user']) {
        $user = user_load(array('uid' => $switch_user));
        if ($user->name) {
          $quick_switch_link[] = l($user->name, 'masquerade/switch/'. $switch_user);
        }
      }
    }
    if (count($quick_switch_link)) {
      $markup_value .= '<div id="quick_switch_links">'. t('Quick switches:') . theme('item_list', $quick_switch_link) .'</div>';
    }

    $form['masquerade_user_field'] = array(
      '#prefix' => '<div class="container-inline">',
      '#type' => 'textfield',
      '#size' => '20',
      '#default_value' => $GLOBALS['masquerading'] ? t('Switch back to use') : $default_test_user->name,
      '#attributes' => $attributes,
      '#autocomplete_path' => 'masquerade/autocomplete',
      '#description' => t('Enter username to masquerade.')
    );
    $form['submit'] = array(
      '#type' => 'submit',
      '#value' => t('Go'),
      '#attributes' => $attributes,
      '#suffix' => '</div>',
    );
  }
  $form['masquerade_desc'] = array(
    '#prefix' => '<div class="form-item"><div class="description">',
    '#type' => 'markup',
    '#value' => $markup_value,
    '#suffix' => '</div></div>',
  );
  return $form;
}

/**
 * Masquerade block form validation. Implementation of hook_validate().
 */
function masquerade_block_1_validate($form_id, $form_value) {
  $name = $form_value['masquerade_user_field'];

  if ($name == '') {
    form_set_error('masquerade_user_field', t('You cannot masquerade as %anonymous!', array('%anonymous' => variable_get('anonymous', 'Anonymous'))));
  }
  if ($GLOBALS['masquerading']) {
    form_set_error('masquerade_user_field', t('You are already masquerading!'));
  }
  global $user;

  if (module_exists('alt_login')) {
    $alt_login = db_fetch_object(db_query("SELECT u.name FROM {users} u INNER JOIN {alt_login} al ON u.uid = al.uid WHERE al.alt_login = '%s'", $name));
    if ($alt_login->name) {
      $name = $alt_login->name;
    }
  }
  $masq_user = user_load(array('name' => $name));
  if (!$masq_user) {
    form_set_error('masquerade_user_field', t('User %masq_as does not exist. Please enter a valid username.', array('%masq_as' => $form_value['masquerade_user_field'])));
  }
  if ($masq_user->uid == $user->uid) {
    form_set_error('masquerade_user_field', t('You cannot masquerade on yourself!'));
  }
  if (variable_get('site_offline', 0)) {
    form_set_error('masquerade_user_field', t('Sorry! But it is not possible to masquerade in off-line mode!'));
  }
}

/**
 * Masquerade block form submission. Implementation of hook_submit().
 */
function masquerade_block_1_submit($form_id, $form_values) {
  $name = $form_values['masquerade_user_field'];

  if (module_exists('alt_login')) {
    $alt_login = db_fetch_object(db_query("SELECT u.name FROM {users} u INNER JOIN {alt_login} al ON u.uid = al.uid WHERE al.alt_login = '%s'", $name));
    if ($alt_login->name) {
      $name = $alt_login->name;
    }
  }

  $masq_user = user_load(array('name' => $name));
  masquerade_switch_user($masq_user->uid);
}

/**
 * Returns JS array for Masquerade autocomplete fields.
 */
function masquerade_autocomplete($string) {
  $matches = array();
  $result = db_query_range("SELECT name FROM {users} WHERE LOWER(name) LIKE LOWER('%s%%')", $string, 0, 10);
  while ($user = db_fetch_object($result)) {
    $matches[$user->name] = check_plain($user->name);
  }
  if (module_exists('alt_login')) {
    $result = db_query_range("SELECT alt_login FROM {alt_login} WHERE LOWER(alt_login) LIKE LOWER('%s%%')", $string, 0, 10);
    while ($user = db_fetch_object($result)) {
      $matches[$user->alt_login] = check_plain($user->alt_login);
    }
  }
  print drupal_to_js($matches);
  exit();
}

/**
 * Page callback that allows a user with the right permissions to become
 * the selected user.
 */
function masquerade_switch_user($uid) {
  $new_user = user_load(array('uid' => $uid));
  if (!$new_user->uid) {
    return drupal_not_found();
  }

  $roles = array_keys(array_filter(variable_get('masquerade_admin_roles', array())));
  $perm = $uid == 1 || array_intersect(array_keys($new_user->roles), $roles) ?
    'masquerade as admin' :
    'masquerade as user';
  // check to see if we need admin permission
  if (!user_access($perm) && !$GLOBALS['masquerading']) {
    return drupal_access_denied();
  }

  global $user;

  if ($user->uid == $uid || $user->masquerading) {
    return drupal_access_denied();
  }

  if (variable_get('site_offline', 0) && !user_access('administer site configuration', $new_user)) {
    drupal_set_message(t('The user is not allowed to access site in off-line mode!'), 'error');
    return drupal_access_denied();
  }

  db_query("INSERT INTO {masquerade} (uid_from, uid_as, sid) VALUES (%d, %d, '%s')",
    $user->uid, $new_user->uid, session_id());
  // switch user

  watchdog('masquerade', t('User %user now masquerading as %masq_as.', array('%user' => $user->name, '%masq_as' => $new_user->name)));
  drupal_set_message(t('Now masquerading as %masq_as.', array('%masq_as' => $new_user->name)));
  $user->masquerading = $new_user->uid;
  $user = $new_user;
  drupal_goto(referer_uri());
}

/**
 * Page callback that allows a user who is currently masquerading to become
 * a new user.
 */
function masquerade_switch_back() {
  // switch user
  global $user;
  cache_clear_all($user->uid, 'cache_menu', true);
  $uid = db_result(db_query("SELECT uid_from FROM {masquerade} WHERE sid = '%s' AND uid_as = %d ", session_id(), $user->uid));
  // erase record
  db_query("DELETE FROM {masquerade} WHERE sid = '%s' AND uid_as = %d ", session_id(), $user->uid);
  $oldname = $user->name;
  $user = user_load(array('uid' => $uid));
  watchdog('masquerade', t('User %user no longer masquerading as %masq_as.', array('%user' => $user->name, '%masq_as' => $oldname)));
  drupal_set_message(t('No longer masquerading as %masq_as.', array('%masq_as' => $oldname)));
  drupal_goto(referer_uri());
}

function masquerade_version() {
  return str_replace(array('$RCSf'.'ile:', ',v', '$Re'.'vision: ', '$Da'.'te: ', '$'), '', '<p style="font-size:x-small">$RCSfile: masquerade.module,v $ version: <b>$Revision: 1.15.2.12 $</b>, $Date: 2008/09/25 12:09:34 $</p>');
}
