<?php
// $Id: versioncontrol_account_status.module,v 1.36 2009/04/03 22:06:57 jpetso Exp $
/**
 * @file
 * Version Control Account Status - Require users to submit a motivation text
 * and meet approval of version control administrators before their VCS account
 * is enabled.
 *
 * Copyright 2006, 2007 by Derek Wright ("dww", http://drupal.org/user/46549)
 * Copyright 2007 by Jakob Petsovits ("jpetso", http://drupal.org/user/56020)
 */

// Status values for VCS access applications.
define('VERSIONCONTROL_ACCOUNT_STATUS_UNDEFINED', 0); // not in the database
define('VERSIONCONTROL_ACCOUNT_STATUS_ALL',       0); // any of the following
define('VERSIONCONTROL_ACCOUNT_STATUS_QUEUED',    1); // not yet looked at
define('VERSIONCONTROL_ACCOUNT_STATUS_PENDING',   2); // needs more info from the applicant
define('VERSIONCONTROL_ACCOUNT_STATUS_APPROVED',  3); // evaluated and approved
define('VERSIONCONTROL_ACCOUNT_STATUS_DECLINED',  4); // evaluated, but not approved
define('VERSIONCONTROL_ACCOUNT_STATUS_DISABLED',  5); // was approved, but has been revoked

// Include site specific overrides if they exist.
$path = drupal_get_path('module', 'versioncontrol_account_status');
if (file_exists($path .'/versioncontrol_account_status_local.inc')) {
  include_once($path .'/versioncontrol_account_status_local.inc');
}


function _versioncontrol_account_status_get_status_options($include_undefined = FALSE) {
  $options = array();
  if ($include_undefined) {
    $options[VERSIONCONTROL_ACCOUNT_STATUS_UNDEFINED] = t('Undefined');
  }
  $options[VERSIONCONTROL_ACCOUNT_STATUS_QUEUED] = t('Queued');
  $options[VERSIONCONTROL_ACCOUNT_STATUS_PENDING] = t('Pending');
  $options[VERSIONCONTROL_ACCOUNT_STATUS_APPROVED] = t('Approved');
  $options[VERSIONCONTROL_ACCOUNT_STATUS_DECLINED] = t('Declined');
  $options[VERSIONCONTROL_ACCOUNT_STATUS_DISABLED] = t('Disabled');
  return $options;
}

/**
 * Implementation of hook_versioncontrol_authorization_methods().
 *
 * @return
 *   A structured array containing information about authorization methods
 *   provided by this module, wrapped in a structured array. Array keys are
 *   the unique string identifiers of each authorization method, and
 *   array values are the user-visible method descriptions (wrapped in t()).
 */
function versioncontrol_account_status_versioncontrol_authorization_methods() {
  return array(
    'versioncontrol_account_status_repository' => t('User application required'),
  );
}

/**
 * Retrieve a list of necessary requirements that each applicant must agree to
 * in order to get the application approved. This list can be replaced by
 * implementing versioncontrol_account_status_local_conditions() in the
 * versioncontrol_account_status_local.inc file.
 *
 * @return
 *   An array of conditions that are afterwards transformed into form elements.
 *   Each condition will be shown to the user together with "No"/"Yes"
 *   radio buttons. All the elements must be marked with "Yes" by the user
 *   in order to get the application submitted.
 *   The array key of each element will be the array key of the corresponding
 *   form element, and the value is the again a structured array that contains
 *   the following user-visible (translated) strings:
 *   - 'description': The statement that is shown on the application form.
 *   - 'error': The error message that is shown if the user stays with "No".
 */
function _versioncontrol_account_status_get_conditions($default_condition_description, $default_condition_error) {
  if (function_exists('versioncontrol_account_status_local_conditions')) {
    return versioncontrol_account_status_local_conditions();
  }
  else {
    return array(
      'policy' => array(
        'description' => $default_condition_description,
        'error' => $default_condition_error,
      ),
    );
  }
}

/**
 * Return information about the VCS account status of a given user,
 * either globally or for a given repository.
 *
 * @param $uid
 *   The Drupal user id of the user whose account status data
 *   should be retrieved.
 * @param $repo_id
 *   The repository id of the repository for which the user's status data
 *   should be retrieved.
 *
 * @return
 *   A structured array of account status data,
 *   containing the following elements:
 *
 *   - 'uid': The user id that was passed to this function.
 *   - 'repo_id': The repository id that was passed to this function.
 *   - 'motivation': The motivation text that the user submitted.
 *        If no motivation text has been submitted yet,
 *        this is an empty string.
 *   - 'status': One of the status constants:
 *     - VERSIONCONTROL_ACCOUNT_STATUS_UNDEFINED:
 *         There is no status data yet for this user and repository,
 *         which means that the user hasn't applied for approval yet.
 *     - VERSIONCONTROL_ACCOUNT_STATUS_QUEUED:
 *         The user has applied for an approval, but the VCS administrator
 *         hasn't yet looked at the application.
 *     - VERSIONCONTROL_ACCOUNT_STATUS_PENDING:
 *         The administrator needs more information from the applicant
 *         in order to properly evaluate the application.
 *     - VERSIONCONTROL_ACCOUNT_STATUS_APPROVED:
 *         The application has been evaluated and approved,
 *         and the user may have access to the repository.
 *     - VERSIONCONTROL_ACCOUNT_STATUS_DECLINED:
 *         The application has been evaluated and disapproved.
 *         The user can, however, reapply.
 *     - VERSIONCONTROL_ACCOUNT_STATUS_DISABLED:
 *         The application had been approved in the past, but was revoked.
 *         The user doesn't have repository access anymore.
 */
function versioncontrol_account_status_get($uid, $repo_id) {
  $result = db_query('SELECT uid, repo_id, motivation, status
                      FROM {versioncontrol_account_status_users}
                      WHERE uid = %d AND repo_id = %d', $uid, $repo_id);

  while ($data = db_fetch_array($result)) {
    return $data;
  }
  // If there is no entry, return a default result value.
  return array(
    'uid' => $uid,
    'repo_id' => $repo_id,
    'motivation' => '',
    'status' => VERSIONCONTROL_ACCOUNT_STATUS_UNDEFINED,
  );
}

/**
 * Store VCS account status information in the database.
 *
 * @param $data
 *   A structured array containing the 'uid', 'repo_id', 'motivation'
 *   and 'status' elements, in the same format as the result value
 *   of versioncontrol_account_status_get().
 */
function versioncontrol_account_status_set($status_data) {
  db_query('DELETE FROM {versioncontrol_account_status_users}
            WHERE uid = %d AND repo_id = %d',
            $status_data['uid'], $status_data['repo_id']);
  db_query("INSERT INTO {versioncontrol_account_status_users}
            (uid, repo_id, motivation, status)
            VALUES (%d, %d, '%s', %d)",
            $status_data['uid'], $status_data['repo_id'],
            $status_data['motivation'], $status_data['status']);
}

/**
 * Implementation of hook_versioncontrol_is_account_authorized():
 * Let the Version Control API know whether the given user is approved or not.
 *
 * @return
 *   TRUE if the account is authorized, or FALSE if it's not.
 */
function versioncontrol_account_status_versioncontrol_is_account_authorized($repository, $uid) {
  if ($repository['authorization_method'] != 'versioncontrol_account_status_repository') {
    return TRUE;
  }
  $result = db_query('SELECT status
                      FROM {versioncontrol_account_status_users}
                      WHERE uid = %d AND repo_id = %d',
                      $uid, $repository['repo_id']);

  while ($account = db_fetch_object($result)) {
    return ($account->status == VERSIONCONTROL_ACCOUNT_STATUS_APPROVED);
  }
  return FALSE;
}

/**
 * Implementation of hook_versioncontrol_alter_repository_selection():
 * Alter the list of repositories that are available for user registration
 * and editing. Mind that this function is only called for non-admin users,
 * as the version control administrator should always be able to create any
 * accounts without being restricted.
 *
 * @param $repository_names
 *   The list of repository names as it is shown in the select box
 *   at 'versioncontrol/register'. Array keys are the repository ids,
 *   and array elements are the captions in the select box.
 *   There's two things that can be done with this array:
 *   - Change (amend) the caption, in order to provide more information
 *     for the user. (E.g. note that an application is necessary.)
 *   - Unset any number of array elements. If you do so, the user will not
 *     be able to register a new account for this repository.
 * @param $repositories
 *   A list of repositories (with the repository ids as array keys) that
 *   includes at least all of the repositories that correspond to the
 *   repository ids of the @p $repository_names array.
 */
function versioncontrol_account_status_versioncontrol_alter_repository_selection(&$repository_names, $repositories) {
  foreach ($repository_names as $repo_id => $caption) {
    if ($repositories[$repo_id]['authorization_method'] == 'versioncontrol_account_status_repository') {
      $repository_names[$repo_id] = t('!repository (application required)', array(
        '!repository' => $repository_names[$repo_id],
      ));
    }
  }
}


/**
 * Implementation of hook_versioncontrol_alter_account_list():
 * Add additional columns into the list of VCS accounts.
 * By changing the @p $header and @p $rows_by_uid arguments,
 * the account list can be customized accordingly.
 *
 * @param $accounts
 *   The list of accounts that is being displayed in the account table. This is
 *   a structured array like the one returned by versioncontrol_get_accounts().
 * @param $repositories
 *   An array of repositories where the given users have a VCS account.
 *   Array keys are the repository ids, and array values are the
 *   repository arrays like returned from versioncontrol_get_repository().
 * @param $header
 *   A list of columns that will be passed to theme('table').
 * @param $rows_by_uid
 *   An array of existing table rows, with Drupal user ids as array keys.
 *   Each row already includes the generic column values, and for each row
 *   there is an account with the same uid given in the @p $accounts parameter.
 */
function versioncontrol_account_status_versioncontrol_alter_account_list($accounts, $repositories, &$header, &$rows_by_uid) {
  $accounts_column_index = 1;
  $status_column_index = NULL;
  $new_header = array();

  // insert a new 'Account status' column after the 'Accounts' one
  foreach ($header as $index => $column) {
    if ($index == $accounts_column_index) {
      $new_header[] = $column;
      $new_header[] = t('Account status');
    }
    else {
      $new_header[] = $column;
    }
  }
  $header = $new_header;

  // Same for the actual status field in each row.
  foreach ($rows_by_uid as $uid => $row) {
    $new_row = array();

    foreach ($row as $index => $column) {
      if ($index == $accounts_column_index) {
        $new_row[] = $column;
        $new_row[] = _versioncontrol_account_status_get_cell(
          $uid, $accounts[$uid], $repositories
        );
      }
      else {
        $new_row[] = $column;
      }
    }
    $rows_by_uid[$uid] = $new_row;
  }
}

/**
 * The actual 'Status' cell for each row in the accounts list.
 */
function _versioncontrol_account_status_get_cell($uid, $usernames_by_repository, $repositories) {
  $status_options = _versioncontrol_account_status_get_status_options(TRUE);
  $statuses = array();

  foreach ($usernames_by_repository as $repo_id => $username) {
    if ($repositories[$repo_id]['authorization_method'] == 'versioncontrol_account_status_repository') {
      $status_data = versioncontrol_account_status_get($uid, $repo_id);
      $statuses[] = t('@repository: !status', array(
        '!status' => $status_options[$status_data['status']],
        '@repository' => $repositories[$repo_id]['name'],
      ));
    }
    else {
      $statuses[] = t('@repository: !status', array(
        '!status' => t('n/a'),
        '@repository' => $repositories[$repo_id]['name'],
      ));
    }
  }
  return theme('item_list', $statuses);
}


/**
 * Implementation of hook_form_alter():
 * Change the form according to the user's account status.
 */
function versioncontrol_account_status_form_alter(&$form, $form_state, $form_id) {
  switch ($form['#id']) {
    case 'versioncontrol-repository-form':
      versioncontrol_account_status_form_alter_repository_settings($form);
      break;

    case 'versioncontrol-account-form':
      if ($form['#repository']['authorization_method'] == 'versioncontrol_account_status_repository') {
        versioncontrol_account_status_form_alter_account(
          $form, $form['#repository']['repo_id']
        );
      }
      break;

    case 'versioncontrol-account-filter-form':
      versioncontrol_account_status_form_alter_account_filter($form);
      break;
  }
}


/**
 * Implementation of hook_form_alter() for Version Control API's
 * account filtering form on the admin account list.
 */
function versioncontrol_account_status_form_alter_account_filter(&$form) {
  $options = array_merge(
    array(VERSIONCONTROL_ACCOUNT_STATUS_ALL => t('All')),
    _versioncontrol_account_status_get_status_options()
  );

  if (!isset($_SESSION['versioncontrol_account_status_filter'])) {
    $_SESSION['versioncontrol_account_status_filter'] = VERSIONCONTROL_ACCOUNT_STATUS_QUEUED;
  }

  $form['submit']['#submit'][] = 'versioncontrol_account_status_account_filter_form_submit';

  $form['versioncontrol_account_status'] = array(
    '#type' => 'select',
    '#title' => t('Status'),
    '#options' => $options,
    '#default_value' => $_SESSION['versioncontrol_account_status_filter'],
    '#weight' => 0,
  );
}

/**
 * Additional submit handler for the account filter form.
 */
function versioncontrol_account_status_account_filter_form_submit($form, &$form_state) {
  $_SESSION['versioncontrol_account_status_filter'] =
    $form_state['values']['versioncontrol_account_status'];
}

/**
 * Implementation of hook_versioncontrol_filter_accounts():
 * Unset filtered accounts before they are even attempted to be displayed
 * on the account list.
 *
 * @param $accounts
 *   The accounts that would normally be displayed, in the same format as the
 *   return value of versioncontrol_get_accounts(). Entries in this list
 *   may be unset by this filter function.
 */
function versioncontrol_account_status_versioncontrol_filter_accounts(&$accounts) {
  if (empty($accounts)) {
    return;
  }
  if (!isset($_SESSION['versioncontrol_account_status_filter'])) {
    $_SESSION['versioncontrol_account_status_filter'] = 0;
  }
  $filter_status = $_SESSION['versioncontrol_account_status_filter'];

  if ($filter_status == VERSIONCONTROL_ACCOUNT_STATUS_ALL) {
    return; // Don't change the list if "All" statuses are selected.
  }

  $uid_constraints = array();
  $params = array($filter_status);
  foreach ($accounts as $uid => $usernames_by_repository) {
    $uid_constraints[] = 'uid = %d';
    $params[] = $uid;
  }

  // Retrieve all users with the given status.
  $result = db_query('SELECT DISTINCT(uid) FROM {versioncontrol_account_status_users}
                      WHERE status = %d
                       AND ('. implode(' OR ', $uid_constraints) .')', $params);

  $filtered_accounts = array();
  while ($account = db_fetch_object($result)) {
    $filtered_accounts[$account->uid] = $accounts[$account->uid];
  }
  $accounts = $filtered_accounts;
}


/**
 * Implementation of hook_form_alter() for Version Control API's
 * repository settings form.
 */
function versioncontrol_account_status_form_alter_repository_settings(&$form) {
  $backends = versioncontrol_get_backends();
  $vcs_name = $backend[$form['#vcs']]['name'];
  $repository = $form['#repository'];

  $form['#versioncontrol_account_status'] = TRUE;

  // Get form and email messages (only the default ones for new repositories).
  $strings = ($repository['repo_id'] == 0)
    ? _versioncontrol_account_status_get_presets()
    : _versioncontrol_account_status_get_strings($repository['repo_id']);

  // Save screen space as there are going to be more fieldsets in here.
  $form['account_registration']['repository_messages']['#collapsed'] = TRUE;

  // Markup strings for the application/registration form.
  $form['account_registration']['versioncontrol_account_status_application_form'] = array(
    '#type' => 'fieldset',
    '#title' => t('Account application form messages'),
    '#description' => t('If account applications are required, the following messages are also shown on the account registration form.'),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
    '#weight' => 4,
  );

  if (!function_exists('versioncontrol_account_status_local_conditions')) {
    $form['account_registration']['versioncontrol_account_status_application_form']['versioncontrol_account_status_default_condition_description'] = array(
      '#type' => 'textfield',
      '#title' => t('Required pledge'),
      '#description' => t('A statement that the user needs to answer with "Yes" in order to proceed with the application. (If more of these statements are required, you can add those with custom code.)'),
      '#default_value' => $strings['default_condition_description'],
      '#size' => 100,
    );
    $form['account_registration']['versioncontrol_account_status_application_form']['versioncontrol_account_status_default_condition_error'] = array(
      '#type' => 'textfield',
      '#title' => t('Error message for the required pledge'),
      '#description' => t('The error text that is shown when the user doesn\'t select "Yes" for the above statement.'),
      '#default_value' => $strings['default_condition_error'],
      '#size' => 100,
    );
  }
  else {
    $form['account_registration']['versioncontrol_account_status_default_condition_description'] = array(
      '#type' => 'value',
      '#value' => $strings['default_condition_description'],
    );
    $form['account_registration']['versioncontrol_account_status_default_condition_error'] = array(
      '#type' => 'value',
      '#value' => $strings['default_condition_error'],
    );
  }

  $form['account_registration']['versioncontrol_account_status_application_form']['versioncontrol_account_status_motivation_description'] = array(
    '#type' => 'textarea',
    '#title' => t('Motivation message description'),
    '#description' => t('Help text to display below the motivation text input field of an application.'),
    '#default_value' => $strings['motivation_description'],
  );

  // Email messages for account status updates.
  $form['account_registration']['versioncontrol_account_status_email'] = array(
    '#type' => 'fieldset',
    '#title' => t('Account application e-mail messages'),
    '#description' => t('The following messages are templates for
      e-mail messages sent to users who have applied for a @vcs account.
      The following variables are substituted (where available)
      when the message is sent.
      <ul>
        <li>%account-name - The Drupal username of the applicant.</li>
        <li>%user-account-url - The URL of the applicant\'s user page.</li>
        <li>%manage-account-url - The URL where the user can manage his VCS account for the appropriate repository.</li>
        <li>%vcs-name - The name of the version control system, for example "CVS" or "Subversion".</li>
        <li>%repository-name - The name of the repository for the applied account.</li>
        <li>%admin-message - Additional guidelines specified by the repository administrator.</li>
        <li>%admin-name - The Drupal username of the repository administrator.</li>
        <li>%motivation-message - The motivation message of the applicant.</li>
        <li>%client-information - The applicant\'s IP and browser related information.</li>
      </ul>', array('@vcs' => $vcs_name)
    ),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
    '#weight' => 5,
  );

  $form['account_registration']['versioncontrol_account_status_email']['versioncontrol_account_status_user_notification_email'] = array(
    '#type' => 'textarea',
    '#title' => t('"Application received" e-mail message'),
    '#description' => t('The message to send to users whose application has been received.'),
    '#default_value' => $strings['user_notification_email'],
  );
  $form['account_registration']['versioncontrol_account_status_email']['versioncontrol_account_status_admin_notification_email'] = array(
    '#type' => 'textarea',
    '#title' => t('"New application" e-mail message'),
    '#description' => t('The message sent to the administrator when a user has submitted an application.'),
    '#default_value' => $strings['admin_notification_email'],
  );
  $form['account_registration']['versioncontrol_account_status_email']['versioncontrol_account_status_approved_email'] = array(
    '#type' => 'textarea',
    '#title' => t('"Account approved" e-mail message'),
    '#description' => t('The message to send to users whose accounts have been approved.'),
    '#default_value' => $strings['approved_email'],
  );
  $form['account_registration']['versioncontrol_account_status_email']['versioncontrol_account_status_pending_email'] = array(
    '#type' => 'textarea',
    '#title' => t('"Account pending" e-mail message'),
    '#description' => t('The message to send to users whose accounts cannot be approved yet.'),
    '#default_value' => $strings['pending_email'],
  );
  $form['account_registration']['versioncontrol_account_status_email']['versioncontrol_account_status_declined_email'] = array(
    '#type' => 'textarea',
    '#title' => t('@vcs account declined e-mail message', array('@vcs' => $vcs_name)),
    '#description' => t('The message to send to users whose accounts have been declined.'),
    '#default_value' => $strings['declined_email'],
  );
  $form['account_registration']['versioncontrol_account_status_email']['versioncontrol_account_status_disabled_email'] = array(
    '#type' => 'textarea',
    '#title' => t('@vcs account disabled e-mail message', array('@vcs' => $vcs_name)),
    '#description' => t('The message to send to users whose (previously approved) accounts have been disabled.'),
    '#default_value' => $strings['disabled_email'],
  );
}

/**
 * Implementation of hook_versioncontrol_repository_submit():
 * Extract message and email strings from the repository editing/adding form's
 * submitted values, and add it to the @p $repository array. Later, that array
 * will be passed to hook_versioncontrol_repository() as part of the repository
 * insert/update procedure.
 */
function versioncontrol_account_status_versioncontrol_repository_submit(&$repository, $form, $form_state) {
  // If the form wasn't altered at all, let it be.
  if (!isset($form['#versioncontrol_account_status'])) {
    return;
  }
  $value_names = _versioncontrol_account_status_get_value_names();
  $strings = array();

  foreach ($value_names as $name) {
    $strings[$name] = $form_state['values']['versioncontrol_account_status_'. $name];
  }
  $repository['versioncontrol_account_status']['strings'] = $strings;
}

/**
 * Implementation of hook_versioncontrol_repository():
 * Manage (insert, update or delete) additional repository data in the database.
 *
 * @param $op
 *   Either 'insert' when the repository has just been created, or 'update'
 *   when repository name, root, URL backend or module specific data change,
 *   or 'delete' if it will be deleted after this function has been called.
 *
 * @param $repository
 *   The repository array containing the repository. It's a single
 *   repository array like the one returned by versioncontrol_get_repository(),
 *   so it consists of the following elements:
 *
 *   - 'repo_id': The unique repository id.
 *   - 'name': The user-visible name of the repository.
 *   - 'vcs': The unique string identifier of the version control system
 *        that powers this repository.
 *   - 'root': The root directory of the repository. In most cases,
 *        this will be a local directory (e.g. '/var/repos/drupal'),
 *        but it may also be some specialized string for remote repository
 *        access. How this string may look like depends on the backend.
 *   - 'authorization_method': The string identifier of the repository's
 *        authorization method, that is, how users may register accounts
 *        in this repository. Modules can provide their own methods
 *        by implementing hook_versioncontrol_authorization_methods().
 *   - 'url_backend': The prefix (excluding the trailing underscore)
 *        for URL backend retrieval functions.
 *   - '[xxx]_specific': An array of VCS specific additional repository
 *        information. How this array looks like is defined by the
 *        corresponding backend module (versioncontrol_[xxx]).
 *   - '???': Any other additions that modules added by implementing
 *        hook_versioncontrol_repository_submit().
 */
function versioncontrol_account_status_versioncontrol_repository($op, $repository) {
  if (!isset($repository['versioncontrol_account_status'])) {
    return;
  }
  $strings = $repository['versioncontrol_account_status']['strings'];

  switch ($op) {
    case 'update':
    case 'insert':
      _versioncontrol_account_status_set_strings($repository['repo_id'], $strings);
      break;

    case 'delete':
      db_query('DELETE FROM {versioncontrol_account_status_strings}
                WHERE repo_id = %d', $repository['repo_id']);
      break;
  }
}

function _versioncontrol_account_status_get_value_names() {
  return array(
    'default_condition_description',
    'default_condition_error',
    'motivation_description',
    'user_notification_email',
    'admin_notification_email',
    'approved_email',
    'pending_email',
    'declined_email',
    'disabled_email',
  );
}


/**
 * Implementation of hook_form_alter() for the register/edit account form.
 */
function versioncontrol_account_status_form_alter_account(&$form, $repo_id) {
  global $user;

  $uid = $form['#uid'];
  $data = versioncontrol_account_status_get($uid, $repo_id);

  $admin_access = versioncontrol_admin_access();
  if ($admin_access) {
    versioncontrol_account_status_form_alter_account_edit($form, $data, $admin_access);
    return;
  }

  switch ($data['status']) {
    case VERSIONCONTROL_ACCOUNT_STATUS_QUEUED:
    case VERSIONCONTROL_ACCOUNT_STATUS_PENDING:
      versioncontrol_account_status_form_alter_pending($form, $data);
      break;

    case VERSIONCONTROL_ACCOUNT_STATUS_UNDEFINED:
    case VERSIONCONTROL_ACCOUNT_STATUS_DECLINED:
    case VERSIONCONTROL_ACCOUNT_STATUS_DISABLED:
      versioncontrol_account_status_form_alter_account_edit($form, $data, $admin_access);
      break;

    case VERSIONCONTROL_ACCOUNT_STATUS_APPROVED:
      $form['motivation_fieldset'] = array(
        '#type' => 'fieldset',
        '#title' => t('My motivation message'),
        '#weight' => 20,
      );
      $form['motivation_fieldset']['motivation_display'] = array(
        '#type' => 'item',
        '#value' => check_markup($data['motivation']),
      );
      break;

    default: // does not happen, but I cannot live without a default branch
      break;
  }
}

/**
 * Replace the current form with one that tells the user
 * that the application is pending.
 */
function versioncontrol_account_status_form_alter_pending(&$form, $status_data) {
  $form['pending'] = array(
    '#type' => 'markup',
    '#value' => t('Your application has been received and is currently pending review.'),
    '#prefix' => '<p>',
    '#suffix' => '</p>',
  );
  if (module_exists('contact')) {
    $form['contact'] = array(
      '#type' => 'markup',
      '#value' => t('If there has been an undue delay in the processing of your application, please use the !contact-form to get in touch with us.', array('!contact-form' => l(t('contact form'), 'contact'))),
      '#prefix' => '<p>',
      '#suffix' => '</p>',
    );
  }

  // We can't unset account because other modules might still want
  // to add elements to the 'account' array, such as a password field.
  $form['account']['#prefix'] = '<!--';
  $form['account']['#suffix'] = '-->';
  unset($form['submit']);
}

function versioncontrol_account_status_form_alter_account_edit(&$form, $status_data, $admin_access) {
  global $user;

  if (!valid_email_address($user->mail)) {
    drupal_set_message(t('You need to provide a valid e-mail address to request commit access. Please edit your user information.'), 'error');
    drupal_goto('user/'. $user->uid .'/edit');
    // drupal_goto() calls exit() so script execution ends right here
  }

  if ($status_data['status'] == VERSIONCONTROL_ACCOUNT_STATUS_UNDEFINED) {
    // The status will not stay undefined when submitting this form.
    $status_data['status'] = VERSIONCONTROL_ACCOUNT_STATUS_QUEUED;
  }

  // Remember a few values for later on.
  $form['#versioncontrol_account_status_repo_id'] = $status_data['repo_id'];
  $form['#versioncontrol_account_status_uid'] = $status_data['uid'];

  $is_reapplication = FALSE;

  $reapplication_statuses = array(
    VERSIONCONTROL_ACCOUNT_STATUS_DECLINED,
    VERSIONCONTROL_ACCOUNT_STATUS_DISABLED,
  );
  if (!$admin_access && in_array($status_data['status'], $reapplication_statuses)) {
    $is_reapplication = TRUE;
    // let the user try again.
    $status_data['status'] = VERSIONCONTROL_ACCOUNT_STATUS_QUEUED;
  }
  $is_application = (empty($status_data['motivation']) || $is_reapplication);

  $strings = _versioncontrol_account_status_get_strings($status_data['repo_id']);

  // Users with declined or disabled accounts can reapply.
  if ($is_reapplication) {
    _versioncontrol_account_status_form_alter_reapplication($form);
  }

  $conditions = _versioncontrol_account_status_get_conditions(
    $strings['default_condition_description'], $strings['default_condition_error']
  );

  $form['motivation_fieldset'] = array(
    '#type' => 'fieldset',
    '#title' => t('Motivation'),
    '#weight' => 2,
  );

  // The motivation message is editable for new application submissions,
  // otherwise it will just be displayed as unmodifiable markup.
  if ($is_application) {
    $form['#send_application_mails'] = TRUE;

    if (!isset($form['overview'])) {
      $registration_message = versioncontrol_get_repository_registration_message($status_data['repo_id']);

      if (!empty($registration_message)) {
        $form['overview'] = array(
          '#type' => 'fieldset',
          '#title' => t('Overview'),
          '#weight' => -100,
        );
        $form['overview']['overview'] = array(
          '#type' => 'markup',
          '#value' => $registration_message,
        );
      }
    }

    foreach ($conditions as $element_name => $texts) {
      $form['motivation_fieldset'][$element_name] = array(
        '#type' => 'radios',
        '#title' => $texts['description'],
        '#default_value' => 0,
        '#options' => array(0 => t('No'), 1 => t('Yes')),
      );
    }
    $form['motivation_fieldset']['motivation'] = array(
      '#type' => 'textarea',
      '#title' => t('Motivation'),
      '#default_value' => $status_data['motivation'],
      '#cols' => 60,
      '#rows' => 9,
      '#description' => $strings['motivation_description'],
      '#required' => TRUE,
    );
  }
  else {
    $form['#skip_conditions_check'] = TRUE;
    $form['motivation_fieldset']['motivation'] = array(
      '#type' => 'value',
      '#value' => $status_data['motivation'],
    );
    $form['motivation_fieldset']['motivation_display'] = array(
      '#type' => 'item',
      '#title' => t('Motivation message'),
      '#value' => check_markup($status_data['motivation']),
    );
  }

  // On account edit forms, admins can change the status of
  // and send mails to the respective user.
  if ($admin_access) {
    _versioncontrol_account_status_form_alter_status_admin($form, $status_data);
  }
  else {
    $form['status'] = array(
      '#type' => 'value',
      '#value' => $status_data['status'],
    );
  }

  // Adapt the caption of the submit button, and add our validation handler.
  if (!$admin_access && isset($form['submit'])) { // not set in the demo mode
    $form['submit']['#value'] = t(
      'Request @vcs account', array('@vcs' => $form['#vcs_name'])
    );
  }
  $form['#validate'][] = 'versioncontrol_account_status_application_validate';
}

/**
 * Add a message for reapplying users as well as a 'is_reapplication'
 * form value, and disable the VCS username for account edit forms.
 */
function _versioncontrol_account_status_form_alter_reapplication(&$form) {
  $form['#is_reapplication'] = TRUE;

  $form['reapplication_message'] = array(
    '#type' => 'item',
    '#value' => t('If you are interested in reapplying for commit access, please amend your application accordingly and resubmit.'),
    '#prefix' => '<p><em>',
    '#suffix' => '</em></p>',
    '#weight' => -101, // -100 is for the registration message
  );
}

/**
 * Construct the fieldset that appears on the account edit form
 * if viewed by a version control administrator.
 */
function _versioncontrol_account_status_form_alter_status_admin(&$form, $status_data) {
  $form['status_admin'] = array(
    '#type' => 'fieldset',
    '#title' => t('Account status administration'),
    '#weight' => 3,
  );

  // Move the motivation textarea or the value plus display item
  // to this fieldset, and remove the obsolete other one.
  $form['status_admin']['motivation'] = $form['motivation_fieldset']['motivation'];
  if (isset($form['motivation_fieldset']['motivation_display'])) {
    $form['status_admin']['motivation_display'] = $form['motivation_fieldset']['motivation_display'];
  }
  $form['#skip_conditions_check'] = TRUE;
  unset($form['motivation_fieldset']);

  // Provide the admin with tools to edit the account status.
  $form['status_admin']['status'] = array(
    '#type' => 'radios',
    '#title' => t('Account status'),
    '#default_value' => $status_data['status'],
    '#options' => _versioncontrol_account_status_get_status_options(),
    '#description' => t('You can change the status of the user\'s VCS account.'),
  );
  $form['status_admin']['send_admin_message'] = array(
    '#type' => 'checkbox',
    '#title' => t('Inform the user by e-mail.'),
    '#default_value' => 1,
  );
  $form['status_admin']['admin_message'] = array(
    '#type' => 'textarea',
    '#title' => t('Reason/Message'),
    '#cols' => 50,
    '#rows' => 5,
    '#description' => t('The message you want to send to the user. This can be the reason for declining the application or an additional message after approval (used in e-mail).'),
  );
}

/**
 * Validation handler for the application form:
 * Make sure that all conditions have been answered with "Yes".
 */
function versioncontrol_account_status_application_validate($form, &$form_state) {
  $strings = _versioncontrol_account_status_get_strings(
    $form['#versioncontrol_account_status_repo_id']
  );
  $conditions = _versioncontrol_account_status_get_conditions(
    $strings['default_condition_description'], $strings['default_condition_error']
  );
  if (!isset($form['#skip_conditions_check'])) {
    foreach ($conditions as $element_name => $texts) {
      if ($form_state['values'][$element_name] != 1) {
        form_set_error($element_name, $texts['error']);
      }
    }
  }
}


/**
 * Implementation of hook_versioncontrol_account_submit():
 * Extract account status data from the account edit/register form's submitted
 * values, and add it to the @p $additional_data array. Later, that array
 * will be passed to hook_versioncontrol_account() as part of the account
 * insert/update procedure.
 */
function versioncontrol_account_status_versioncontrol_account_submit(&$additional_data, $form, $form_state) {
  // If the form wasn't altered at all, let it be.
  if (!isset($form['#versioncontrol_account_status_uid'])) {
    return;
  }

  $applicant_uid = $form['#versioncontrol_account_status_uid'];
  $repo_id = $form['#versioncontrol_account_status_repo_id'];
  $status_data = versioncontrol_account_status_get($applicant_uid, $repo_id);

  if (isset($form_state['values']['send_admin_message'])) { // if status administration was on the form
    if ($status_data['status'] == $form_state['values']['status']
        && $status_data['status'] == VERSIONCONTROL_ACCOUNT_STATUS_QUEUED) {
      // The QUEUED status should only indicate the initial status of an
      // application. Once it has been replied to, it should be set to PENDING.
      // In case the administrator forgets to do this, do it automatically.
      $form_state['values']['status'] = VERSIONCONTROL_ACCOUNT_STATUS_PENDING;
    }
  }
  $status_data['motivation'] = $form_state['values']['motivation'];
  $status_data['status'] = $form_state['values']['status'];

  $additional_data['versioncontrol_account_status'] = array(
    'status_data' => $status_data,
    'admin_message' => array(
      'is_send' => isset($form_state['values']['send_admin_message'])
        ? $form_state['values']['send_admin_message']
        : FALSE,
      'contents' => isset($form_state['values']['admin_message'])
        ? $form_state['values']['admin_message']
        : '',
    ),
    'is_reapplication' => !empty($form['#is_reapplication']),
    'send_application_mails' => $form['#send_application_mails'],
  );
}

/**
 * Implementation of hook_versioncontrol_account():
 * Insert the account status for accounts that are being inserted or updated,
 * and delete the account status for those that are being deleted.
 */
function versioncontrol_account_status_versioncontrol_account($op, $uid, $username, $repository, $additional_data = array()) {
  switch ($op) {
    case 'insert':
    case 'update':
      // Recap: if form_alter() wasn't applied, our array element is not set.
      $additional_data = isset($additional_data['versioncontrol_account_status'])
        ? $additional_data['versioncontrol_account_status']
        : NULL;

      if (!isset($additional_data)) {
        // Auto-approve the account if it was inserted programmatically and
        // the caller doesn't know anything about this module.
        if ($op == 'insert') {
          $additional_data = array(
            'status_data' => array(
              'uid' => $uid,
              'repo_id' => $repository['repo_id'],
              'motivation' => 'Inserted programmatically.',
              'status' => VERSIONCONTROL_ACCOUNT_STATUS_APPROVED,
            ),
            'admin_message' => array('is_send' => FALSE, 'contents' => ''),
            'send_application_mails' => FALSE,
          );
        }
        // Don't change the status for programmatical updates, though.
        if ($op == 'update') {
          break;
        }
      }

      $status_data = $additional_data['status_data'];
      versioncontrol_account_status_set($status_data);

      if ($additional_data['admin_message']['is_send']) {
        $contents = $additional_data['admin_message']['contents'];
        _versioncontrol_account_status_send_status_update_mails($status_data, $contents);
      }
      if ($additional_data['send_application_mails']) {
        $is_reapplication = !empty($additional_data['is_reapplication']);
        _versioncontrol_account_status_send_application_mails($status_data, $is_reapplication);
      }
      break;

    case 'delete':
      db_query('DELETE FROM {versioncontrol_account_status_users}
                WHERE uid = %d', $uid);
      break;
  }
}

/**
 * Send out notification mails in response to an application submission.
 */
function _versioncontrol_account_status_send_application_mails($status_data, $is_reapplication) {
  // Don't send notification mails if the admin submitted the application.
  if (versioncontrol_admin_access()) {
    return;
  }
  $admin_mail = variable_get('versioncontrol_email_address', 'versioncontrol@example.com');
  $applicant = user_load($status_data['uid']);

  $params = array(
    'applicant_user' => $applicant,
    'repository' => versioncontrol_get_repository($status_data['repo_id']),
    'motivation_message' => $status_data['motivation'],
    'status' => $status_data['status'],
    'is_reapplication' => $is_reapplication,
  );
  $backend = versioncontrol_get_backend($params['repository']);

  // Send an e-mail to the version control administrators.
  $from = "$applicant->name <$applicant->mail>";
  drupal_mail('versioncontrol_account_status', 'application_admin',
    $admin_mail, language_default(), $params, $from
  );

  // Send an e-mail to the applicant.
  $from = t('!site @vcs administrator <!mail>', array(
    '!site' => variable_get('site_name', 'Drupal'),
    '@vcs' => $backend['name'],
    '!mail' => $admin_mail,
  ));
  drupal_mail('versioncontrol_account_status', 'application_applicant',
    $applicant->mail, user_preferred_language($applicant), $params, $from
  );

  // Display a status message.
  drupal_set_message(t('Your application has been sent to the version control administrators and will be processed as soon as possible.'));
}

/**
 * Send a mail to the user with updated account status if the admin says so.
 */
function _versioncontrol_account_status_send_status_update_mails($status_data, $admin_message) {
  global $user; // = the admin who submitted the update form
  $admin_mail = variable_get('versioncontrol_email_address', 'versioncontrol@example.com');
  $applicant = user_load($status_data['uid']);

  $params = array(
    'admin_user' => drupal_clone($user),
    'applicant_user' => $applicant,
    'repository' => versioncontrol_get_repository($status_data['repo_id']),
    'admin_message' => $admin_message,
    'status' => $status_data['status'],
  );
  $backend = versioncontrol_get_backend($params['repository']);

  // Send an e-mail to the user whose account status has been updated.
  $from = t('!site @vcs administrator <!mail>', array(
    '!site' => variable_get('site_name', 'Drupal'),
    '@vcs' => $backend['name'],
    '!mail' => $admin_mail,
  ));
  drupal_mail('versioncontrol_account_status', 'status_update',
    "$applicant->mail, $admin_mail", user_preferred_language($applicant), $params, $from
  );
}

/**
 * Implementation of hook_mail().
 */
function versioncontrol_account_status_mail($key, &$message, $params) {
  $is_application_mail = in_array($key, array('application_admin', 'application_applicant'));
  $is_update_mail = ($key == 'status_update');

  // Gather data for the notification mails.
  $strings = _versioncontrol_account_status_get_strings($params['repository']['repo_id']);
  $backend = versioncontrol_get_backend($params['repository']);

  $variables = array(
    '%account-name' => $params['applicant_user']->name,
    '%user-account-url' => url('user/'. $params['applicant_user']->uid, array('absolute' => TRUE)),
    '%manage-account-url' => url('user/'. $params['applicant_user']->uid .'/edit/versioncontrol/'. $params['repository']['repo_id'], array('absolute' => TRUE)),
    '%vcs-name' => $backend['name'],
    '%repository-name' => check_plain($params['repository']['name']),
    '%motivation-message' => wordwrap($params['motivation_message'], 72),
  );

  if ($is_application_mail) {
    $client_information = array();
    $client_information[] = @gethostbyaddr(ip_address());
    $client_information[] = $_SERVER['HTTP_USER_AGENT'];

    $variables['%client-information'] = implode("\n", $client_information);

    $message['subject'] = t('@vcs account request', array('@vcs' => $backend['name']));

    if (isset($params['is_reapplication'])) {
      $message['subject'] = t('!subject [reapplication]', array(
        '!subject' => $message['subject'],
      ));
    }

    switch ($key) {
      case 'application_admin':
        $message['body'] = strtr($strings['admin_notification_email'], $variables);
        break;
      case 'application_applicant':
        $message['body'] = strtr($strings['user_notification_email'], $variables);
        break;
    }
  }
  if ($is_update_mail) {
    if (!empty($params['admin_message'])) {
      $params['admin_message'] = t('Message from the @vcs maintainer:',
        array('@vcs' => $backend['name'])) ."\n". $params['admin_message'] ."\n";
    }

    switch ($params['status']) {
      case VERSIONCONTROL_ACCOUNT_STATUS_QUEUED:
      case VERSIONCONTROL_ACCOUNT_STATUS_PENDING:
        $params['admin_message'] = empty($params['admin_message'])
          ? t('Question:') ."\n". $params['admin_message'] ."\n\n"
          : '';
        break;
      case VERSIONCONTROL_ACCOUNT_STATUS_DECLINED:
      case VERSIONCONTROL_ACCOUNT_STATUS_DISABLED:
        $params['admin_message'] = empty($params['admin_message'])
          ? t('Reason:') ."\n". $params['admin_message'] ."\n\n"
          : '';
        break;
      default:
        break;
    }

    $variables['%admin-message'] = $params['admin_message'];
    $variables['%admin-name'] = $params['admin_user']->name;

    $message['subject'] = t('@vcs account maintenance', array('@vcs' => $backend['name']));

    switch ($params['status']) {
      case VERSIONCONTROL_ACCOUNT_STATUS_PENDING:
      case VERSIONCONTROL_ACCOUNT_STATUS_QUEUED:
        $message['body'] = strtr($strings['pending_email'], $variables);
        break;
      case VERSIONCONTROL_ACCOUNT_STATUS_APPROVED:
        $message['body'] = strtr($strings['approved_email'], $variables);
        break;
      case VERSIONCONTROL_ACCOUNT_STATUS_DECLINED:
        $message['body'] = strtr($strings['declined_email'], $variables);
        break;
      case VERSIONCONTROL_ACCOUNT_STATUS_DISABLED:
        $message['body'] = strtr($strings['disabled_email'], $variables);
        break;
    }
  }

  if (!isset($message['body'])) {
    $message['body'] = 'Error in versioncontrol_account_status.module, should not happen.';
  }
}


/**
 * Return the admin-defined strings for the given repository id, or an array of
 * preset strings if the admin hasn't defined any yet.
 */
function _versioncontrol_account_status_get_strings($repo_id) {
  $result = db_query('SELECT default_condition_description,
                             default_condition_error, motivation_description,
                             user_notification_email, admin_notification_email,
                             approved_email, pending_email, declined_email,
                             disabled_email
                      FROM {versioncontrol_account_status_strings}
                      WHERE repo_id = %d', $repo_id);
  while ($strings = db_fetch_array($result)) {
    return $strings;
  }

  // If no entry was found, return the default values.
  return _versioncontrol_account_status_get_presets();
}

/**
 * Store a set of admin-defined strings for the given repository id
 * in the database.
 */
function _versioncontrol_account_status_set_strings($repo_id, $strings) {
  db_query('DELETE FROM {versioncontrol_account_status_strings}
            WHERE repo_id = %d', $repo_id);

  db_query(
    "INSERT INTO {versioncontrol_account_status_strings}
      (repo_id, default_condition_description, default_condition_error,
       motivation_description, user_notification_email,
       admin_notification_email, approved_email, pending_email, declined_email,
       disabled_email)
      VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s')",
    $repo_id, $strings['default_condition_description'],
    $strings['default_condition_error'], $strings['motivation_description'],
    $strings['user_notification_email'], $strings['admin_notification_email'],
    $strings['approved_email'], $strings['pending_email'],
    $strings['declined_email'], $strings['disabled_email']
  );
}

/**
 * Return preset values that are used in the application forms.
 */
function _versioncontrol_account_status_get_presets() {
  $presets = array();

  $presets['default_condition_description'] = t('I will adhere to both commit policies and licensing policies of this project');

  $presets['default_condition_error'] = t('Your application cannot be considered: you need to agree with our policies in order to gain commit access.');

  $presets['motivation_description'] = t('A message providing information about yourself and your planned contributions.
<ul>
  <li>Describe what modules, themes, or translations you want to maintain and why. Please provide as much information as possible along with appropriate links if available.</li>
  <li>If your contributions implement existing functionality explain why you want to duplicate it or why you cannot extend any of the existing projects.</li>
  <li>If you have been asked to co-maintain an existing module, please link to an issue where the current maintainer explicitly states so.</li>
  <li>The more information, the better.</li>
</ul>
<strong>Failure to provide a well written motivation message will result in your application being denied.</strong>');


  // For email strings, arguments are supplanted when the mail is sent.

  $presets['user_notification_email'] = t('%account-name,
  Your %vcs-name account request has been received and will be processed as soon as possible.

Kind regards,
Drupal %vcs-name administrator.');

  $presets['admin_notification_email'] = t('User:
  %user-account-url

Motivation:
  %motivation-message

Client information:
  %client-information');

  $presets['approved_email'] = t('%account-name,
  We are pleased to inform you that your %vcs-name account request has been approved. It will be activated within one or two hours.

%admin-message
Useful links:
* http://cvs.drupal.org/viewcvs/drupal/contributions : The README.txt and FAQ.txt contain important guidelines on how to use your account.
* http://drupal.org/handbook/cvs : The CVS handbook details all that you need to know to use the Drupal CVS repositories.
* http://drupal.org/contributors-guide : The contributors guide should provide all pertinent information on contributing to Drupal.
* http://drupal.org/mailing-lists : It is recommended that you subscribe to the appropriate Drupal mailing lists.
* %manage-account-url : Manage your %vcs-name password.

Please do not hesitate to contact us if you have any questions. Welcome on board!

Kind regards,
%admin-name.

--------------------
User:
  %user-account-url

Motivation message:
  %motivation-message');

  $presets['pending_email'] = t('%account-name,
  We cannot approve your %vcs-name account yet. Please answer the questions below and reply at your earliest convenience.

%admin-message
Kind regards,
%admin-name.

--------------------
User:
  %user-account-url

Motivation message:
  %motivation-message');

  $presets['declined_email'] = t('%account-name,
  We are sorry to inform you that your %vcs-name account request has been declined due to the reasons outlined below.

%admin-message
Please do not hesitate to contact us if you would like to discuss this further, as we can still review our position.

Kind regards,
%admin-name.

--------------------
User:
  %user-account-url

Motivation message:
  %motivation-message');

  $presets['disabled_email'] = t('%account-name,
  We are sorry to inform you that your %vcs-name account has been disabled due to the reasons outlined below.

%admin-message
Please do not hesitate to contact us if you would like to discuss this further, as we can still review our position.

Kind regards,
%admin-name.

--------------------
User:
  %user-account-url

Motivation message:
  %motivation-message');

  return $presets;
}
