<?php
// $Id$

/**
 * @file
 * Enables BOINC user functionality.
 *
 * Integrates user data and features of the existing
 * BOINC platorm with the Drupal concept of a user.
 */


/*  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *
 * Includes that provide supporting functions
 *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  */

require_once('includes/boincuser.forms.inc');
require_once('includes/boincuser.helpers.inc');
require_once('includes/boincuser.rules.inc');

/*  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *
 * Hooks into core modules
 *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  */

/**
 * Implementation of hook_menu(); determine the actions that correspond
 * with defined URL paths
 */
function boincuser_menu() {
  $items['account/posts'] = array(
    'title' => 'Recent posts',
    'description' => '',
    'page callback' => 'boincuser_goto_recent_posts',
    'access callback' => 'user_is_logged_in',
    'type' => MENU_CALLBACK,
  );
  $items['account/profile'] = array(
    'title' => '',
    'description' => '',
    'page callback' => 'boincuser_view_profile',
    'access callback' => 'user_is_logged_in',
    'type' => MENU_NORMAL_ITEM
  );
  $items['account/profile/view'] = array(
    'title' => 'View',
    'description' => 'Show a user profile',
    'page callback' => 'boincuser_view_profile',
    'access callback' => 'user_is_logged_in',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => 0
  );
  $items['account/profile/edit'] = array(
    'title' => 'Edit',
    'description' => 'Edit a user profile',
    'page callback' => 'boincuser_edit_profile',
    'access arguments' => array('edit own profile content'),
    'type' => MENU_LOCAL_TASK,
    'weight' => 5
  );
  $items['account/team'] = array(
    'title' => 'User team',
    'description' => '',
    'page callback' => 'boincuser_goto_team',
    'access callback' => 'user_is_logged_in',
    'type' => MENU_CALLBACK,
  );
  $items['moderate/profile/%user/approve'] = array(
    'title' => 'Profile approval',
    'description' => 'Approve profile content',
    'page callback' => 'boincuser_moderate_profile_approve',
    'page arguments' => array(2),
    'access arguments' => array('edit any profile content'),
    'type' => MENU_CALLBACK,
    'weight' => 5
  );
  $items['moderate/profile/%user/edit'] = array(
    'title' => 'Profile editor',
    'description' => 'Edit a user profile',
    'page callback' => 'boincuser_edit_profile',
    'page arguments' => array(2),
    'access arguments' => array('edit any profile content'),
    'type' => MENU_CALLBACK,
    'weight' => 5
  );
  $items['moderate/profile/%/reject'] = array(
    'title' => bts('Reject profile', array(), NULL, 'boinc:moderate-user'),
    'description' => 'Reject profile content',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('boincuser_moderate_profile_reject_form', 2),
    'access arguments' => array('edit any profile content'),
    'type' => MENU_CALLBACK,
    'weight' => 5
  );
  $items['moderate/user/%/ban'] = array(
    'title' => bts('Ban user', array(), NULL, 'boinc:moderate-ban-user'),
    'description' => 'Ban a user from using community features',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('boincuser_moderate_user_ban_form', 2),
    'access callback' => 'boincuser_moderate_community_access',
    'type' => MENU_CALLBACK,
  );
  $items['join'] = array(
    'title' => '',
    'description' => '',
    'page callback' => 'join_page',
    'access arguments' => array('access content'),
    'type' => MENU_NORMAL_ITEM
  );
  $items['join/new'] = array(
    'title' => bts("I'm new"),
    'page callback' => 'join_page',
    'page arguments' => array(1),
    'access arguments' => array('access content'),
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => 0
  );
  $items['join/boinc'] = array(
    'title' => bts("I'm a BOINC user"),
    'page callback' => 'join_page',
    'page arguments' => array(1),
    'access arguments' => array('access content'),
    'type' => MENU_LOCAL_TASK,
    'weight' => 5
  );
  $items['user/login/auth'] = array(
    'title' => bts('Authenticator login', array(), NULL, 'boinc:authenticator-login-page'),
    'description' => 'Log in using a user authenticator',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('boincuser_authloginform'),
    'access arguments' => array('access content'),
    'type' => MENU_CALLBACK,
  );
  $items['user/termsofuse'] = array(
    'title' => bts('Terms of Use', array(), NULL, 'boinc:termsofuse-form'),
    'description' => 'A site\'s term of use.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('boincuser_termsofuse_form'),
    'access callback' => 'user_is_logged_in',
    'type' => MENU_CALLBACK,
  );
  $items['user_control'] = array(
    'page callback' => 'boincuser_control',
    'access arguments' => array('access user profiles'),
    'type' => MENU_CALLBACK
  );
  $items['admin/boinc'] = array(
    'title' => 'BOINC configuration',
    'position' => 'right',
    'weight' => -8,
    'page callback' => 'system_admin_menu_block_page',
    'access arguments' => array('administer site configuration'),
    'file' => 'system.admin.inc',
    'file path' => drupal_get_path('module', 'system'),
  );
  $items['admin/boinc/environment'] = array(
    'title' => 'Environment: General',
    'description' => 'Set paths to BOINC functions and any other necessary
      variables that establish a BOINC environment.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('boincuser_admin_environment'),
    'access arguments' => array('administer site configuration'),
    'type' => MENU_NORMAL_ITEM,
    'file' => 'boincuser.admin.inc'
  );
  $items['admin/boinc/scheduler'] = array(
    'title' => 'Environment: Scheduling server URLs',
    'description' => 'Set BOINC scheduler options.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('boincuser_admin_scheduler'),
    'access arguments' => array('administer site configuration'),
    'type' => MENU_NORMAL_ITEM,
    'file' => 'boincuser.admin.inc'
  );
  $items['admin/boinc/weboptions'] = array(
    'title' => 'Environment: Website Options',
    'description' => 'Set options configuring this Drupal-BOINC Web site.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('boincuser_admin_weboptions'),
    'access arguments' => array('administer site configuration'),
    'type' => MENU_NORMAL_ITEM,
    'file' => 'boincuser.admin.inc'
  );

  $items['create_account.php'] = array(
    'title' => 'Create Account RPC',
    'description' => 'RPC for creating user accounts.',
    'page callback' => 'boincuser_create_account',
    'access callback' => TRUE,
    'type' => MENU_CALLBACK
  );
  $items['account_finish.php'] = array(
    'title' => 'Welcome to ' . variable_get('site_name', 'Drupal-BOINC'),
    'description' => 'RPC for after a user has created an account.',
    'page callback' => 'boincuser_account_finish',
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
  );
  $items['boincuser/autocomplete'] = array(
    'page callback' => '_boincuser_user_name_autocomplete',
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
  );
  $items['user/%user/recoveremail/%'] = array(
    'title' => t('Recover previous email'),
    'description' => t('Form to revert email to previous address.'),
    'page callback' => 'drupal_get_form',
    'page arguments' => array('boincuser_revertemail', 3),
    'access callback' => 'user_is_logged_in',
    'type' => MENU_CALLBACK,
  );
  $items['recover_email.php'] = array(
    'title' => t('Recover previous email'),
    'description' => t('redirect'),
    'page callback' => '_boincuser_redirect_recover_email',
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
  );
  return $items;
}

/**
 * Implementation of hook_init()
 */
function boincuser_init() {
  global $user;
  // Skip this check for charts, which are loaded separately
  // (may get duplicate or unexpected messages otherwise)
  if (substr($_GET['q'], 0, 7) == 'charts/') {
    return;
  }

  // If admin user, do some basic site functionality checks
  if (user_access('administer site configuration')) {
    // Ensure we have a configured BOINC environment
    boinc_get_path();
    boinc_get_scheduler_tags();
  }

  // Check credits for the verified contributor role
  boincuser_check_credit_requirements();

  if (module_exists('boincteam')) {
    // Display any persistent team messages
    boincteam_show_messages();
  }

  // Check if user has agreed to the terms of use. If not, send the
  // user to the terms-of-use form. This is only makes sense if the
  // termsofuse is enabled, by having text in the termsofuse variable.
  $existinguser_tou = variable_get('boinc_weboptions_existinguser_tou', FALSE);
  $termsofuse = variable_get('boinc_weboptions_termsofuse', '');
  if ( (!empty($termsofuse)) and ($user->uid) ) {
    if ( !boincuser_check_termsofuse($user) and ($existinguser_tou) ) {

      // Admins are exempt, otherwise the admin may not be able to
      // access the site!
      $administrator_role = array_search('administrator', user_roles(true));
      if (!isset($user->roles[$administrator_role])) {
        $path = drupal_get_path_alias($_GET['q']);

        // Any paths that should NOT be redirected go here.
        // The site will not function correctly if these are not exempt!
        $paths0 = array(
          'user/termsofuse',
          'logout',
          'account/info/edit',
          'user/' . $user->uid . '/edit',
          'user/' . $user->uid . '/recoveremail/*',
          'recover_email.php',
        );
        if (module_exists('boincuser_delete')) {
          $paths0[] = 'user/' . $user->uid . '/delete';
          $paths0[] = 'user/' . $user->uid . '/deleteconfirm/*';
          $paths0[] = 'user/' . $user->uid . '/odeleteconfirm/*';
        }

        // Paths added by the admin
        $paths1 = preg_split('/\r\n|\r|\n/', variable_get('boinc_weboptions_pathstoignore', "moderation\ncontent/moderation\nprivacy"));
        $paths2 = array();
        if (is_array($paths1)) {
          $paths2 = array_map('strtolower', $paths1);
        }

        // paths to ignore
        $paths_to_ignore = array_unique( array_merge($paths0, $paths2) );

        if (!_boincuser_ignore_paths($path, $paths_to_ignore)) {
          drupal_goto('user/termsofuse');
        }
      }
    }
  }
}

/**
 * Implementation of hook_user(); add custom actions to standard
 * Drupal user operations
 */
function boincuser_user($op, &$edit, &$account, $category = NULL) {
  require_boinc('boinc_db');
  require_boinc('user');
  require_boinc('xml');

  require_boinc('password_compat/password');
  // Handle BOINC integration for users with UID > 1 (skip anonymous and admin)
  if (isset($account->uid) && ($account->uid > 1)) {
    switch($op) {
    case 'load':
      // User loading; insert BOINC data into the user object
      $drupal_user = db_fetch_object(db_query("
        SELECT boinc_id, penalty_expiration
        FROM {boincuser} WHERE uid = %d",
        $account->uid
      ));
      $account->boincuser_id = $drupal_user->boinc_id;
      $account->boincuser_penalty_expiration = $drupal_user->penalty_expiration;
      db_set_active('boinc_rw');
      $boinc_user = db_fetch_object(db_query("
        SELECT
          name,
          authenticator,
          passwd_hash,
          total_credit,
          expavg_credit,
          expavg_time,
          cross_project_id,
          teamid,
          venue,
          previous_email_addr,
          email_addr_change_time
        FROM {user}
        WHERE id = %d",
        $account->boincuser_id
      ));
      $account->boincuser_name = $boinc_user->name;
      $account->boincuser_account_key = $boinc_user->authenticator;
      $account->boincuser_weak_auth = md5($boinc_user->authenticator . $boinc_user->passwd_hash);
      $account->boincuser_total_credit = round($boinc_user->total_credit);
      $account->boincuser_expavg_credit = round($boinc_user->expavg_credit);
      $account->boincuser_expavg_time = round($boinc_user->expavg_time);
      $account->boincuser_cpid = md5($boinc_user->cross_project_id . $account->mail);
      $account->boincuser_default_pref_set = $boinc_user->venue;
      $account->boincteam_id = $boinc_user->teamid;
      $account->boincuser_previous_email_addr = $boinc_user->previous_email_addr;
      $account->boincuser_email_addr_change_time = $boinc_user->email_addr_change_time;
      db_set_active('default');
      // Set Drupal team ID
      $account->team = NULL;
      if ($account->boincteam_id) {
        $account->team = db_result(db_query("
          SELECT nid FROM {boincteam} WHERE team_id = %d",
          $account->boincteam_id
        ));
      }
      // Set post count
      $account->post_count = db_result(db_query("
        SELECT COUNT(*) +
        (
          SELECT COUNT(*) FROM {node}
          WHERE uid = '%d'
          AND type IN('page','story','forum','news')
          AND status = 1
        ) AS total_posts
        FROM {comments}
        INNER JOIN node ON comments.nid = node.nid
        WHERE comments.uid = '%d'
        AND node.status = 1",
        $account->uid, $account->uid
      ));
      break;

    case 'view':
      // SAMPLE: Add BOINC data to the user profile
      /*$account->content['summary']['boinc_id'] = array(
        '#type' => 'user_profile_item',
        '#title' => bts('BIONC ID'),
        '#value' => $account->boincuser_id,
        '#attributes' => array('class' => 'boinc-data'),
        '#weight' => 10
      );
      $account->content['summary']['total_credit'] = array(
        '#type' => 'user_profile_item',
        '#title' => bts('Total credit', array(), NULL, 'boinc:user-or-team-total-credits'),
        '#value' => $account->boincuser_total_credit,
        '#attributes' => array('class' => 'boinc-data'),
        '#weight' => 10
      );*/
      break;

    case 'validate':
      if (isset($edit['validation_source'])) {
        switch ($edit['validation_source']) {
        case 'user_account':
          // Validate data before updating user account info
          boincuser_account_validate($edit, $account);
          break;

        default:

        }
        // We don't want to save validation source, so remove it
        $edit['validation_source'] = null;
      }
      break;

    case 'insert':
      // New user being added to the system
      $imported = $_SESSION['importedUser'];
      unset($_SESSION['importedUser']);

      watchdog(
        'boincuser',
        'Creating user account for %email_addr',
        array('%email_addr' => $edit['mail']),
        WATCHDOG_NOTICE
      );

      // The create_acount RPC will call this block of code when
      // user_save() is used. If user is registering using the Web
      // registration form, create a BOINC user and relationships.
      // Create a BOINC account unless importing from BOINC.
      if (!$imported) {

        // set email address lower-case
        $lower_email_addr = strtolower($edit['mail']);

        if ($edit['boincuser_name']) {
          $myname = $edit['boincuser_name'];
        }
        else if ($edit['name']) {
          $myname = $edit['name'];
        }
        else {
          $myname = 'noname';
        }

        $user_params = array(
          'email_addr' => $lower_email_addr,
          'name' => $myname,
        );

        // If the 'pass' variable is already a hash, then don't hash it again.
        if ($edit['boinchash_flag']) {
          $user_params['passwd_hash'] = $edit['pass'];
        }
        else {
          // The passwd_hash here is only the md5() hash. This is
          // because BOINC make_user(), called later, will run
          // password_hash() on this md5 hash.
          $user_params['passwd_hash'] = md5($edit['pass'].$lower_email_addr);
        }

        $boinc_user = boincuser_register_make_user($user_params);
        if (!$boinc_user) {
          // Account exists with this email addr
          form_set_error('email', bts('Error creating BOINC account.', array(), NULL, 'boinc:add-new-user'));
          return;
        }

        // Add user to community role by default (not banned)
        $unrestricted_role = array_search('community member', user_roles(true));
        $edit['roles'] = array(
          $unrestricted_role => ''
        );

        // Disable show_hosts flag, set to TRUE by default
        db_set_active('boinc_rw');
        db_query("UPDATE {user} SET show_hosts=0 WHERE id='%d'", $boinc_user->id);
        db_set_active('default');

        // Cross reference Drupal account with BOINC
        $reference = db_query("INSERT INTO {boincuser} SET uid='%d', boinc_id='%d'", $account->uid, $boinc_user->id);
        if (!$reference) {
          drupal_set_message(t('Error connecting BOINC account.'), 'error');
          return;
        }

        // if terms of use exist, the user must agree.
        $termsofuse = variable_get('boinc_weboptions_termsofuse', '');
        if (!empty($termsofuse)) {
          $reference2 = boincuser_consentto_termsofuse($account);
        }

        // Don't save custom fields to the Drupal user object
        $edit['boincuser_name'] = null;
        $edit['boinchash_flag'] = null;
        // Set email address to lower case in Drupal users table
        if ($account) {
          user_save($account, array('mail' => $lower_email_addr));
        }
      }
      break;

    case 'update':
      if (isset($edit['update_source'])) {
        require_boinc('boinc_db');
        $boinc_user = BoincUser::lookup_id($account->boincuser_id);
        switch ($edit['update_source']) {
        case 'user_account':
          // Ensure that BOINC data is altered

          $changing_email = ($edit['mail'] AND $edit['mail'] != $boinc_user->email_addr) ? true : false;
          $changing_pass = ($edit['pass']) ? true : false;
          if ($changing_email OR $changing_pass) {
            // set email address to lower-case
            $lower_email_addr = strtolower($edit['mail']);

            // Set password hash appropriately
            $passwd = ($edit['pass']) ? $edit['pass'] : $edit['current_pass'];
            $passwd_hash = password_hash( md5($passwd.$lower_email_addr), PASSWORD_DEFAULT );
            // Algorithm for changing email and/or password
            if ($changing_email) {
              // locally store current email to set as previous email
              $prev_email = $account->mail;
              $mytime = (user_access('administer users')) ? $boinc_user->email_addr_change_time : time();
              $querypart = "email_addr='{$lower_email_addr}', passwd_hash='{$passwd_hash}', previous_email_addr = '{$prev_email}', email_addr_change_time = $mytime";
            }
            else {
              $querypart = "email_addr='{$lower_email_addr}', passwd_hash='{$passwd_hash}'";
            }

            // Update user account information
            $result = $boinc_user->update($querypart);

            if ($changing_email) {
              // reload account
              $account = user_load($account->uid);
              _boincuser_send_emailchange($account, $lower_email_addr, $prev_email, user_access('administer users'));
            }

            // Change email to edit to lower-case version, this sets
            // email in Drupal database to the lower-case email
            // address.
            $edit['mail'] = strtolower($lower_email_addr);
          }

          // Change boinc username
          if ($edit['boincuser_name'] and ($edit['boincuser_name'] != $boinc_user->name)) {
            $boincuser_name = $edit['boincuser_name'];
            $result = $boinc_user->update(
                "name='{$boincuser_name}'"
            );
          }

          break;
        case 'user_profile':
          if ($edit['boincuser_name'] != $boinc_user->name) {
            $boincuser_name = $edit['boincuser_name'];
            $result = $boinc_user->update(
                "name='{$boincuser_name}'"
            );
          }
          break;
        default:
        }
        // We don't want to save update source or duplicate custom fields, so
        // remove them before continuing to core Drupal routines
        $edit['update_source'] = null;
        $edit['boincuser_name'] = null;
        $edit['current_pass'] = null;
      }
      break;

    case 'login':
      // Function is forward compatible to Drupal 7
      boincuser_user_login($edit, $account);
      break;

    case 'delete':
      // Function is forward compatible to Drupal 7
      boincuser_user_delete($account);
      break;

    default:

    }
  }
}

/**
 * Implementation of hook_user_login(); When user-logins, checks if
 * they have agreed to the terms of use.
 *(forward compatible to Drupal 7).
 */
function boincuser_user_login(&$edit, $account) {
  $existinguser_tou = variable_get('boinc_weboptions_existinguser_tou', FALSE);
  $termsofuse = variable_get('boinc_weboptions_termsofuse', '');

  // Use the same code as boincuser_form_alter(), for case
  // 'user_profile_form', if the refering page is the user password
  // reset form, then do not check for terms of use.
  $reset_pass = (strpos($_SERVER['HTTP_REFERER'], "/user/reset/$account->uid") === FALSE) ? 0 : 1;
  if ($reset_pass) {
    return;
  }

  // Check if user has agreed to terms of use.
  if ( (!empty($termsofuse)) and ($account->uid) and
       (!boincuser_check_termsofuse($account)) and ($existinguser_tou) ) {

    // Admins are exempted.
    $administrator_role = array_search('administrator', user_roles(true));
    if (!isset($account->roles[$administrator_role])) {

      // Find and save the current destination and use as an parameter
      // to send the user back to here he/she came from.
      $np = ltrim('user/termsofuse', '/');
      $path_for_destination = rawurlencode($np);

      $query_for_destination = '';
      $prevdest = $_REQUEST['destination'];
      if ($prevdest) {
        $query_for_destination = '?destination=' . $prevdest;
      }
      $_REQUEST['destination'] = $path_for_destination . $query_for_destination;

    }
  }
}

/**
 * Implementation of hook_user_delete(); deletes boinc user data
 * (forward compatible to Drupal 7).
 */
function boincuser_user_delete($account) {

  $boincid = $account->boincuser_id;
  // bug in comment module, remove user name from comments. Find all
  // comments with uid=0 and clear the field 'name'.
  $qrc1 = db_query("UPDATE {comments} SET comments.name='' WHERE comments.uid=0");

  // Delete entry in drupal boincuser table.
  $qrc2 = db_query("DELETE FROM {boincuser} WHERE uid=%d", $account->uid);
  if (!$qrc2) {
    watchdog('user', 'Error deleting user account, boincuser table UID: %uid.', array('%uid' => $account->uid), WATCHDOG_ERROR);
  }
}


/**
 * Implementation of hook_nodeapi(); add custom actions to node operations
 * Obsolete in Drupal 7...
 */
function boincuser_nodeapi(&$node, $op, $a3 = null, $a4 = null) {
  // In Drupal 7, these operation cases will all exist as their own hooks,
  // so let's approximate that here so that this function can simply be removed
  // upon migration to 7
  switch($op) {
  case 'update':
    boincuser_node_update($node);
  }
}

/**
 * Implementation of hook_node_update(); add custom actions when a node
 * is updated (forward compatible to Drupal 7)
 */
function boincuser_node_update($node) {
  switch($node->type) {
  case 'profile':
    // Update the BOINC database directly
    $account = user_load($node->uid);
    // Save user account data
    $country = $node->field_country[0]['value'];
    $postal_code = $node->field_zip[0]['value'];
    $url = $node->field_url[0]['value'];
    db_set_active('boinc_rw');
    $account_updated = db_query("
      UPDATE user
      SET country = '%s', postal_code = '%s', url = '%s', has_profile = 1
      WHERE id = %d",
      $country, $postal_code, $url, $account->boincuser_id
    );
    db_set_active('default');
    if (!$account_updated) {
      drupal_set_message(t('Error saving BOINC account info.'), 'error');
    }
    // Save profile data
    $response1 = $node->field_background[0]['value'];
    $response2 = $node->field_opinions[0]['value'];
    db_set_active('boinc_rw');
    $profile_updated = db_query("
      INSERT INTO profile
      SET userid = %d, response1 = '%s', response2 = '%s'
      ON DUPLICATE KEY UPDATE
        response1 = '%s', response2 = '%s'",
      $account->boincuser_id, $response1, $response2,
      $response1, $response2
    );
    db_set_active('default');
    if (!$profile_updated) {
      drupal_set_message(t('Error saving BOINC profile.'), 'error');
    }
    break;

  default:

  }
}

/**
* Implementation of hook_views_api()
*/
function boincuser_views_api() {
  return array(
    'api' => 2.0,
    'path' => drupal_get_path('module', 'boincuser')
  );
}

/**
* Implementation of hook_form_alter()
*/
function boincuser_form_alter(&$form, $form_state, $form_id) {
  require_boinc('token');

  global $user;
  switch ($form_id) {
  case 'flag_confirm':
    // The URL seems to be the only way to put any kind of context to this
    // request!
    $action = arg(2);
    $flag_type = arg(3);
    $cancel_dest = isset($_REQUEST['destination']) ? $_REQUEST['destination'] : '';

    // Wrap action buttons for styling consistency
    $form['form control tabs prefix'] = array(
      '#value' => '<ul class="form-control tab-list">',
      '#weight' => 1001,
    );

    switch ($flag_type) {
    case 'friend':
      $friend_id = $form['content_id']['#value'];
      $flag = flag_get_flag('friend');
      $friend_status = flag_friend_determine_friend_status($flag, $friend_id, $user->uid);

      // General friend form overrides
      $form['flag_friend_submit']['#prefix'] = '<li class="first tab">';
      $form['flag_friend_submit']['#value'] = bts('Send request', array(), NULL, 'boinc:friends-page');
      $form['flag_friend_submit']['#type'] = 'submit';
      $form['flag_friend_submit']['#suffix'] = '</li>';
      $form['flag_friend_submit']['#weight'] = 1002;

      switch ($friend_status) {
      case FLAG_FRIEND_BOTH:
      case FLAG_FRIEND_FLAGGED:
        unset($form['actions']);
        $form['flag_friend_submit']['#value'] = bts('Remove friend', array(), NULL, 'boinc:friends-remove');
        // Add in our hack handler to fix the flag_friend module
        $final_handler = array_pop($form['#submit']);
        $form['#submit'][] = 'boincuser_fix_unfriend_form_submit';
        $form['#submit'][] = $final_handler;
        break;
      case FLAG_FRIEND_PENDING:
        unset($form['actions']);
        $form['flag_friend_submit']['#value'] = bts('Remove request', array(), NULL, 'boinc:friends-page');
        break;
      case FLAG_FRIEND_APPROVAL:
        if ($action == 'flag') {
          $form['flag_friend_submit']['#value'] = bts('Approve request', array(), NULL, 'boinc:friends-page');
        }
        elseif ($action == 'unflag') {
          unset($form['actions']);
          $form['flag_friend_submit']['#value'] = bts('Deny request', array(), NULL, 'boinc:friends-page');
        }
        break;
      case FLAG_FRIEND_UNFLAGGED:
      default:
        $user_links[] = array(
          'title' => bts('Add as friend', array(), NULL, 'boinc:friends-add'),
          'href' => "flag/confirm/flag/friend/{$account->uid}"
        );
      }
      break;

    default:
    }

    $form['cancel'] = array(
      '#value' => '<li class="tab">' . l(bts('Cancel', array(), NULL, 'boinc:form-cancel'), $cancel_dest) . '</li>',
      '#weight' => 1004,
    );
    $form['form control tabs suffix'] = array(
      '#value' => '</ul>',
      '#weight' => 1010,
    );

    break;

  // General node edit form
  case 'news_node_form':
    $form['separator_bottom'] = array(
      '#value' => '<div class="separator buttons"></div>',
      '#weight' => 999,
    );

    // Wrap action buttons for styling consistency
    $form['buttons']['form control tabs prefix'] = array(
      '#value' => '<ul class="form-control tab-list">',
      '#weight' => 1001,
    );
    $form['buttons']['submit']['#prefix'] = '<li class="first tab">';
    $form['buttons']['submit']['#value'] = bts('Save changes', array(), NULL, 'boinc:form-save');
    $form['buttons']['submit']['#suffix'] = '</li>';
    $form['buttons']['submit']['#weight'] = 1002;
    $form['buttons']['preview']['#prefix'] = '<li class="tab">';
    $form['buttons']['preview']['#suffix'] = '</li>';
    $form['buttons']['preview']['#weight'] = 1003;
    $form['buttons']['preview_changes']['#prefix'] = '<li class="tab">';
    $form['buttons']['preview_changes']['#suffix'] = '</li>';
    $form['buttons']['preview_changes']['#weight'] = 1004;
    $form['buttons']['cancel'] = array(
      '#value' => '<li class="tab">' . l(bts('Cancel', array(), NULL, 'boinc:form-cancel'), "node/{$form['nid']['#value']}") . '</li>',
      '#weight' => 1005,
    );
    $form['buttons']['delete']['#prefix'] = '<li class="tab">';
    $form['buttons']['delete']['#suffix'] = '</li>';
    $form['buttons']['delete']['#weight'] = 1006;
    $form['buttons']['form control tabs suffix'] = array(
      '#value' => '</ul>',
      '#weight' => 1010,
    );

    // Preview is ugly, unset until it works
    unset($form['buttons']['preview']);

    break;

  case 'node_delete_confirm':
    $form['separator_bottom'] = array(
      '#value' => '<div class="separator buttons"></div>',
      '#weight' => 999,
    );

    // Wrap action buttons for styling consistency
    $form['actions']['form control tabs prefix'] = array(
      '#value' => '<ul class="form-control tab-list">',
      '#weight' => 1001,
    );
    $form['actions']['submit']['#prefix'] = '<li class="first tab">';
    $form['actions']['submit']['#value'] = bts('Delete', array(), NULL, 'boinc:form-delete');
    $form['actions']['submit']['#suffix'] = '</li>';
    $form['actions']['submit']['#weight'] = 1002;
    $form['actions']['cancel'] = array(
        '#value' => '<li class="tab">' . l(bts('Cancel', array(), NULL, 'boinc:form-cancel'), "node/{$form['nid']['#value']}") . '</li>',
      '#weight' => 1005,
    );
    $form['actions']['form control tabs suffix'] = array(
      '#value' => '</ul>',
      '#weight' => 1010,
    );
    $form['#redirect'] = 'account/profile';
    break;

  case 'privatemsg_new':

    $form['privatemsg']['body']['#title'] = '';

    // Wrap action buttons for styling consistency
    $form['privatemsg']['form control tabs prefix'] = array(
      '#value' => '<ul class="form-control tab-list">',
      '#weight' => 1001,
    );
    $form['privatemsg']['submit']['#prefix'] = '<li class="first tab">';
    $form['privatemsg']['submit']['#value'] = bts('Send message', array(), NULL, 'boinc:private-message');
    $form['privatemsg']['submit']['#suffix'] = '</li>';
    $form['privatemsg']['submit']['#weight'] = 1002;
    $form['privatemsg']['preview']['#prefix'] = '<li class="tab">';
    $form['privatemsg']['preview']['#suffix'] = '</li>';
    $form['privatemsg']['preview']['#weight'] = 1003;
    $form['privatemsg']['cancel'] = array(
      '#value' => '<li class="tab">' . l(bts('Cancel', array(), NULL, 'boinc:form-cancel'), $_GET['q']) . '</li>',
      '#weight' => 1004,
    );
    $form['privatemsg']['form control tabs suffix'] = array(
      '#value' => '</ul>',
      '#weight' => 1010,
    );

    unset($form['privatemsg']['recipient_display']);

    break;

  // Login form
  case 'user_login':
  case 'user_login_block':
      drupal_set_title(bts('Login', array(), NULL, 'boinc:menu-link'));
    // Replace name with email in login form
    unset($form['name']);
    array_unshift($form, array(
      'email' => array(
        '#type' => 'textfield',
        '#title' => bts('Email address', array(), NULL, 'boinc:email-address-to-login'),
        '#size' => ($form_id == 'user_login_block') ? 15 : 60,
        '#maxlength' => EMAIL_MAX_LENGTH,
        '#required' => TRUE,
        '#attributes' => array('tabindex' => '1'),
        '#description' => bts('Enter your @s email address.', array('@s' => variable_get('site_name', 'Drupal-BOINC')), NULL, 'boinc:standard-login-page')
      ),
      'validation_source' => array(
        '#type' => 'hidden',
        '#value' => 'user_login'
      )
    ));
    $form['#redirect'] = 'home';

    // Wrap action buttons for styling consistency
    $form['buttons']['form control tabs prefix'] = array(
      '#value' => '<ul class="form-control tab-list">',
      '#weight' => 1001,
    );
    $form['buttons']['submit'] = $form['submit'];
    $form['buttons']['submit']['#prefix'] = '<li class="first tab">';
    $form['buttons']['submit']['#value'] = bts('Log in', array(), NULL, 'boinc:standard-login-page');
    $form['buttons']['submit']['#suffix'] = '</li>';
    $form['buttons']['submit']['#weight'] = 1002;
    $form['buttons']['form control tabs suffix'] = array(
      '#value' => '</ul>',
      '#weight' => 1010,
    );
    unset($form['submit']);

    // Set the tab indices to follow a logical procession...
    isset($form['pass']['#attributes']) ? array_push($form['pass']['#attributes'], array('tabindex' => '2')) : $form['pass']['#attributes'] = array('tabindex' => '2');
    isset($form['buttons']['submit']['#attributes']) ? array_push($form['buttons']['submit']['#attributes'], array('tabindex' => '3')) : $form['buttons']['submit']['#attributes'] = array('tabindex' => '3');
    // If the user login form is being submitted, use BOINC validation handler.
    if (isset($form_state['post']['email']) and isset($form_state['post']['pass'])) {
      // Find the local validation function's entry so we can replace it.
      $array_key = array_search('user_login_authenticate_validate', $form['#validate']);
      if ($array_key === FALSE) {
        // Could not find it. Some other module must have run form_alter().
        // We will simply add our validation just before the final validator.
        $final_validator = array_pop($form['#validate']);
        $form['#validate'][] = 'boincuser_login_validate';
        $form['#validate'][] = $final_validator;
      } else {
        // Replace the local validation function with BOINC validation
        $form['#validate'][$array_key] = 'boincuser_login_validate';
      }
    }
    break;

  // User credentials form
  case 'user_profile_form':

    // Use the displaly name as the title, not the username
    $account = user_load($form['#uid']);
    drupal_set_title($account->boincuser_name);

    // Message for admins
    if (user_access('administer users')) {
      drupal_set_message(
        bts('WARNING: You are editing the information for user. Please note: you may change a user\'s password by itself. But to change the user\'s email address you must change both the email address and the password simultaneously.')
      , 'warning');
    }

    // Set special message if user has not agreed to TOU
    $existinguser_tou = variable_get('boinc_weboptions_existinguser_tou', FALSE);
    $termsofuse = variable_get('boinc_weboptions_termsofuse', '');
    if ( (!boincuser_check_termsofuse($account)) and ($existinguser_tou) and (!empty($termsofuse)) and (!user_access('administer users')) ) {
      drupal_set_message(
        bts('INFO: You have not agreed to the terms of use for @project. You may use this form to change your email address and/or password. Please note: you may not delete your account within seven (7) days of changing your email address.',
        array(
          '@project' => variable_get('site_name','Drupal-BOINC'),
        ), NULL, 'boinc:account-credentials-change')
      , 'info');
    }

    // A bit hackish... but don't require the user to enter his password if
    // coming from the password reset function
    $reset_pass = (strpos($_SERVER['HTTP_REFERER'], "/user/reset/{$form['#uid']}") === FALSE) ? 0 : 1;
    if ($reset_pass) {
      $_SESSION['reset_pass'] = 1;
    }

    // Adjust form elements already present
    $form['account']['name']['#title'] = bts('Drupal Username', array(), NULL, 'boinc:drupal-username');
    $form['account']['name']['#size'] = 40;
    $form['account']['name']['#attributes']['autocomplete'] = 'off';
    $form['account']['name']['#description'] .= '<p>This is the drupal (internal) username. The title above shows the BOINC username, which is the display name used publicly that the user can change in Community Preferences.';
    $form['account']['mail']['#size'] = 40;
    $form['account']['pass']['#size'] = 17;

    if (user_access('administer users')) {
      // Add BOINC username (aka displayname)
      $form['account']['boincuser_name'] = array(
        '#type' => 'textfield',
        '#title' => bts('BOINC Username', array(), NULL, 'boinc:user-or-team-name'),
        '#default_value' => $account->boincuser_name,
        '#maxlength' => USERNAME_MAX_LENGTH,
        '#required' => TRUE,
        '#description' => bts('This is the BOINC (external) username. This is the same setting as found in Account -> Preferences -> Community.', array(), NULL, 'boinc:username-change'),
        '#size' => 40,
      );
    }

    // If email address was changed less than 7 days (7 * 86400 s)
    // ago, it cannot be changed again.
    $duration = TOKEN_DURATION_ONE_WEEK;
    if (($account->boincuser_email_addr_change_time + $duration) > time() and (!user_access('administer users'))) {
      $form['account']['mail']['#required'] = FALSE;
      $form['account']['mailhelp'] = array(
        '#value' => bts("You email address was changed within the past seven (7) days. Please look for an email to !prev_email if you need to revert this change. You may change your email address on !time.",
          array(
            '!prev_email' => $account->boincuser_previous_email_addr,
            '!time' => date('F j, Y \a\t G:i T', $account->boincuser_email_addr_change_time + $duration),
          ), NULL, 'boinc:account-credentials-change'),
      );
    }

    if (!$reset_pass AND ($user->uid == $account->uid OR !user_access('administer users'))) {
      // Add a password authenticator, required to change email or pw
      $form['account']['current_pass'] = array(
        '#type' => 'password',
        '#title' => bts('Enter your password to save changes', array(), NULL, 'boinc:account-credentials-change'),
        '#description' => bts('Enter your current password if changing your email
          address or password.', array(), NULL, 'boinc:account-credentials-change'),
        '#size' => 17,
        '#attributes' => array(
          'autocomplete' => 'off',
        ),
      );
    }

    // Add account keys, CPID, etc
    $form['account']['boincuser_id'] = array(
      '#value' => '
        <div class="form-item">
          <label>' . bts('BOINC user ID', array(), NULL, 'boinc:account-credentials-change') . '</label>
          <span>' . $account->boincuser_id . '</span>
        </div>',
    );
    $form['account']['user_id'] = array(
      '#value' => '
        <div class="form-item">
          <label>' . bts('Drupal user ID', array(), NULL, 'boinc:account-credentials-change') . '</label>
          <span>' . $account->uid . '</span>
        </div>',
    );
    $form['account']['account_key'] = array(
      '#value' => '
        <div class="form-item">
          <label>' . bts('Account key', array(), NULL, 'boinc:account-credentials-change') . '</label>
          <span>' . $account->boincuser_account_key . '</span>
        </div>',
    );
    $form['account']['weak_account_key'] = array(
      '#value' => '
        <div class="form-item">
          <label>' . bts('Weak account key', array(), NULL, 'boinc:account-credentials-change') . '</label>
          <span>' . "{$account->boincuser_id}_{$account->boincuser_weak_auth}" . '</span>
        </div>',
    );
    $form['account']['cpid'] = array(
      '#value' => '
        <div class="form-item">
          <label>' . bts('Cross-project ID', array(), NULL, 'boinc:account-credentials-change') . '</label>
          <span>' . $account->boincuser_cpid . '</span>
        </div>',
    );

    $form['account']['separator_bottom'] = array(
      '#value' => '<div class="separator buttons"></div>'
    );

    // Wrap action buttons for styling consistency
    $form['form control tabs prefix'] = array(
      '#value' => '<ul class="form-control tab-list">',
      '#weight' => 1001,
    );
    $form['submit']['#prefix'] = '<li class="first tab">';
    $form['submit']['#value'] = bts('Save changes', array(), NULL, 'boinc:form-save');
    $form['submit']['#suffix'] = '</li>';
    $form['submit']['#weight'] = 1002;
    $form['cancel'] = array(
      '#value' => '<li class="tab">' . l(bts('Cancel', array(), NULL, 'boinc:form-cancel'), $_GET['q']) . '</li>',
      '#weight' => 1003,
    );
    if (isset($form['delete']) AND is_array($form['delete'])) {
      $form['delete']['#prefix'] = '<li class="first alt tab">';
      $form['delete']['#suffix'] = '</li>';
      $form['delete']['#weight'] = 1004;
    }
    $form['form control tabs suffix'] = array(
      '#value' => '</ul>',
      '#weight' => 1010,
    );

    // Rearrange form elements
    $form['account']['name']['#weight'] = -2;
    $form['account']['boincuser_name']['#weight'] = -1;
    $form['account']['mailhelp']['#weight'] = 4;
    $form['account']['mail']['#weight'] = 5;
    $form['account']['pass']['#weight'] = 15;
    $form['account']['boincuser_id']['#weight'] = 45;
    $form['account']['user_id']['#weight'] = 50;
    $form['account']['account_key']['#weight'] = 55;
    $form['account']['weak_account_key']['#weight'] = 60;
    $form['account']['cpid']['#weight'] = 65;
    $form['account']['separator_bottom']['#weight'] = 1000;
    $form['account']['current_pass']['#weight'] = 1001;

    // Remove redundant / unnecessary form elements
    unset($form['theme_select']);
    if (!module_exists('boincuser_delete')) {
      unset($form['delete']);
    }

    // These are on the Community preferences form (boincwork module)
    unset($form['enable_pm_mail']);
    unset($form['friend_notification']);
    unset($form['locale']);
    unset($form['signature_settings']);
    unset($form['timezone']);
    unset($form['wysiwyg']);

    // Built-in picture support is not used
    unset($form['picture']);

    // Internal fields to indicate where these user changes are taking place
    array_unshift($form, array(
      'validation_source' => array(
        '#type' => 'hidden',
        '#value' => 'user_account'
      ),
      'update_source' => array(
        '#type' => 'hidden',
        '#value' => 'user_account'
      )
    ));
    break;

  case 'profile_node_form':

    // Use the display name as the title, not the username
    $account = user_load($form['uid']['#value']);
    drupal_set_title($account->boincuser_name);

    // Set form['title'], but hide it
    $form['title']['#default_value'] = $account->boincuser_name;
    $form['title']['#access'] = FALSE;

    $form['separator_bottom'] = array(
      '#value' => '<div class="separator buttons"></div>',
      '#weight' => 999,
    );

    if (module_exists('captcha')) {
      // Add an optional captcha
      $form['profile_captcha'] = array(
        '#type' => 'captcha',
        '#weight' => 1000,
      );
    }

    // Wrap action buttons for styling consistency
    $form['buttons']['form control tabs prefix'] = array(
      '#value' => '<ul class="form-control tab-list">',
      '#weight' => 1001,
    );
    $form['buttons']['submit']['#prefix'] = '<li class="first tab">';
    $form['buttons']['submit']['#value'] = bts('Save changes', array(), NULL, 'boinc:form-save');
    $form['buttons']['submit']['#suffix'] = '</li>';
    $form['buttons']['submit']['#weight'] = 1002;
    $form['buttons']['preview']['#prefix'] = '<li class="tab">';
    $form['buttons']['preview']['#suffix'] = '</li>';
    $form['buttons']['preview']['#weight'] = 1003;
    $form['buttons']['preview_changes']['#prefix'] = '<li class="tab">';
    $form['buttons']['preview_changes']['#suffix'] = '</li>';
    $form['buttons']['preview_changes']['#weight'] = 1004;
    $form['buttons']['cancel'] = array(
      '#value' => '<li class="tab">' . l(bts('Cancel', array(), NULL, 'boinc:form-cancel'), $_GET['q']) . '</li>',
      '#weight' => 1005,
    );
    $form['buttons']['delete']['#prefix'] = '<li class="tab">';
    $form['buttons']['delete']['#suffix'] = '</li>';
    $form['buttons']['delete']['#value'] = bts('Clear User Profile', array(), NULL, 'boinc:form-save');
    $form['buttons']['delete']['#weight'] = 1006;
    $form['buttons']['delete']['#submit'] = array('_boincuser_node_profile_delete_submit');
    $form['buttons']['form control tabs suffix'] = array(
      '#value' => '</ul>',
      '#weight' => 1010,
    );

    $form['#after_build'][] = 'boincuser_profile_node_form_after_build';

    // Preview is ugly, unset until it works
    unset($form['buttons']['preview']);

    unset($form['body_field']);
    $form['#redirect'] = 'account/profile';

    // Internal fields to indicate where these user changes are taking place
    array_unshift($form, array(
      'validation_source' => array(
        '#type' => 'hidden',
        '#value' => 'user_profile'
      ),
      'update_source' => array(
        '#type' => 'hidden',
        '#value' => 'user_profile'
      )
    ));
    break;

  // Registration form
  case 'user_register':
    array_unshift($form, array(
      'boincuser_name' => array(
        '#type' => 'textfield',
        '#title' => bts('Name', array(), NULL, 'boinc:user-or-team-name'),
        '#default_value' => $edit['boincuser_name'],
        '#maxlength' => USERNAME_MAX_LENGTH,
        '#description' => bts('Spaces are allowed; punctuation is not allowed except for periods, hyphens, and underscores.', array(), NULL, 'boinc:user-register'),
        '#required' => TRUE
      ),
    ));
    // Set name temporarily to dummy value to beat validation
    $form['name'] = array(
      '#type' => 'hidden',
      '#value' => rand() . '.' . time()
    );

    // Add JS for submit button disabling
    drupal_add_js(drupal_get_path('module', 'boincuser') . '/boincuser.js');

    // Terms of use section
    $termsofuse = variable_get('boinc_weboptions_termsofuse', '');
    if (!empty($termsofuse)) {

      $form['termsofuse'] = array(
        '#type'   => 'fieldset',
        '#prefix' => '<div id="termsofuse-wrapper">', // This is our wrapper div.
        '#suffix' => '</div>',
        '#tree'   => TRUE,
        '#weight' => -15,
      );

      $form['termsofuse']['title1'] = array(
        '#weight' => -12,
        '#value' => '<h2>' . bts(variable_get('boinc_weboptions_registrationtitle', 'Please read and acknowledge our terms of use'), array(), NULL, 'project:user-register' ) . '</h2>',
        '#prefix' => '<div id="register-title1">',
        '#suffix' => '</div>',
      );

      $form['termsofuse']['body'] = array(
        '#weight' => -10,
        '#value' => bts($termsofuse, array(), NULL, 'project:user-register'),
        '#prefix' => '<div id="register-termsofuse">',
        '#suffix' => '</div>',
      );

      $form['termsofuse']['agreeTOU'] = array(
        '#type' => 'checkbox',
        '#title' => bts(variable_get('boinc_weboptions_agreequestion', 'Do you agree with the above terms of use?'), array(), NULL, 'project:user-register'),
        '#weight' => -8,
        '#prefix' => '<div id="register-checkbox">',
        '#suffix' => '</div>',
      );
    }

    $form['title2'] = array(
      '#weight' => -6,
      '#value' => '<h2>' . bts(variable_get('boinc_weboptions_registrationtitle2', 'Fill in your name, email, and choose a secure passphrase.'), array(), NULL, 'project:user-register') . '</h2>',
      '#prefix' => '<div id="register-title2">',
      '#suffix' => '</div>',
    );

    if (module_exists('captcha')) {
      // Add an optional captcha
      $form['register_captcha'] = array(
        '#type' => 'captcha',
        '#weight' => 1000,
      );
    }

    $form['#validate'][] = 'boincuser_register_validate';

    $form['submit']['#weight'] = 1001;
    break;

  // Request new password form
  case 'user_pass':
    drupal_set_title(bts('Forgot password', array(), NULL, 'boinc:forgot-password'));
    // Replace name/email text box with email only; retain "name" label
    // for compatibility with standard Drupal submit function
    unset($form['name']);
    array_unshift($form, array(
      'name' => array(
        '#type' => 'textfield',
        '#title' => bts('Email address', array(), NULL, 'boinc:email-address-to-login'),
        '#size' => 60,
        '#maxlength' => EMAIL_MAX_LENGTH,
        '#required' => TRUE,
        '#description' => bts(
          'Enter your email address to receive instructions for resetting your password (or use the !authenticator_login).',
          array(
            '!authenticator_login' => l(
              bts('authenticator-based login', array(), NULL, 'boinc:forgot-password'),
              'user/login/auth'
            )
          )
        , NULL, 'boinc:forgot-password'),
      ),
    ));

    if (module_exists('captcha')) {
      // Add an optional captcha
      $form['register_captcha'] = array(
        '#type' => 'captcha',
        '#weight' => 0,
        '#prefix' => '<div id="captcha-password-reset">',
        '#suffix' => '</div>'
      );
    }

    // Wrap action buttons for styling consistency
    $form['buttons']['form control tabs prefix'] = array(
      '#value' => '<ul class="form-control tab-list">',
      '#weight' => 1001,
    );
    $form['buttons']['submit'] = $form['submit'];
    $form['buttons']['submit']['#prefix'] = '<li class="first tab">';
    $form['buttons']['submit']['#value'] = bts('Send', array(), NULL, 'boinc:form-send');
    $form['buttons']['submit']['#suffix'] = '</li>';
    $form['buttons']['submit']['#weight'] = 1002;
    $form['buttons']['cancel'] = array(
      '#value' => '<li class="tab">' . l(bts('Cancel', array(), NULL, 'boinc:form-cancel'), 'user/login') . '</li>',
      '#weight' => 1005,
    );
    $form['buttons']['form control tabs suffix'] = array(
      '#value' => '</ul>',
      '#weight' => 1010,
    );
    unset($form['submit']);

    // If the form is being submitted, use BOINC validation handler.
    if (isset($form_state['post']['name'])) {
      // Prepend the BOINC validation function to local validation
      array_unshift($form['#validate'], 'boincuser_request_pass_validate');
    }
    break;

  case 'views_exposed_form':
      $form['submit']['#value'] = bts('Search', array(), NULL, 'boinc:search-user');
    break;
  }
}

/**
 * Some form alterations (CCK fields) cannot be done until after the form is
 * built; this is called from boincuser_form_alter()
 */
function boincuser_profile_node_form_after_build($form, &$form_state) {
  // Move to community prefs form
  $form_state['storage']['avatar'] = $form['field_image'];
  unset($form['field_image']);
  return $form;
}

/**
 * Implementation of hook_elements()
 * @see http://api.drupal.org/api/drupal/developer--hooks--core.php/function/hook_elements/6
 */
function boincuser_elements() {
  $type['password_confirm']['#process'][] = 'boincuser_process_password_confirm';
  return $type;
}

/**
 * This madness is required to change the password field label on the user
 * profile form
 */
function boincuser_process_password_confirm($element) {
  // Check if parent element is "account".
  if ($element['#array_parents'][0] == 'account') {
    $element['pass1']['#title'] = bts('Change password', array(), NULL, 'boinc:forgot-password');
  }
  return $element;
}

/**
 * Implementation of hook_theme()
 * Register theme functions for use in this module.
 */
function boincuser_theme($existing, $type, $theme, $path) {
  return array(
    'boincuser_user_pass' => array(
      'arguments' => array()
    )
  );
}

/**
 * Implementation of hook_token_values
 */
function boincuser_token_values($type, $object = NULL, $options = array()) {
  if ($type == 'user') {
    $account = user_load($object->uid);
    $tokens['display-name'] = $account->boincuser_name;
    return $tokens;
  }
}

/**
 * Implementation of hook_token_list
 */
function boincuser_token_list($type = 'all') {
  if ($type == 'user' || $type == 'all') {
    $tokens['user']['display-name']      = t("The user's name that should be displayed");
    return $tokens;
  }
}

/**
 * Implementation of hook_views_pre_execute()
 */
function boincuser_views_pre_execute(&$view) {
  if ($view->args) {
    $account_id = $view->args[0];
  }
  if ($view->name=="user_activity") {
    // Run the following custom query for the user_activity view
    $view->build_info['query']= "
      SELECT node_revisions.vid AS vid,
        node.nid AS node_nid,
        node.uid AS users_node_uid,
        node.type AS node_type,
        node.title AS node_title,
        node_revisions.body AS node_revisions_body,
        node_revisions.timestamp AS node_revisions_timestamp
      FROM node_revisions node_revisions
      LEFT JOIN node node ON node_revisions.nid = node.nid
      INNER JOIN users users ON node.uid = users.uid
      WHERE (node.type in ('forum', 'news', 'page', 'story')) AND (node.status = 1) AND (node.uid = {$account_id} )

      UNION

      SELECT comments.cid AS cid,
        node_comments.nid AS node_comments_nid,
        users_comments.uid AS users_comments_uid,
        'comment' AS node_comments_type,
        node_comments.title AS node_comments_title,
        comments.comment AS comments_comment,
        comments.timestamp AS comments_timestamp
      FROM comments comments
      INNER JOIN node node_comments ON comments.nid = node_comments.nid
      INNER JOIN users users_comments ON comments.uid = users_comments.uid
      WHERE (node_comments.status = 1) AND (users_comments.uid = {$account_id} )
      ORDER BY node_revisions_timestamp DESC";

    // count_query determines the pager.  Do this so the right item count is returned.
    $view->build_info['count_query'] = $view->build_info['query'];
  }
}

/**
 * Implementation of hook_cron()
 */
function boincuser_cron() {
  // Delete expired users in the BOINC database, user_delete table.
  require_boinc('boinc_db');
  $num_deleted = BoincUserDeleted::delete_expired();
  if ($num_deleted>0) {
    watchdog('boincuser', "Deleted ${num_deleted} users from user_deleted table", WATCHDOG_NOTICE);
  }

  // Delete expired tokens from token table
  $tokens_deleted = BoincToken::delete_expired();
  if ($tokens_deleted>0) {
    watchdog('boincuser', "Deleted ${tokens_deleted} tokens from token table", WATCHDOG_NOTICE);
  }
}

/*  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *
 * Page callbacks from hook_menu()
 *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  */

/**
 * Page callback shortcut to recent posts for the logged in user
 */
function boincuser_goto_recent_posts() {
  global $user;
  drupal_goto("account/{$user->uid}/posts");
}

/**
 * Page callback shortcut to the team of the logged in user
 */
function boincuser_goto_team() {
  global $user;
  $account = user_load($user->uid);
  drupal_goto("community/teams/{$account->team}");
}

/**
 * Page callback for displaying a user profile
 * For some reason, menu context cannot be configured as expected for
 * user profile pages, so use a wrapper for display
 */
function boincuser_view_profile($account = null) {
  // Create the user profile page
  if (!$account) {
    global $user;
    $account = $user;
  }

  $min_credit_to_post = variable_get('boinc_comment_min_credit', 0);
  $verified_contributor = array_search('verified contributor', user_roles(true));
  if (!isset($account->roles[$verified_contributor])) {
    drupal_set_message(bts(
        'You may only create or modify your user profile after earning @count credits.',
        array('@count' => $min_credit_to_post), NULL, 'boinc:view-profile'
    ), 'warning', FALSE);
  }

  // For now, just call the user module profile view function
  user_build_content($account);
  return theme('user_profile', $account);
}

/**
 * Page callback for editing a user profile
 */
function boincuser_edit_profile($account = null) {
  // Create the user profile form
  if (!$account) {
    global $user;
    $account = $user;
  }
  // Render the form
  module_load_include('pages.inc', 'node', 'node');
  return content_profile_page_edit('profile', $account);
}

/**
  * Join page menu callback.
  * Display instructions on joining for new or existing BOINC users
  */
function join_page($type = null) {
  global $base_url;
  /* The paths/links to the rules-and-policies page is hardcoded
   * here. An improvement would be admin settings for the Join Page
   * where this path could be set.
   */
  $ruleslinkA = 'rules-and-policies';
  $ruleslinkB = 'content/rules-and-policies';
  $site_name = variable_get('site_name', 'Drupal-BOINC');
  $registration_enabled = variable_get('user_register', 0);
  $output = '<div class="join">';
  switch ($type) {
  case 'boinc':
    $output .= '<ol>';
    if ($registration_enabled) {
      $output .= '<li>' . bts('First !create_an_account here at @sitename.',
      array(
        '!create_an_account' => l(bts('create an account', array(), NULL, 'boinc:join-page'), 'user/registration'),
        '@sitename' => $site_name,
      ), NULL, 'boinc:join-page') . '</li>';
    }
    $output .= '  <li>' . bts("Install BOINC on this device if not already present.", array(), NULL, 'boinc:join-page') . '</li>';
    $output .= '  <li>' . bts("Select <i>Tools / Add Project</i>. Choose @sitename from the list, or enter @siteurl.",
    array(
      '@sitename' => $site_name,
      '@siteurl' => $base_url,
    ), NULL, 'boinc:join-page') . '</li>';
    if ($registration_enabled) {
      $output .= '<li>' . bts("If you're running a command-line or pre-5.0 version of BOINC, use <b>!boinccmd</b> to add the project.",
        array(
          '!boinccmd' => l('boinccmd --project_attach', 'http://boinc.berkeley.edu/wiki/Boinccmd_tool'),
        ), NULL, 'boinc:join-page') . '</li>';
    }
    else {
      $output .= '<li>' . bts("If you're running a command-line version of BOINC,
        please follow the <b>!instructionslink</b> to first <i>create an account</i>, and then <i>attach</i> to this project. Use the same project URL as above.",
        array(
          '!instructionslink' => l('instructions', 'http://boinc.berkeley.edu/wiki/Boinccmd_tool'),
        ), NULL, 'boinc:join-page') . '</li>';
    }
    $output .= '<li>' . bts("If you're running a pre-5.0 version of BOINC, please
      upgrade to a more recent version of BOINC to create an account
      at @this_project.",
      array(
        '@this_project' => $site_name,
      ), NULL, 'boinc:join-page') . '</li>';
    $output .=  '</ol>';
    break;
  case 'new':
  default:
    // Determine if there is a link to rules-and-policies
    //$ruleslink='';
    //if (drupal_lookup_path('source', $ruleslinkA)) {
    //  $ruleslink = drupal_lookup_path('source', $ruleslinkA);
    //} else if (drupal_lookup_path('source', $ruleslinkB)) {
    //  $ruleslink = drupal_lookup_path('source', $ruleslinkB);
    //}

    $ruleslink = drupal_lookup_path('source', variable_get('boinc_weboptions_rulespolicies', '') );

    // Join page output
    $output .= '<ol>';
    if ($registration_enabled) {
      $output .= '<li>' . bts('First !create_an_account here at @sitename.',
      array(
        '!create_an_account' => l(bts('create an account', array(), NULL, 'boinc:join-page'), 'user/registration'),
        '@sitename' => $site_name,
      ), NULL, 'boinc:join-page') . '</li>';
    }
    else if ( menu_valid_path(array('link_path' => $ruleslink)) ) {
      $output .= '  <li>' . bts("Read our !rules_and_policies.", array(
        '!rules_and_policies' => l(bts('Rules and Policies', array(), NULL, 'boinc:join-page'), $ruleslink),
      ), NULL, 'boinc:join-page') . '</li>';
    }
    $output .= '  <li>' . bts('Download the BOINC desktop software.', array(), NULL, 'boinc:join-page');
    $output .= '    <p>';
    $output .= '      <a class="button" href="http://boinc.berkeley.edu/download.php">Download</a>';
    $output .= '    </p>';
    $output .= '    ' . bts("For Android devices, visit !fdroid_link or download !apk_link directly.", array(
      '!fdroid_link' => '<a href="https://f-droid.org/en/packages/edu.berkeley.boinc">F-Droid</a>',
      '!apk_link' => '<a href="https://boinc.berkeley.edu/download_all.php">BOINC for Android</a>',
    ), NULL, 'boinc:join-page');
    $output .= '  </li>';
    $output .= '  <li>' . bts('Run the installer.', array(), NULL, 'boinc:join-page') . '</li>';
    $output .= '  <li>' . bts('Choose @sitename from the list, or enter @siteurl.', array(
      '@sitename' => $site_name,
      '@siteurl' => $base_url,
    ), NULL, 'boinc:join-page') . '</li>';
    $output .=  '</ol>';
  }
  $output .= '</div>';
  return $output;
}

/**
  * Home page content for embedding in Panels page
  */
function boincuser_home_page() {
  global $user;
  $site_name = variable_get('site_name', 'Drupal-BOINC');
  // get the front page message from database; this is set in the admin interface under BOINC Other
  $site_message = variable_get('boinc_other_frontpage','');

  // Determine the user of the day
  $current_uotd = db_fetch_object(db_query("
    SELECT
      uid,
      uotd_time
    FROM {boincuser}
    ORDER BY uotd_time DESC
    LIMIT 1"
  ));
  if ($current_uotd->uotd_time < strtotime('today midnight')) {
    $uotd = boincuser_select_user_of_the_day();
  }
  else {
    $uotd = user_load($current_uotd->uid);
  }
  $uotd_image = boincuser_get_user_profile_image($uotd->uid, FALSE);
  $output = '<h2 class="pane-title">';
  $output .= ($user->uid) ? bts('Welcome back!', array(), NULL, 'boinc:front-page') : ($site_name ? bts('What is @this_project?', array('@this_project' => $site_name)) : bts('Welcome!', array(), NULL, 'boinc:front-page'));
  $output .= '</h2>';
  $output .= '<div class="boinc-overview balance-height-front">';
  $output .= '  <div>' . bts($site_message, array(), NULL, "project:front page") . ' ' . l(bts('Learn more', array(), NULL, 'boinc:front-page'), 'about') . '</div>';
  if ($user->uid) {
    $output .= '  <div>' . l(bts('View account', array(), NULL, 'boinc:front-page'), 'dashboard', array('attributes' => array('class' => 'join button'))) . '</div>';
  }
  else {
    $output .= '  <div>' . l(bts('Join now', array(), NULL, 'boinc:front-page'), 'join', array('attributes' => array('class' => 'join button'))) . '</div>';
  }
  $output .= '</div>';
  $output .= '<div class="boinc-overview-details">';
  $output .= '  <div class="detail-container">';
  $output .= '    <a class="user-of-the-day" href="account/' . $uotd->uid . '">';
  $output .= '      <div class="picture">';
  $output .= theme('imagefield_image', $uotd_image['image'], $uotd_image['alt'],
    $uotd_image['alt'], array(), FALSE);
  $output .= '      </div>';
  $output .= '      <div class="text">' . bts('User of the day', array(), NULL, 'boinc:front-page') . '</div>';
  $output .= '      <div class="detail">' . $uotd->boincuser_name . '</div>';
  $output .= '    </a>';
  $output .= '    <div class="volunteers">';
  $output .= '      <div class="text">' . bts('Over 500,000 volunteers and counting.', array(), NULL, 'boinc:front-page') . '</div>';
  $output .= '      <div class="platforms">';
  $output .= '        <div class="detail platform windows">' . bts('Windows', array(), NULL, 'boinc:front-page') . '</div>';
  $output .= '        <div class="detail platform mac">' .  bts('Mac', array(), NULL, 'boinc:front-page') . '</div>';
  $output .= '        <div class="detail platform linux">' . bts('Linux', array(), NULL, 'boinc:front-page') . '</div>';
  $output .= '      </div>';
  $output .= '    </div>';
  $output .= '  </div>';
  $output .= '</div>';
  return $output;
}

/**
 * Page callback for the create account RPC (create_account.php).
 * Create a new user account based on supplied parameters.
 */
function boincuser_create_account() {
  global $base_url;

  require_boinc('boinc_db');
  require_boinc('user_util');
  require_boinc('xml');
  $params = array(
    'email_addr' => isset($_GET['email_addr']) ? $_GET['email_addr'] : '',
    'user_name' => isset($_GET['user_name']) ? $_GET['user_name'] : '',
    'passwd_hash' => isset($_GET['passwd_hash']) ? $_GET['passwd_hash'] : ''
  );

  // Begin output
  xml_header();

  // Account creation disabled
  $enablethisRPC = variable_get('boinc_weboptions_enableaccountcreateRPC', TRUE);
  if (!$enablethisRPC) {
    $mess = bts('Account creation is done through our Web site. Please register at @url', array(
      '@url' => $base_url . '/user/registration',
    ),
    NULL, 'boinc:create_account');
    xml_error(-208, $mess);
  }
  // Invalid invite code

  // Validate input
  if (user_validate_mail($params['email_addr']) or !is_valid_email_addr($params['email_addr'])) {
    xml_error(-205);
  }

  // Make sure user_name is unique and cleaned
  $unique_name = create_proper_drupalname($params['user_name']);
  if ($error = user_validate_name($unique_name)) {
    xml_error(-188, $error);
  }
  if (strlen($params['passwd_hash']) != 32) {
    xml_error(-1, 'password hash length not 32');
  }

  // Process input
  // Check this email against previous email addresses.
  $tmpuser = BoincUser::lookup_prev_email_addr($params['email_addr']);
  if ($tmpuser) {
    xml_error(-137);
  }

  // Check this email on current email addresses.
  $boinc_user = BoincUser::lookup_email_addr($params['email_addr']);
  if ($boinc_user) {
    // Return authenticator for existing users
    if ( ($params['passwd_hash'] == $boinc_user->passwd_hash) or
    password_verify($params['passwd_hash'], $boinc_user->passwd_hash) ) {
      $output = array('authenticator' => $boinc_user->authenticator);
    }
    else {
      xml_error(-137);
    }
  }
  else {
    // Verify that there isn't somehow a Drupal user already (not possible with proper function)
    if ($existing_user = user_load(array('mail' => $params['email_addr']))) {
      xml_error(-137, 'account error');
    }
    // Create new account
    $unrestricted_role = array_search('community member', user_roles(true));

    $newUser = array(
      'name' => $unique_name,
      'pass' => $params['passwd_hash'], // note: passing a hash here requires ALL passwords to be hashed via hook prior to interacting with the hash stored in the db
      'mail' => $params['email_addr'],
      'status' => 1,
      'init' => $params['email_addr'],
      'roles' => array($unrestricted_role => ''),
      'boincuser_name' => $params['user_name'],
      'boinchash_flag' => TRUE,
    );

    // Create the drupal user. If the drupal user cannot be created,
    // then stop with error message.
    // The user is created in the 'insert' op in hook_user.
    $user = user_save(null, $newUser);
    if (!$user) {
      watchdog('boincuser', 'create_account: Failed to create Drupal user account for @email', array('@email' => $params['email_addr']), WATCHDOG_WARNING);
      xml_error(-137, 'error creating BOINC account');
    }// if drupal user created.

    $output = array('authenticator' => $user->boincuser_account_key);
  }// if existing user found.

  // Output authenticator
  echo " <account_out>\n";
  echo "   <authenticator>{$output['authenticator']}</authenticator>\n";
  echo "</account_out>\n";
}

/**
 * RPC for account_finish.php, the page user is sent to once an
 * account is created using the BOINC clinet.
 */
function boincuser_account_finish() {
  global $user;

  $authtoken = isset($_GET['auth']) ? $_GET['auth'] : '';

  // Ensure there is a authentication token before continuing
  if (empty($authtoken)) {
    drupal_not_found();
    return ;
  }

  if (strlen($authtoken) != 32) {
    drupal_set_message(bts('ERROR: There is no account with that authenticator.', array(), NULL, 'boinc:account-finish'), 'error');
    drupal_goto();
  }

  require_boinc('boinc_db');
  $boinc_user = BoincUser::lookup("authenticator='".addslashes($authtoken)."'");
  if (!$boinc_user) {
    drupal_set_message(bts('ERROR: There is no account with that authenticator.', array(), NULL, 'boinc:account-finish'), 'error');
    drupal_goto();
  }
  $user = user_load(get_drupal_id($boinc_user->id));

  if (!$user) {
    drupal_set_message(bts('ERROR: There was a problem loading your account. Try logging in with your user name and password.', array(), NULL, 'boinc:account-finish'), 'error');
    drupal_goto();
  }

  // Lookup path to custom account finish page
  $customaccountfinishpath = drupal_lookup_path('source', variable_get('boinc_weboptions_accountfinish', '') );
  if ( menu_valid_path(array('link_path' => $customaccountfinishpath)) ) {
    $node = menu_get_object('node', 1, $customaccountfinishpath);
    if ($node) {
      return node_page_view($node);
    }
  }

  // open links in new window
  $options = array(
    'attributes' => array( 'target' => '_blank' ),
  );

  // Check moderation page exists
  $moderationpath = drupal_lookup_path('source', variable_get('boinc_weboptions_moderationpage', '') );
  if ( menu_valid_path(array('link_path' => $moderationpath)) ) {
    $modsentence = bts('Please note: user profiles are subject to !moderation.', array('!moderation' => l(bts('moderation', array(), NULL, 'boinc:account-finish'), $moderationpath, $options)), NULL, 'boinc:account-finish');
  } else {
    $modsentence = bts('Please note: user profiles are subject to moderation.', array(), NULL, 'boinc:account-finish');
  }

  $username = $user->boincuser_name;
  $site_name = variable_get('site_name', 'Drupal-BOINC');
  $output = "<p>" . bts('Thank you @user_name for joining @site_name. Your account has been created. Your BOINC client should start working on assigned tasks soon, without any additional action or configuration. Please visit the links below for more information and additional options. (Links will open in a new window.)',
  array(
    '@user_name' => $username,
    '@site_name' => $site_name,
  ), NULL, 'boinc:account-finish') . "</p>";

  $links = array(
    array(
      'data' => bts('Change your username at !community_preferences.', array(
        '!community_preferences' => l(bts('Community Preferences', array(), NULL, 'boinc:account-fininsh'), 'account/prefs/community', $options),
      ), NULL, 'boinc:account-finish'),
      'children' => array(
        bts('Your username is used to identify yourself to other volunteers on this Web site.', array(), NULL, 'boinc:account-finish'),
        bts('In addition, you may set your account\'s default language and adjust notification settings.', array(), NULL, 'boinc:account-finish'),
      ),
    ),
    array(
      'data' => bts('Change your !computing_preferences.', array(
        '!computing_preferences' => l(bts('Computing Preferences', array(), NULL, 'boinc:account-finish'), 'account/prefs', $options),
      ), NULL, 'boinc:account-finish'),
      'children' => array(
        bts('You may adjust how much CPU, RAM, and Disk space the BOINC client is allowed to use for tasks on your computer.', array(), NULL, 'boinc:account-finish'),
        bts('By default, you will run @site_name tasks without any additional configuration.', array(
          '@site_name' => $site_name,
        ), NULL, 'boinc:account-finish'),
        bts('It is recommended new volunteers leave the default settings until they gain experience running some tasks. Ask questions in the !forums to get advice before making changes to a setting you don\'t understand.', array(
          '!forums' => l(bts('forums', array(), NULL, 'boinc:account-finish'), 'community/forum', $options),
        ), NULL, 'boinc:account-finish'),
      ),
    ),
    array(
        'data' => bts('Create a !user_profile.', array(
          '!user_profile' => l(bts('User Profile', array(), NULL, 'boinc:account-finish'), '/account/profile/edit', $options),
        ), NULL, 'boinc:account-finish'),
      'children' => array(
        bts('A user profile will inform other volunteers who you are and why you joined @site_name.', array(
          '@site_name' => $site_name,
        ), NULL, 'boinc:account-finish'),
        $modsentence,
      ),
    ),
    array(
      'data' => bts('Join a !team.', array(
        '!team' => l(bts('Team', array(), NULL, 'boinc:account-finish'), '/community/teams', $options),
      ), NULL, 'boinc:account-finish'),
      'children' => array(
        bts('You may join a team, made up of other volunteers.', array(), NULL, 'boinc:account-finish'),
      ),
    ),
    array(
      'data' => bts('Go to your !account_dashboard.', array(
        '!account_dashboard'=> l(bts('Account Dashboard', array(), NULL, 'boinc:account-finish'), 'account/dashboard', $options),
      ), NULL, 'boinc:account-finish'),
      'children' => array(
        bts('Your account dashboard has information and links about your computer(s) and task(s) assigned.', array(), NULL, 'boinc:account-finish'),
      ),
    ),
    array(
      'data' => bts('Visit our !help pages.', array(
        '!help' => l(bts('Help', array(), NULL, 'boinc:account-finish'), '/help', $options)
      ), NULL, 'boinc:account-finish'),
      'children' => array(
        bts('Ask for help in our community\'s !forums.', array(
          '!forums' => l(bts('forums', array(), NULL, 'boinc:account-finish'), 'community/forum', $options)
        ), NULL, 'boinc:account-finish'),
      ),
    ),
  );

  //List of links
  $output .= theme_item_list($links, $title = NULL, $type='ul');

  return $output;
}

/**
 * Determine if the user has permission to control community access
 */
function boincuser_moderate_community_access() {
  if (user_access('assign community member role')
  OR user_access('assign all roles')) {
    return TRUE;
  }
  return FALSE;
}

/**
 * Get the count of items in the moderation queue
 */
function boincuser_moderation_queue_count($caller = 'user') {
  $allowed = FALSE;
  switch ($caller) {
  case 'cron':
    $allowed = TRUE;
    break;
  case 'user':
  default:
    $allowed = user_access('edit any profile content');
  }
  if ($allowed) {
    return db_result(db_query("
      SELECT COUNT(*)
      FROM {node}
      WHERE type = 'profile'
      AND moderate = 1"
    ));
  }
  return NULL;
}

/**
 * Controller for handling simple user account status configuration.
 * Allow community membership status to be set for users by direct link rather
 * than through the user account info form.
 */
function boincuser_control($uid = NULL, $action = NULL) {
  if (!$uid OR !$account = user_load($uid)) {
    // What are you even doing here...
    return FALSE;
  }
  switch ($action) {
  case 'ban':
    if (boincuser_moderate_community_access()) {
      $penalty_period = variable_get('boinc_penalty_period', 7*24*60*60);
      $boincuser_record = array(
        'uid' => $uid,
        'penalty_expiration' => time() + $penalty_period,
      );
      drupal_write_record('boincuser', $boincuser_record, 'uid');
      $community_role = array_search('community member', user_roles(true));
      if (isset($account->roles[$community_role])) {
        unset($account->roles[$community_role]);
        user_save($account, array('roles' => $account->roles));
      }
    }
    break;
  case 'lift-ban':
    if (boincuser_moderate_community_access()) {
      $boincuser_record = array(
        'uid' => $uid,
        'penalty_expiration' => 0,
      );
      drupal_write_record('boincuser', $boincuser_record, 'uid');
      $community_role = array_search('community member', user_roles(true));
      if (!isset($account->roles[$community_role])) {
        $account->roles[$community_role] = 'community member';
        user_save($account, array('roles' => $account->roles));
      }
    }
    break;
  default:
  }
  drupal_goto("account/{$account->uid}");
}

/**
 * Mark a user profile as approved by publishing it and turning off the
 * moderation flag.
 */
function boincuser_moderate_profile_approve($account) {
  $node = new stdClass;
  $node->type = 'profile';
  $node->language = '';
  $nid = content_profile_profile_exists($node, $account->uid);
  $profile = node_load($nid);
  $profile->moderate = 0;
  $profile->status = 1;
  node_save($profile);
  drupal_set_message('This profile has been marked as approved.');
  drupal_goto();
}

/**
 * Mark a user profile as rejected and notify the user of the reason.
 */
function boincuser_moderate_profile_reject($uid, $reason = '') {
  $account = user_load($uid);
  $node = new stdClass;
  $node->type = 'profile';
  $node->language = '';
  $nid = content_profile_profile_exists($node, $uid);
  $profile = node_load($nid);
  if ($profile->nid) {
    global $user;
    global $base_url;
    global $base_path;
    $site_name = variable_get('site_name', 'Drupal-BOINC');
    $site_url = $base_url . $base_path;
    $moderator = user_load($user->uid);
    $profile->moderate = 0;
    $profile->status = 0;
    node_save($profile);
    $settings = array(
      'from' => '',
      'subject' => "Profile moderation at {$site_name}",
      'message' => ''
      . "{$account->boincuser_name},\n"
      . "\n"
      . "{$moderator->boincuser_name} has rejected your profile at"
      . " {$site_name} for the following reason: \n"
      . "\n"
      . "{$reason}\n"
      . "\n"
      . "\n"
      . "Since it has not been approved, your profile is not visible to other"
      . " {$site_name} users. Please make the needed changes here:\n"
      . "\n"
      . "{$site_url}account/profile \n"
      . "\n"
      . "Thanks, \n"
      . "\n"
      . "{$site_name} support team",
    );
    rules_action_mail_to_user($account, $settings);
    drupal_set_message('This profile has been marked as rejected.');
  }
  drupal_goto();
}

/**
 * Ban a user and send a notification of the reason.
 */
function boincuser_moderate_user_ban($uid, $reason = '', $duration = '') {
  if (user_access('assign community member role')
  OR user_access('assign all roles')) {
    $account = user_load($uid);
    if ($account->uid) {
      module_load_include('inc', 'rules', 'modules/system.rules');
      if ($duration === '') {
        $duration = variable_get('boinc_penalty_period', 7*24*60*60);
      }
      $penalty_expiration = ($duration > 0) ? time() + $duration : 4294967295;
      $boincuser_record = array(
        'uid' => $uid,
        'penalty_expiration' => $penalty_expiration,
      );
      drupal_write_record('boincuser', $boincuser_record, 'uid');

      $community_role = array_search('community member', user_roles(true));
      if (isset($account->roles[$community_role])) {
        unset($account->roles[$community_role]);
        user_save($account, array('roles' => $account->roles));
      }

      global $user;
      global $base_url;
      global $base_path;
      $site_name = variable_get('site_name', 'Drupal-BOINC');
      $site_url = $base_url . $base_path;
      $moderator = user_load($user->uid);
      $settings = array(
        'from' => '',
        'subject' => "User moderation at {$site_name}",
        'message' => ''
        . "{$account->boincuser_name},\n"
        . "\n"
        . "You have been banned from using community features at"
        . " {$site_name} for the following reason: \n"
        . "\n"
        . "{$reason}\n"
        . "\n"
        . "The duration of this ban can be found on your account page: \n"
        . "\n"
        . "{$site_url}account \n"
        . "\n"
        . "If you feel that this decision was made in error, please send a"
        . " private message to {$moderator->boincuser_name}. \n"
        . "\n"
        . "Thanks, \n"
        . "\n"
        . "{$site_name} support team",
      );
      rules_action_mail_to_user($account, $settings);
      drupal_set_message('This user has been banned.');
    }
  }
  drupal_goto();
}


/*  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *
 * User data access support functions
 *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  */

/**
 *
 */
function boincuser_get_weak_auth($boinc_id = null) {
  if (!$boinc_id) {
    global $user;
    $account = user_load($user->uid);
    $boinc_id = $account->boincuser_id;
  }

  $boinc_user = boincuser_load($account->boincuser_id);

  return weak_auth($boinc_user);
}

/**
 * Retrieve the profile image for a user account
 * User profile image is managed by the content_profile module rather than core
 * Drupal User so must be inserted into comments, etc. (not so by default)
 */
function boincuser_get_user_profile_image($uid, $avatar = TRUE) {
  // Though the function name implies otherwise, get the avatar by default
  $image_field = ($avatar) ? 'field_image_fid' : 'field_profile_image_fid';
  $image_fid = db_result(db_query("
    SELECT ctp.%s
    FROM {content_type_profile} ctp
    INNER JOIN {node} n ON ctp.nid = n.nid
    WHERE n.uid = %d AND n.type = '%s'",
    $image_field, $uid, 'profile'));
  $user_image['image'] = field_file_load($image_fid);
  if (!$user_image['image']['filepath']) {
    // Load the default image if one does not exist
    $account = user_load($uid);
    if ($avatar AND module_exists('gravatar') AND user_access('use gravatar', $account) AND $account->gravatar) {
      // Use a Gravatar rather than the system default image
      $options = array(
        'size' => 100,
        'rating' => 'G',
      );
      // Get the Gravatar URL and see if the image exists
      $url = gravatar_get_gravatar($account->mail, $options);
      $headers = @get_headers($url);
      if (preg_match("|200|", $headers[0])) {
        return $url;
      }
    }
    // Get default image if nothing else works
    $content_node_widget_settings = db_result(db_query("SELECT widget_settings FROM {content_node_field_instance} WHERE field_name = '%s'", ($avatar ? 'field_image' : 'field_profile_image')));
    $content_node_widget_settings = unserialize($content_node_widget_settings);
    $user_image['image'] = $content_node_widget_settings['default_image'];
  }
  $user = user_load($uid);
  // Use boinc username for image alt/title attributes
  $user_image['alt'] = $user->boincuser_name;
  return $user_image;
}

/**
 * Generate a table of a user's projects
 */
function boincuser_get_projects_table($account = null) {
  if ($account AND is_numeric($account)) {
    $account = user_load($account);
  }
  $projects = boincuser_get_projects($account);
  if (!$projects) return bts('no projects...', array(), NULL, 'boinc:account-dashboard');

  $output = '';
  $output .= '<table class="user-projects">' . "\n";
  $output .= '<thead>' . "\n";
  $output .= '  <tr>' . "\n";
  $output .= '    <th>' . bts('Name', array(), NULL, 'boinc:project-name:-1:ignoreoverwrite') . '</th>' . "\n";
  $output .= '    <th class="numeric">' . bts('Avg credit', array(), NULL, 'boinc:account-dashboard') . '</th>' . "\n";
  $output .= '    <th class="numeric">' . bts('Total credit', array(), NULL, 'boinc:user-or-team-total-credits') . '</th>' . "\n";
  $output .= '  </tr>' . "\n";
  $output .= '</thead>' . "\n";
  $output .= '<tbody>' . "\n";
  foreach ($projects AS $project) {
    $url = rtrim($project->url, '/') . '/show_user.php?userid=' . $project->id;
    $output .= '  <tr>' . "\n";
    $output .= '    <td>' . l($project->name, $url) . '</td>' . "\n";
    $output .= '    <td class="numeric">' . boincwork_format_stats((float) $project->expavg_credit) . '</td>' . "\n";
    $output .= '    <td class="numeric">' . boincwork_format_stats((float) $project->total_credit) . '</td>' . "\n";
    $output .= '  </tr>' . "\n";
  }
  $output .= '</tbody>' . "\n";
  $output .= '</table>' . "\n";
  $more_link = ($account) ? "user/{$account->uid}/stats" : 'account/stats';
  //$output .= "<div class=\"more-link\"><a href=\"{$more_link}\">More stats</a></div>" . "\n";
  return $output;
}

/**
 * Make an RPC call to the stats server and retreive user data
 */
function boincuser_get_stats_user_data($cpid = null) {

  // [TODO] Set this stuff in site config!
  $stats_server = 'stats.gridrepublic.org';
  $stats_rpc = 'rpc/get_user.php';

  // Construct query string
  $get = array(
    'cpid' => $cpid
  );
  $args = array();
  foreach ($get as $arg => $value) $args[] = "{$arg}=" . rawurlencode($value);
  $query = '?' . implode('&', $args);

  // Load XML from RPC
  $target_url = "http://{$stats_server}/{$stats_rpc}{$query}";
  $result = drupal_http_request($target_url);
  if (in_array($result->code, array(200, 304)) || in_array($result->redirect_code, array(200, 304))) {
    return simplexml_load_string($result->data);
  }
  return NULL;
}

/**
 * Get the list of all BOINC projects of a user
 */
function boincuser_get_projects($account = null) {

  // Use the current user by default
  if (!$account) {
    global $user;
    $account = user_load($user->uid);
  }

  $account_stats = boincuser_get_stats_user_data($account->boincuser_cpid);

  return ($account_stats AND isset($account_stats->project)) ? $account_stats->project : null;
}


/**
 * Get the links to display under the user profile
 */
function boincuser_get_profile_links($uid) {
  global $user;
  $account = user_load($uid);
  $profile = content_profile_load('profile', $account->uid);
  $output = '';
  if ($profile) {
    $profile_is_approved = ($profile->status AND !$profile->moderate);
    $user_is_moderator = user_access('edit any profile content');
    $is_own_profile = ($user->uid == $account->uid);
    $profile_moderation_path = "moderate/profile/{$account->uid}";
    $links = array();

    if ($profile->moderate AND $user_is_moderator) {
      $links['approve_profile'] = array(
        'title' => bts('Approve profile', array(), NULL, 'boinc:moderate-user'),
        'href' => "{$profile_moderation_path}/approve",
        'attributes' => array(
          'title' => bts('Approve this profile content', array(), NULL, 'boinc:moderate-user'),
          'class' => 'first primary tab',
        )
      );
      /*$links['edit_profile'] = array(
        'title' => bts('Edit profile', array(), NULL, 'boinc:moderate-user'),
        'href' => "{$profile_moderation_path}/edit",
        'attributes' => array(
          'title' => bts('Edit the content of this profile', array(), NULL, 'boinc:moderate-user'),
          'class' => 'tab',
        )
      );*/
      $links['reject_profile'] = array(
        'title' => bts('Reject profile', array(), NULL, 'boinc:moderate-user'),
        'href' => "{$profile_moderation_path}/reject",
        'attributes' => array(
          'title' => bts('Reject this profile content', array(), NULL, 'boinc:moderate-user'),
          'class' => 'tab',
        )
      );
    }
    $output .= '<ul class="tab-list">';
    $count = 0;
    foreach ($links as $key => $link) {
      $output .= '<li class="' . (($count == 0) ? 'first primary ' : '') . 'tab">';
      $output .= l($link['title'], $link['href'], array('query' => drupal_get_destination()));
      $output .= '</li>';
      $count++;
    }
    $output .= '<li class="' . (($count) ? '' : 'first ') . 'last tab">' . flag_create_link('abuse_user_meta', $account->uid) . '</li>';
    $output .= '</ul>';
  }
  return $output;
/*
<ul class="tab-list">
  <li class="primary first tab">
    <?php print l(bts('Approve profile'), 'moderate/profile/%user:uid/approve'); ?>
  </li>
  <li class="tab">
    <?php print l(bts('Remove profile'), 'moderate/profile/%user:uid/remove'); ?>
  </li>
  <li class="last tab">
    <?php print flag_create_link('abuse_user', %user:uid); ?>
  </li>
</ul>
*/
}

/*  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *
 * Call backs for apachesolr
 *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  */

/**
 * Implementation of hook_apachesolr_index_documents_alter()
 *
 * This function modifies the Apache Solr Document to use the user's
 * join date as the "ds_create" timestamp.
 *
 * @param array $documents
 *   Array of ApacheSolrDocument which are to be indexed
 * @param string $entity
 *   The entity object, typically a node object.
 * @param string $entity_type
 *   The entity's type, typically 'node'.
 * @param $env_id
 *   Environment ID for apache solr.
 */
function boincuser_apachesolr_index_documents_alter(array &$documents, $entity, $entity_type, $env_id) {

  foreach ($documents as $document) {
    if ( $document->entity_type=='node' AND $document->bundle=='profile' ) {
      // Node information.
      $nid = $document->entity_id;
      $node = node_load($nid);
      $account = user_load($node->uid);

      // Use boincuser name and not drupal user name
      $document->label = apachesolr_clean_text($account->boincuser_name);
      // Author information
      if ($node->uid == 0 || strlen($node->name) == 0) {
        // @see user_validate_name(). !'0' === TRUE.
        $document->ss_name = '0';
      }
      else {
        $document->ss_name = $account->boincuser_name;
        // We want the name to be searchable for keywords.
        $document->tos_name = $account->boincuser_name;
      }

      // Rename "Profle" to "User"
      $document->bundle = "User";
      $document->bundle_name = "User";

      // Replace the Solr document's created field with the date the user
      // account was created. This replaces the node creation date typically
      // used for indexing nodes.
      $document->ds_created = apachesolr_date_iso($account->created);
    }
  }

}

/*  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *
 * Call backs for private message
 *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  */

/**
 * Implementation of hook_privatemsg_name_lookup();
 */
function boincuser_privatemsg_name_lookup($string) {
  // Get the BOINC ID from the name string, and lookup the
  // corresponding drupal user.
  $boincname = substr($string, 0, strrpos($string, '_'));
  $boincid = substr($string, strrpos($string, '_') + 1);
  $drupalid = get_drupal_id($boincid);

  // Name has spaced replaced with special UTF-8 characters in
  // privatemsg module. We need to convert them back to spaces for the
  // check below.
  $boincname = preg_replace("/\\xc2\\xa0/", " ", $boincname);
  if ($drupalid>0) {
    if ($recipient = user_load(array('uid' => $drupalid))) {
      // Double-check that the loaded user matches both boincuser_id
      // and boincuser_name.
      if ( ($boincid == $recipient->boincuser_id) AND ($boincname == $recipient->boincuser_name) ) {
        return $recipient;
      }
    }
  }
}
