<?php

/**
 * Implements hook_menu().
 */
function email_required_menu() {
  $items = array();
  $items['email/verify'] = array(
    'title' => 'Email verifications',
    'description' => 'Form to submit for email verification',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('email_required_verification_form'),
    'access callback' => 'email_required_verification_form_access',
    'file' => 'email_required.pages.inc',
    'type' => MENU_CALLBACK,
  );
  $items['email/verify/%user/%'] = array(
    'title' => 'Email verification',
    'description' => 'Path to verify your email address',
    'page callback' => 'email_required_verification_page',
    'page arguments' => array(2, 3),
    'access callback' => 'email_required_verification_page_access',
    'access arguments' => array(2, 3),
    'file' => 'email_required.pages.inc',
    'type' => MENU_CALLBACK,
  );
  $items['admin/config/people/email-required'] = array(
    'title' => 'Email verification',
    'description' => 'Configure when users will need to verify their email address',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('email_required_admin_settings'),
    'access arguments' => array('administer email required'),
    'file' => 'email_required.admin.inc',
    'type' => MENU_NORMAL_ITEM,
  );
  return $items;
}

/**
 * Implements hook_init().
 */
function email_required_init() {
  // If authenticated
  if (user_is_logged_in()) {
    // See if we have paths to enforce a required email
    if ($paths = variable_get('email_required_paths', NULL)) {
      // Determine the current path
      $path = drupal_strtolower(drupal_get_path_alias($_GET['q']));
      // See if we have a match
      if (drupal_match_path($path, $paths)) {
        // See if the user has to validate their email
        if (!email_required_user_is_validated()) {
          // See if we can capture the referred page
          $options = array();
          if (isset($_SERVER['HTTP_REFERER']) && $referer = $_SERVER['HTTP_REFERER']) {
            $referer = str_replace(url('<front>', array('absolute' => TRUE)), '', $referer);
            $options['query'] = array('destination' => $referer);
          }
          // Redirect to the verify form
          drupal_goto('email/verify', $options);
        }
      }
    }
  }
}

/**
 * Implements hook_mail().
 */
function email_required_mail($key, &$message, $params) {
  global $user;
  // Get email text
  $text = _email_required_email_text();
  // Inject a verification link
  $text['body'] = str_replace('!link', email_required_generate_link($user, TRUE), $text['body']);
  // Attach to the message, and replace tokens
  $message['subject'] = token_replace($text['subject'], array('user' => $user));
  $message['body'] = array(token_replace($text['body'], array('user' => $user)));
}

/**
 * Implements hook_permission().
 */
function email_required_permission() {
  return array(
    'administer email required' => array(
      'title' => t('Administer email required'),
      'description' => t('Configure the settings for this module.'),
    ),
    'bypass email required' => array(
      'title' => t('Bypass email verification'),
      'description' => t('These users will not have to verify their email. Anonyous users will never have to.'),
    ),
  );
}

/**
 * Implements hook_user_presave().
 */
function email_required_user_presave(&$edit, $account, $category) {
  // See if the user exists yet
  if ($account->uid) {
    // See if the email address has changed
    if (strtolower($edit['mail']) != strtolower($account->mail)) {
      // Mark this user as no longer having a verified email address
      email_required_delete_hash($account);
    }
  }
}

/**
 * Generate a URL to verify an email address
 * 
 * @param $user
 *   A user object or uid
 * @param $absolute
 *   TRUE if the link should be absolute.
 * @return
 *   A link to verify an email address, or FALSE if one cannot be made.
 */
function email_required_generate_link($user = NULL, $absolute = FALSE) {
  // Get the uid
  $uid = _email_required_get_uid($user);
  // Get the hash string
  if ($hash = email_required_load_hash($user, TRUE)) {
    // Provide the link
    return url("email/verify/{$uid}/{$hash->hash}", array('absolute' => $absolute));
  }
  return FALSE;
}

/**
 * Delete a hash entry for a given user
 *
 * @param $user
 *   A user object or uid
 */
function email_required_delete_hash($user = NULL) {
  // Delete the hash entry
  db_delete('email_required')
    ->condition('uid', _email_required_get_uid($user))
    ->execute();
}

/**
 * Create a hash entry for a given user
 * 
 * This will delete any existing entries so call with caution
 *
 * @param $user
 *   A user object
 * @return
 *   A hash object
 */
function email_required_create_hash($user = NULL) {
  if (!$user) {
    global $user;
  }
  
  // Create the hash object
  $hash = array(
    'uid' => _email_required_get_uid($user),
    'hash' => _email_required_hash($user),
    'created' => REQUEST_TIME,
    'verified' => 0,
  );
  
  // Delete any existing hashes for this user
  email_required_delete_hash($user);
  
  // Insert it
  db_insert('email_required')
    ->fields($hash)
    ->execute();
    
  return $hash;
}

/**
 * Validate a user's hash
 *
 * @param $user
 *   A user object or uid
 */
function email_required_validate_hash($user = NULL) {
  // Delete the hash entry
  db_update('email_required')
    ->fields(array('verified' => REQUEST_TIME))
    ->condition('uid', _email_required_get_uid($user))
    ->execute();
}

/**
 * Get a user's hash object
 *
 * @param $user
 *   A user object or uid
 * @param $reset
 *   TRUE if the static cache should be skipped, otherwise FALSE.
 * @return
 *   A hash object
 */
function email_required_load_hash($user = NULL, $reset = FALSE) {
  static $hashes = array();
  
  // Determine the uid
  $uid = _email_required_get_uid($user);
  
  // Check the cache
  if ($reset || !isset($hashes[$uid])) {
    // Fetch the hash entry
    $hashes[$uid] = 
      db_select('email_required', 'e')
        ->fields('e', array('uid', 'hash', 'created', 'verified'))
        ->condition('uid', $uid)
        ->execute()
        ->fetchObject();
  }
  
  return $hashes[$uid];
}

/**
 * Determine if a given user has validated their email
 * 
 * @param $user
 *   A user object or uid
 * @param $admin
 *   TRUE if users with adequate permissions will be considered
 *   validated regardless
 * @return
 *   TRUE if the user is validated, otherwise FALSE.
 */
function email_required_user_is_validated($user = NULL, $admin = TRUE) {
  static $validated = array();
  
  // Determine the uid
  $uid = _email_required_get_uid($user);
  
  // Generate a cache key
  $key = $uid . ($admin ? ':admins' : '');
  
  // Check the cache
  if (!isset($validated[$key])) {
    // Assume false
    $validated[$key] = FALSE;
    // Check higher permissions first, if desired
    if ($admin && (user_access('administer email required') || user_access('bypass email required'))) {
      $validated[$key] = TRUE;
    }
    // Load the hash to check
    else if ($hash = email_required_load_hash($user)) {
      if ($hash->verified) {
        $validated[$key] = TRUE;
      }
    }
    // Allow modules to alter this
    drupal_alter('email_required_validated', $user, $admin, $validated[$key]);
  }
  
  return $validated[$key];
}

/**
 * Access callback for the verification form
 */
function email_required_verification_form_access() {
  global $user;
  // No anonymous allowed
  if (!$user->uid) {
    return FALSE;
  }
  return email_required_user_is_validated($user) ? FALSE : TRUE;
}

/**
 * Access callback for the verification page
 * 
 * @param $account
 *   A user object specified in the link
 * @param $string
 *   The hash string on the URL
 */
function email_required_verification_page_access($account, $string) {
  global $user;
  
  // If the user is logged in, they can only verify themselves
  if ($user->uid && ($account->uid != $user->uid)) {
    return FALSE;
  }
  
  // We'll check for the existance of a hash, and the validity of it
  // in the page callback, because we don't want a 403 if the hash
  // has been purged
  return TRUE;
}

/**
 * Wrapper for user_pass_rehash()
 * 
 * Generate a a hash that will be used in the verification URL
 * 
 * @param $user
 *   A user object or ID
 * @return
 *   A hash
 */
function _email_required_hash($user) {
  return user_pass_rehash($user->pass, REQUEST_TIME, $user->name);
}

/**
 * Helper function to determine the user id to use
 * 
 * @param $user
 *   A user object, or user ID, or NULL if you want to use
 *   the current user.
 * @return
 *   A user ID
 */
function _email_required_get_uid($user = NULL) {
  if (!$user) {
    global $user;
  }
  return is_object($user) ? $user->uid : $user;
}

/**
 * Helper function to return email strings
 * 
 * @return
 *   An array containing the 'subject' and 'body for the 
 *   verification email
 */
function _email_required_email_text() {
  // Load the defaults
  $text = array(
    'subject' => variable_get('email_required_subject', NULL),
    'body' => variable_get('email_required_body', NULL),
  );
  
  // See if we need a subject
  if (!$text['subject']) {
    $text['subject'] = '[[site:name]] ' . t('Verify your email address');
  }
  
  // See if we need a body
  if (!$text['body']) {
    $text['body'] = '[user:name],' . "\n\n";
    $text['body'] .= t('You have requested to have your email address validated at !sitename.', array('!sitename' => '[site:name]')) . "\n\n";
    $text['body'] .= t('Click the link below to validate your email address:') . "\n\n";
    $text['body'] .= '!link' . "\n\n";
    $text['body'] .= t('You will never have to validate your email address again, unless you change it.') . "\n\n";
    $text['body'] .= '- [site:name]';
  }
  
  return $text;
}