You are here

botcha.module in BOTCHA Spam Prevention 6

BOTCHA - Spam Prevention It modifies forms by adding various botcha's.

File

botcha.module
View source
<?php

/**
 * @file
 * BOTCHA - Spam Prevention
 * It modifies forms by adding various botcha's.
 */
define('BOTCHA_LOG', 'BOTCHA');
define('BOTCHA_LOGLEVEL', variable_get('botcha_loglevel', 1));

/** BOTCHA_LOGLEVEL:
 *  0 - no log
 *  1 - log blocked/bad submissions only
 *  2 - also log why blocked
 *  3 - also log good submissions
 *  4 - also log when preparing forms
 *  5 - log extra submission details
 *  6 - misc development items
 */

/**
 * Implementation of hook_help().
 */
function botcha_help($path, $arg) {
  switch ($path) {
    case 'admin/help#botcha':
      $output = '<p>' . t('"BOTCHA" is an acronym for "BOT Computerized Heuristic Analysis". It is a method of protection from automated form submissions by performing analysis of submitted data that determines whether the user is a bot. The BOTCHA module is a tool to fight automated submission by malicious users that utilize automated form submission (e.g. for spamming) of for example comments forms, user registration forms, guestbook forms, etc. BOTCHA inserts elements into the desired forms that will not be shown to normal users. These elements have no impact on humans and require no puzzles to solve, but they are easy enough for automated scripts and spam bots to trip on.') . '</p>';
      $output .= '<p>' . t('Note that BOTCHA module interacts with page caching (see <a href="!performancesettings">performance settings</a>). Because BOTCHA elements should be unique for each generated form, the caching of the page it appears on is prevented. Make sure that these forms do not appear on too many pages or you will lose much caching efficiency. For example, if you put a BOTCHA on the user login block, which typically appears on each page for anonymous visitors, caching will practically be disabled. The comment submission forms are another example. In this case you should set the "%commentlocation" to "%separatepage" in the comment settings of the relevant <a href="!contenttypes">content types</a> for better caching efficiency.', array(
        '!performancesettings' => url('admin/settings/performance'),
        '%commentlocation' => t('Location of comment submission form'),
        '%separatepage' => t('Display on separate page'),
        '!contenttypes' => url('admin/content/types'),
      )) . '</p>';

      //      $output .= '<p>'. t('BOTCHA is a trademark of IVA2K.') .'</p>';
      return $output;
    case 'admin/user/botcha':
      module_load_include('inc', 'botcha');
      $output = '<p>' . t('A BOTCHA protection can be added to virtually each Drupal form. Some default forms are already provided in the form list and more can be added using form internal name.') . '</p>';
      $output .= '<p>' . t('All existing forms can be easily added and managed when the option "%adminlinks" is enabled.', array(
        '%adminlinks' => t('Add BOTCHA administration links to forms'),
      )) . '</p>';
      if (botcha_is_captcha_installed()) {
        $output .= '<p>' . t('Other forms will be added automatically based on CAPTCHA settings when the option "%usecaptcha" is enabled.', array(
          '%usecaptcha' => t('Add BOTCHA to forms selected for CAPTCHA'),
        )) . '</p>';
      }
      $output .= '<p>' . t('Forms served to users with the "%skipbotcha" <a href="@perm">permission</a> won\'t be protected. Be sure to grant this permission to the trusted users (e.g. site administrators). If you want to test a protected form, be sure to do it as a user without the "%skipbotcha" permission (e.g. as anonymous user).', array(
        '%skipbotcha' => t('skip BOTCHA'),
        '@perm' => url('admin/user/permissions', array(
          'fragment' => 'module-' . 'botcha',
        )),
      )) . '</p>';
      return $output;
  }
}

/**
 * Implements hook_menu().
 */
function botcha_menu() {
  $items = array();

  // main configuration page of BOTCHA module
  $items['admin/user/botcha'] = array(
    'title' => 'BOTCHA',
    'description' => 'Administer how and where BOTCHAs are used.',
    'file' => 'botcha.pages.inc',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'botcha_admin_settings',
    ),
    'access arguments' => array(
      'administer BOTCHA settings',
    ),
    'type' => MENU_NORMAL_ITEM,
  );
  $items['admin/user/botcha/botcha_point'] = array(
    'title' => 'BOTCHA point administration',
    'file' => 'botcha.pages.inc',
    'page callback' => 'botcha_point_admin',
    'page arguments' => array(
      4,
      5,
    ),
    'access arguments' => array(
      'administer BOTCHA settings',
    ),
    'type' => MENU_CALLBACK,
  );
  return $items;
}

/**
 * Implementation of hook_perm().
 */
function botcha_perm() {
  return array(
    'administer BOTCHA settings',
    'skip BOTCHA',
  );
}

/**
 * Implementation of hook_theme().
 */
function botcha_theme() {
  return array(
    'botcha_admin_settings_botcha_points' => array(
      'arguments' => array(
        'form' => NULL,
      ),
    ),
  );
}

/**
 * Implementation of hook_form_alter().
 *
 * This function adds BOTCHA protection to forms for untrusted users if needed and adds
 * BOTCHA administration links for site administrators if this option is enabled.
 */
function botcha_form_alter(&$form, &$form_state, $form_id) {
  $botcha = FALSE;
  if (!user_access('skip BOTCHA')) {

    // Visitor does not have permission to skip the BOTCHA
    module_load_include('inc', 'botcha');

    // Get BOTCHA type for given form_id.
    $botcha_point = botcha_get_form_id_setting($form_id);
    if ($botcha_point) {
      $botcha = !$botcha_point->botcha_type || $botcha_point->botcha_type == 'none' ? FALSE : $botcha_point->botcha_type;
    }
    elseif (variable_get('botcha_on_captcha_forms', TRUE) && botcha_is_captcha_installed()) {

      // Check captcha.module settings. If there is a captcha, there is a botcha.
      // Botcha's don't hurt humans, so we don't implement any logic to bypass
      // the Botcha.
      $captcha_point = botcha_get_captcha_point($form_id, TRUE);
      if ($captcha_point && $captcha_point != 'none') {
        $botcha = 'default';
      }
    }
  }
  if (variable_get('botcha_administration_mode', FALSE) && user_access('administer BOTCHA settings') && (arg(0) != 'admin' || variable_get('botcha_allow_on_admin_pages', FALSE) || $form_id == 'user_register')) {

    // Add BOTCHA administration tools.
    module_load_include('inc', 'botcha');
    $botcha_point = botcha_get_form_id_setting($form_id);

    // For administrators: show BOTCHA info and offer link to configure it
    $botcha_element = array(
      '#type' => 'fieldset',
      '#title' => t('BOTCHA'),
      '#collapsible' => TRUE,
      '#collapsed' => TRUE,
    );
    if ($botcha_point !== NULL && $botcha_point->botcha_type && $botcha_point->botcha_type != 'none') {
      $botcha_element['#title'] = t('BOTCHA: protection enabled (@type cookbook)', array(
        '@type' => $botcha_point->botcha_type,
      ));
      $botcha_element['#description'] = t('Untrusted users will have form %form_id protected by BOTCHA (!settings).', array(
        '%form_id' => $form_id,
        '!settings' => l(t('general BOTCHA settings'), 'admin/user/botcha'),
      ));
      $botcha_element['protection'] = array(
        '#type' => 'item',
        '#title' => t('Enabled protection'),
        '#value' => t('"@type" cookbook (!change, !disable)', array(
          '@type' => $botcha_point->botcha_type,
          '!change' => l(t('change'), "admin/user/botcha/botcha_point/{$form_id}", array(
            'query' => drupal_get_destination(),
            'html' => TRUE,
          )),
          '!disable' => l(t('disable'), "admin/user/botcha/botcha_point/{$form_id}/disable", array(
            'query' => drupal_get_destination(),
            'html' => TRUE,
          )),
        )),
      );
    }
    else {
      $botcha_element['#title'] = t('BOTCHA: no protection enabled');
      $botcha_element['add_botcha'] = array(
        '#value' => l(t('Add BOTCHA protection on form %form_id for untrusted users.', array(
          '%form_id' => $form_id,
        )), "admin/user/botcha/botcha_point/{$form_id}", array(
          'query' => drupal_get_destination(),
          'html' => TRUE,
        )),
      );
    }

    // Get placement in form and insert in form.
    $botcha_placement = _botcha_get_botcha_placement($form_id, $form);
    _botcha_insert_botcha_element($form, $botcha_placement, $botcha_element);
  }
  switch ($form_id) {

    /* UNUSED
        case 'user_admin_settings':
          // Insert BOTCHA settings into admin/user/settings
          module_load_include('inc', 'botcha', 'botcha.pages');
          $myform = _botcha_admin_settings($form_state);
          $form['register'] = array(
            '#type' => 'fieldset',
            '#title' => t('User Register settings'),
            '#description' => t('These options adjust User Register form (provided by <em>BOTCHA</em> module)'),
            '#collapsible' => TRUE,
            '#collapsed' => TRUE,
          );
          $form['register'] += $myform;
          $form['register']['#weight'] = $form['registration']['#weight'] + 0.001;
          foreach ($myform['#submit'] as $func) {
            $form['#submit'][] = $func;
          }
          break;
    */
    case 'user_register':
      if (FALSE === strpos($form['#action'], 'user/register')) {
        if (!variable_get('botcha_allow_on_admin_pages', FALSE)) {
          $botcha = FALSE;
        }

        // Only change the registration form. There is also 'user_register' form at /admin/user/user/create path, but we leave it alone.
      }
      break;
  }
  if ($botcha) {
    $form += array(
      '#input' => TRUE,
    );

    // '#input'=1 hacks FAPI to call #process handler on the form
    $form['#process'][] = 'botcha_fprocess';
    module_load_include('inc', 'botcha', 'botcha.botcha');
    botcha_form_alter_botcha($form, $form_state, $form_id, $botcha);
  }

  // Add a warning about caching on the Perfomance settings page.
  if ($form_id == 'system_performance_settings') {
    $form['page_cache']['cache']['#description'] .= '<p><strong class="error">' . t('Warning: the BOTCHA module will disable caching of pages that contain forms processed by BOTCHA.') . '</strong></p>';
  }
}

/**
 * Process form callback for BOTCHA form.
 * Using hooking mechanism that is a hack - we want to come in before #process,
 * but our _form_alter() is way too early (we want to let it use cache for all other
 * modules & data). Ideal would be to have #process callback
 * work on the form, but FAPI does not call it on forms, despite the
 * documentation stating that it does. In fact, D6 code needs '#input'=TRUE on
 * the element to get into calling #process callbacks. So we set '#input'=>TRUE
 * on the form when inserting our callback into form['#process'], and it forces
 * the FAPI to call that code path and we get our intercept. Works like a charm!
 */
function botcha_fprocess($element, $edit, &$form_state, $complete_form) {

  // Prevent caching of the page with BOTCHA elements.
  // This needs to be done even if the BOTCHA will be ommitted later:
  // other untrusted users should not get a cached page.
  global $conf;
  $conf['cache'] = CACHE_DISABLED;

  // This temporarily disables cache (for this page request)
  unset($element['#cache']);
  return $element;
}

/**
 * Implementation of hook_simpletest().
 */

// function botcha_simpletest() {
//   // Scan through botcha/tests directory for any .test files to tell SimpleTest module.
//   $tests = file_scan_directory(drupal_get_path('module', 'botcha') .'/tests', '\.test');
//   return array_keys($tests);
// }

/**
 * Implementation of hook_menu_alter().
 */
function botcha_menu_alter(&$items) {
}
function _botcha_variables($i18n = FALSE) {
  $ret = array();
  if (!$i18n) {
    $ret += array(
      'botcha_secret',
      'botcha_loglevel',
      'botcha_form_passed_counter',
      'botcha_form_blocked_counter',
    );
  }
  return $ret;
}
function _botcha_i18n() {
  $variables = _botcha_variables(TRUE);
  $i18n_variables = variable_get('i18n_variables', array());
  if (in_array($variables[0], $i18n_variables)) {
    return;
  }
  $i18n_variables = array_merge($i18n_variables, $variables);
  variable_set('i18n_variables', $i18n_variables);
}

// END

Functions

Namesort descending Description
botcha_form_alter Implementation of hook_form_alter().
botcha_fprocess Process form callback for BOTCHA form. Using hooking mechanism that is a hack - we want to come in before #process, but our _form_alter() is way too early (we want to let it use cache for all other modules & data). Ideal would be to have #process…
botcha_help Implementation of hook_help().
botcha_menu Implements hook_menu().
botcha_menu_alter Implementation of hook_menu_alter().
botcha_perm Implementation of hook_perm().
botcha_theme Implementation of hook_theme().
_botcha_i18n
_botcha_variables

Constants

Namesort descending Description
BOTCHA_LOG @file BOTCHA - Spam Prevention It modifies forms by adding various botcha's.
BOTCHA_LOGLEVEL