You are here

captcha_after.module in CAPTCHA After 6

Same filename and directory in other branches
  1. 7 captcha_after.module

Show CAPTCHA protection on selected forms after specified number of unsuccessful form submit attempts has been made.

File

captcha_after.module
View source
<?php

/**
 * @file
 * Show CAPTCHA protection on selected forms after specified number of
 * unsuccessful form submit attempts has been made.
 */

/**
 * Implementation of hook_menu().
 */
function captcha_after_menu() {
  $items = array();
  $items['admin/user/captcha/captcha/after'] = array(
    'title' => 'CAPTCHA After',
    'file' => 'captcha_after.admin.inc',
    'description' => 'Change CAPTCHA After settings.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'captcha_after_settings',
    ),
    'access arguments' => array(
      'administer CAPTCHA settings',
    ),
    'type' => MENU_LOCAL_TASK,
    'weight' => 2,
  );
  return $items;
}

/**
 * Implementation of hook_form_alter().
 */
function captcha_after_form_alter(&$form, $form_state, $form_id) {

  // First check do we have captcha for this form.
  module_load_include('inc', 'captcha');
  $captcha_point = captcha_get_form_id_setting($form_id, TRUE);
  if (!$captcha_point || $captcha_point == '' || $captcha_point == 'none') {
    return;
  }

  // Load captcha after settings for this form.
  $ca_settings = captcha_after_get_forms_settings($form_id);

  // Check if captcha_after is enabled for this form.
  if (!$ca_settings['enable']) {
    return;
  }
  $form['#after_build'][] = 'captcha_after_form_after_build';
  $form['#validate'][] = 'captcha_after_form_validate';
  $form['#pre_render'][] = 'captcha_after_form_pre_render';
  $form['#submit'][] = 'captcha_after_form_submit';
}

/**
 * After build state of form. Decide should we skip captcha element validation.
 */
function captcha_after_form_after_build($form_element, &$form_state) {

  // Test should we skip captcha widget validation.
  if (!captcha_after_show_captcha($form_element['form_id']['#value']) && ($captcha =& captcha_after_get_captcha_element($form_element))) {
    $captcha['#validated'] = TRUE;
    $captcha['captcha_widgets']['captcha_response']['#validated'] = TRUE;
  }
  return $form_element;
}

/**
 * Form is validated.
 */
function captcha_after_form_validate($form, &$form_state) {

  // Form is submited - lets increment flood counter.
  flood_register_event('captcha_after_' . $form['form_id']['#value']);
}

/**
 * Form is submited.
 */
function captcha_after_form_submit($form, &$form_state) {
  $form_id = $form['form_id']['#value'];

  // We are in submit - reset flood submit threshold counter.
  captcha_after_db_flood_delete_events('captcha_after_' . $form_id);

  // Register correct form submission.
  flood_register_event($form_id);
}

/**
 * Form is in pre rendered state.
 */
function captcha_after_form_pre_render($form) {

  // Test should we hide captcha widget.
  if (!captcha_after_show_captcha($form['form_id']['#value']) && ($captcha =& captcha_after_get_captcha_element($form))) {
    $captcha =& captcha_after_get_captcha_element($form);
    $captcha['#access'] = FALSE;
  }
  return $form;
}

/**
 * Helper function.
 *
 * Removed hostname check from Drupal core's flood_is_allowed function.
 */
function captcha_after_global_flood_is_allowed($name, $threshold) {
  $number = db_result(db_query("SELECT COUNT(*) FROM {flood} WHERE event = '%s' AND timestamp > %d", $name, time() - 3600));
  return $number < $threshold ? TRUE : FALSE;
}

/**
 * Helper function for returning captcha_after forms configuration values.
 */
function captcha_after_get_forms_settings($form_id) {
  $form_settings = captcha_after_db_get_form($form_id);
  $settings['enable'] = FALSE;

  // If we do not have captcha_after settings for this form or captcha_after is
  // disabled for this form there is nothign to do.
  if (!$form_settings || !$form_settings['enable']) {
    return $settings;
  }
  $settings['enable'] = $form_settings['enable'];

  // Load global settings for all forms.
  $settings['submit_threshold'] = variable_get('captcha_after_submit_threshold', 3);
  $settings['flooding_threshold'] = variable_get('captcha_after_flooding_threshold', 3);
  $settings['global_flooding_threshold'] = variable_get('captcha_after_global_flooding_threshold', 1000);

  // Overide global settings where needed.
  if ($form_settings = captcha_after_db_get_form($form_id)) {
    foreach ($settings as $key => $value) {
      if (isset($form_settings['options'][$key]) && $form_settings['options'][$key] != '') {
        $settings[$key] = $form_settings['options'][$key];
      }
    }
  }
  return $settings;
}

/**
 * Helper function for finding captcha element in form array.
 *
 * @return boolean
 *   Reference to form captcha element or FALSE if if captcha element is not
 *   found in form.
 */
function &captcha_after_get_captcha_element(&$form) {
  if (isset($form['captcha'])) {
    return $form['captcha'];
  }
  if (isset($form['actions']['captcha'])) {
    return $form['actions']['captcha'];
  }

  // For node forms.
  if (isset($form['buttons']['captcha'])) {
    return $form['buttons']['captcha'];
  }

  // We didn't find captcha element on captcha enabled form - this is a valid
  // case for example when skip captcha protection is configured for some user
  // roles. And we are returing variable here because function needs to return
  // reference.
  $element = FALSE;
  return $element;
}

/**
 * Tests current form on captcha after threashold settings.
 * 
 * @param string $form_id
 *   Form id.
 * @return boolean
 *   If some of the threasholds are reached returns TRUE (captcha should be 
 *   shown). If not returns FALSE.
 */
function captcha_after_show_captcha($form_id) {

  // Get captcha_after settings for this form.
  $ca_settings = captcha_after_get_forms_settings($form_id);

  // Now we will test all captcha_after threshold settings.
  // If some settings is empty or 0 we will not take it into consideration.
  if (!empty($ca_settings['flooding_threshold']) && !flood_is_allowed($form_id, $ca_settings['flooding_threshold'])) {
    return TRUE;
  }
  if (!empty($ca_settings['submit_threshold']) && !flood_is_allowed('captcha_after_' . $form_id, $ca_settings['submit_threshold'])) {
    return TRUE;
  }
  if (!empty($ca_settings['global_flooding_threshold']) && !captcha_after_global_flood_is_allowed($form_id, $ca_settings['global_flooding_threshold'])) {
    return TRUE;
  }

  // All captcha_after tests are passed so we do not need to show captcha.
  return FALSE;
}

/**
 * Loads all captcha_after forms settings from database.
 * 
 * @return array
 *   Array of all captcha_after form settings.
 */
function captcha_after_db_get_forms() {
  $forms = array();
  $res = db_query('SELECT * FROM {captcha_after}');
  while ($form = db_fetch_array($res)) {
    $form['options'] = empty($form['options']) ? $form['options'] : unserialize($form['options']);
    $forms[$form['form_id']] = $form;
  }
  return $forms;
}

/**
 * Loads captcha_after form settings from database.
 * 
 * @param $form_id
 *   Form id value.
 * @return array
 *   Array of all captcha_after form settings if they exist in database or 
 *   FALSE.
 */
function captcha_after_db_get_form($form_id) {
  static $forms = array();
  if (!isset($forms[$form_id])) {
    $form = db_fetch_array(db_query("SELECT * FROM {captcha_after} WHERE form_id = '%s'", $form_id));
    if (!empty($form)) {
      $form['options'] = empty($form['options']) ? $form['options'] : unserialize($form['options']);
      $forms[$form_id] = $form;
    }
    else {
      $forms[$form_id] = FALSE;
    }
  }
  return $forms[$form_id];
}

/**
 * Saves captcha_after form settings in database.
 */
function captcha_after_db_set_form($form_id, array $options) {
  $enable = $options['enable'];
  unset($options['enable']);
  db_query("DELETE FROM {captcha_after} WHERE form_id = '%s'", $form_id);
  db_query("INSERT INTO {captcha_after} (form_id, enable, options) VALUES ('%s', %d, '%s')", $form_id, $enable, serialize($options));
}

/**
 * Delete flood events for a given name and user hostname.
 * 
 * @param string $name
 *   Registerd flood name.
 */
function captcha_after_db_flood_delete_events($name) {
  db_query("DELETE FROM {flood} WHERE event = '%s' AND hostname = '%s'", $name, ip_address());
}

Functions

Namesort descending Description
captcha_after_db_flood_delete_events Delete flood events for a given name and user hostname.
captcha_after_db_get_form Loads captcha_after form settings from database.
captcha_after_db_get_forms Loads all captcha_after forms settings from database.
captcha_after_db_set_form Saves captcha_after form settings in database.
captcha_after_form_after_build After build state of form. Decide should we skip captcha element validation.
captcha_after_form_alter Implementation of hook_form_alter().
captcha_after_form_pre_render Form is in pre rendered state.
captcha_after_form_submit Form is submited.
captcha_after_form_validate Form is validated.
captcha_after_get_captcha_element Helper function for finding captcha element in form array.
captcha_after_get_forms_settings Helper function for returning captcha_after forms configuration values.
captcha_after_global_flood_is_allowed Helper function.
captcha_after_menu Implementation of hook_menu().
captcha_after_show_captcha Tests current form on captcha after threashold settings.