'. t('"CAPTCHA" is an acronym for "Completely Automated Public Turing test to tell Computers and Humans Apart". It is typically a challenge-response test to determine whether the user is human. The CAPTCHA module is a tool to fight automated submission by malicious users (spamming) of for example comments forms, user registration forms, guestbook forms, etc. You can extend the desired forms with an additional challenge, which should be easy for a human to solve correctly, but hard enough to keep automated scripts and spam bots out.') .'
'; $output .= ''. t('Note that the CAPTCHA module interacts with page caching (see performance settings). Because the challenge 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 CAPTCHA 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 for better caching efficiency.' , array( '!performancesettings' => url('admin/settings/performance'), '%commentlocation' => t('Location of comment submission form'), '%separatepage' => t('Display on separate page'), '!commentsettings' => url('admin/content/comment/settings'), ) ) .'
'; $output .= ''. t('CAPTCHA is a trademark of Carnegie Mellon University.') .'
'; return $output; case 'admin/user/captcha': case 'admin/user/captcha/captcha': case 'admin/user/captcha/captcha/settings': return t('A CAPTCHA can be added to virtually each Drupal form. Some default forms are already provided in the form list, but arbitrary forms can be easily added and managed when the option "%adminlinks" is enabled.
Users with the "%skipcaptcha" permission won\'t be offered a challenge. 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 "%skipcaptcha" permission (e.g. as anonymous user).
', array( '@perm' => url('admin/user/access'), '%adminlinks' => t('Add CAPTCHA adminstration links to forms'), '%skipcaptcha' => 'skip CAPTCHA', )); } } /** * Implementation of hook_menu(). */ function captcha_menu($may_cache) { $items = array(); if ($may_cache) { // main configuration page of the basic CAPTCHA module $items[] = array( 'path' => 'admin/user/captcha', 'title' => t('CAPTCHA'), 'description' => t('Administer how and where CAPTCHAs are used.'), 'callback' => 'captcha_admin', 'access' => user_access('administer CAPTCHA settings'), 'type' => MENU_NORMAL_ITEM, ); // the default local task (needed when other modules want to offer // alternative CAPTCHA types and their own configuration page as local task) $items[] = array( 'path' => 'admin/user/captcha/captcha', 'title' => t('CAPTCHA'), 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -20, ); $items[] = array( 'path' => 'admin/user/captcha/captcha/settings', 'title' => t('General settings'), 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => 0, ); $items[] = array( 'path' => 'admin/user/captcha/captcha/examples', 'title' => t('Examples'), 'description' => t('An overview of the available challenge types with examples.'), 'callback' => 'captcha_examples', 'type' => MENU_LOCAL_TASK, 'weight' => 5, ); } return $items; } /** * Implementation of hook_perm(). */ function captcha_perm() { return array('administer CAPTCHA settings', 'skip CAPTCHA'); } /** * Implementation of hook_requirements(). */ function captcha_requirements($phase) { $requirements = array(); $t = get_t(); if ($phase == 'runtime') { // show the wrong response counter in the status report $requirements['captcha_wrong_response_counter'] = array( 'title' => $t('CAPTCHA'), 'value' => $t('Already @counter blocked form submissions', array('@counter' => variable_get('captcha_wrong_response_counter', 0))), 'severity' => REQUIREMENT_INFO, ); } return $requirements; } /** * Return an array with the available CAPTCHA types, for use as options array * for a select form elements. * The array is an associative array mapping "$module/$type" to * "$module/$type" with $module the module name implementing the CAPTCHA * and $type the name of the CAPTCHA type. * (It also includes a 'none' => 'none' option) */ function _captcha_available_challenge_types() { $captcha_types['none'] = 'none'; foreach (module_implements('captcha') as $module) { $result = call_user_func_array($module .'_captcha', 'list'); if (is_array($result)) { foreach ($result as $type) { $captcha_types["$module/$type"] = "$type ($module)"; } } } return $captcha_types; } /** * Get the description which appears above the CAPTCHA in forms. * If the locale module is enabled, an optional language code can be given */ function _captcha_get_description($lang_code=NULL) { if (module_exists('locale')) { if ($lang_code == NULL) { global $locale; $lang_code = $locale; } $description = variable_get("captcha_description_$lang_code", t('This question is for testing whether you are a human visitor and to prevent automated spam submissions.')); } else { $description = variable_get('captcha_description', t('This question is for testing whether you are a human visitor and to prevent automated spam submissions.')); } return $description; } /** * General CAPTCHA settings handler. Main entry point for CAPTCHA management. * * If arguments are given: first argument is used as form_id, the second one * is interpreted as action (such as disable, delete and enable) to execute on * the form_id. * Otherwise: returns the general CAPTCHA configuration form. */ function captcha_admin($form_id='', $op='') { // if $form_id and action $op given: do the action if ($form_id) { switch ($op) { case 'disable': // disable the CAPTCHA for the form: set the module and type to NULL db_query("UPDATE {captcha_points} SET module = NULL, type = NULL WHERE form_id = '%s'", $form_id); drupal_set_message(t('Disabled CAPTCHA for form %form_id.', array('%form_id' => $form_id))); // goto the CAPTCHA adminstration or alternative destination if present in URI drupal_goto('admin/user/captcha'); break; case 'delete': db_query("DELETE FROM {captcha_points} WHERE form_id = '%s'", $form_id); drupal_set_message(t('Deleted CAPTCHA for form %form_id.', array('%form_id' => $form_id))); // goto the CAPTCHA adminstration or alternative destination if present in URI drupal_goto('admin/user/captcha'); break; case 'enable': db_query("DELETE FROM {captcha_points} WHERE form_id = '%s'", $form_id); db_query("INSERT INTO {captcha_points} (form_id, module, type) VALUES ('%s', NULL, NULL)", $form_id); // No drupal_goto() call because we have to go to the CAPTCHA adminstration // form and not a different destination if that would be present in the // URI. So we call this form explicitly. The destination will be preserved // so after completing the form, the user will still be redirected. return drupal_get_form('captcha_captcha_point_settings_form', $form_id); break; } // return edit form for individual form (aka CAPTCHA point) return drupal_get_form('captcha_captcha_point_settings_form', $form_id); } // no $form_id or legal action given: generate general CAPTCHA settings form return drupal_get_form('captcha_admin_settings'); } /** * Form builder function for the general CAPTCHA configuration */ function captcha_admin_settings() { // field for the CAPTCHA adminstration mode $form['captcha_administration_mode'] = array( '#type' => 'checkbox', '#title' => t('Add CAPTCHA adminstration links to forms'), '#default_value' => variable_get('captcha_administration_mode', FALSE), '#description' => t('This option is very helpful to enable/disable challenges on forms. When enabled, users with the "%admincaptcha" permission will see CAPTCHA administration links on all forms (except on administrative pages, which shouldn\'t be accessible to untrusted users in the first place). These links make it possible to enable a challenge of the desired type or disable it.', array('%admincaptcha' => 'administer CAPTCHA settings')), ); // field set with form_id -> CAPTCHA type configuration $form['captcha_types'] = array( '#type' => 'fieldset', '#title' => t('Challenge type per form'), '#description' => t('Select the challenge type you want for each of the listed forms (identified by their so called form_id\'s). You can easily add arbitrary forms with the help of the \'%CAPTCHA_admin_links\' option.', array('%CAPTCHA_admin_links'=>t('Add CAPTCHA adminstration links to forms'))), '#tree' => TRUE, '#collapsible' => TRUE, '#collapsed' => FALSE, '#theme' => 'captcha_admin_settings_captcha_points', ); // list all possible form_id's $captcha_types = _captcha_available_challenge_types(); $result = db_query("SELECT * FROM {captcha_points} ORDER BY form_id"); while ($captcha_point = db_fetch_object($result)) { $form['captcha_types'][$captcha_point->form_id]['form_id'] = array( '#value' => $captcha_point->form_id, ); // select widget for CAPTCHA type $form['captcha_types'][$captcha_point->form_id]['captcha_type'] = array( '#type' => 'select', '#default_value' => "{$captcha_point->module}/{$captcha_point->type}", '#options' => $captcha_types, ); // additional operations $form['captcha_types'][$captcha_point->form_id]['operations'] = array( '#value' => implode(", ", array( l(t('delete'), "admin/user/captcha/{$captcha_point->form_id}/delete"), )) ); } // field(s) for setting the additional CAPTCHA description if (module_exists('locale')) { $langs = locale_supported_languages(); $form['captcha_descriptions'] = array( '#type' => 'fieldset', '#title' => t('Challenge description'), '#description' => t('With this description you can explain the purpose of the challenge to the user.'), ); foreach ($langs['name'] as $lang_code => $lang_name) { $form['captcha_descriptions']["captcha_description_$lang_code"] = array( '#type' => 'textfield', '#title' => t('For language %lang_name (code %lang_code)', array('%lang_name' => $lang_name, '%lang_code' => $lang_code)), '#default_value' => _captcha_get_description($lang_code), '#maxlength' => 256, ); } } else { $form['captcha_description'] = array( '#type' => 'textfield', '#title' => t('Challenge description'), '#description' => t('With this description you can explain the purpose of the challenge to the user.'), '#default_value' => _captcha_get_description(), '#maxlength' => 256, ); } // field for CAPTCHA persistence $form['captcha_persistence'] = array( '#type' => 'radios', '#title' => t('Persistence'), '#default_value' => variable_get('captcha_persistence', CAPTCHA_PERSISTENCE_SHOW_ALWAYS), '#options' => array( CAPTCHA_PERSISTENCE_SHOW_ALWAYS => t('Always add a challenge.'), CAPTCHA_PERSISTENCE_SKIP_ONCE_SUCCESSFUL_PER_FORM => t('Omit challenges for a form once the user has successfully responded to a challenge for that form.'), CAPTCHA_PERSISTENCE_SKIP_ONCE_SUCCESSFUL => t('Omit challenges for all forms once the user has successfully responded to a challenge.'), ), '#description' => t('Define if challenges should be omitted during the rest of a session once the user successfully responses to a challenge.'), ); // option for logging wrong responses $form['captcha_log_wrong_responses'] = array( '#type' => 'checkbox', '#title' => t('Log wrong responses'), '#description' => t('Report information about wrong responses to the !watchdoglog.', array('!watchdoglog' => l('log', 'admin/logs/watchdog'))), '#default_value' => variable_get('captcha_log_wrong_responses', FALSE), ); // submit button $form['submit'] = array( '#type' => 'submit', '#value' => t('Submit'), ); return $form; } /** * Custom theme function for a table of (form_id -> CAPTCHA type) settings */ function theme_captcha_admin_settings_captcha_points($form) { foreach (element_children($form) as $key) { $row = array(); $row[] = drupal_render($form[$key]['form_id']); $row[] = drupal_render($form[$key]['captcha_type']); $row[] = drupal_render($form[$key]['operations']); $rows[] = $row; } $header = array('form_id', t('Challenge type (module)'), t('Operations')); $output = theme('table', $header, $rows); return $output; } /** * Submission function for captcha_admin_settings form */ function captcha_admin_settings_submit($form_id, $form_values) { if ($form_id == 'captcha_admin_settings') { variable_set('captcha_administration_mode', $form_values['captcha_administration_mode']); foreach ($form_values['captcha_types'] as $captcha_point_form_id => $data) { if ($data['captcha_type'] == 'none') { db_query("UPDATE {captcha_points} SET module = NULL, type = NULL WHERE form_id = '%s'", $captcha_point_form_id); } else { list($module, $type) = explode('/', $data['captcha_type']); db_query("UPDATE {captcha_points} SET module = '%s', type = '%s' WHERE form_id = '%s'", $module, $type, $captcha_point_form_id); } } // description stuff if (module_exists('locale')) { $langs = locale_supported_languages(); foreach ($langs['name'] as $lang_code => $lang_name) { variable_set("captcha_description_$lang_code", $form_values["captcha_description_$lang_code"]); } } else { variable_set('captcha_description', $form_values['captcha_description']); } variable_set('captcha_persistence', $form_values['captcha_persistence']); variable_set('captcha_log_wrong_responses', $form_values['captcha_log_wrong_responses']); drupal_set_message(t('The CAPTCHA settings were saved.'), 'status'); } } /** * form generating function for CAPTCHA point settings */ function captcha_captcha_point_settings_form($form_id) { $result = db_query("SELECT * FROM {captcha_points} WHERE form_id = '%s'", $form_id); $captcha_point = db_fetch_object($result); if ($captcha_point) { $form = array(); $form['captcha_point_form_id'] = array( '#type' => 'hidden', '#value' => $captcha_point->form_id, ); // select widget for CAPTCHA type $form['captcha_type'] = array( '#type' => 'select', '#title' => t('Select the challenge for @form_id', array('@form_id' => $form_id)), '#default_value' => "{$captcha_point->module}/{$captcha_point->type}", '#options' => _captcha_available_challenge_types(), ); // submit button $form['submit'] = array( '#type' => 'submit', '#value' => t('Submit'), ); // cancel button $form['cancel'] = array( '#type' => 'submit', '#value' => t('Cancel'), ); return $form; } else { // illegal form_id drupal_set_message(t('Unavailable form_id %form_id ', array('%form_id' => $form_id)), 'error'); // goto the CAPTCHA adminstration or alternative destination if present in URI drupal_goto('admin/user/captcha'); } } /** * submit function for captcha_captcha_point_settings_form */ function captcha_captcha_point_settings_form_submit($form_id, $form_values) { if ($form_id == 'captcha_captcha_point_settings_form' && $form_values['op'] == t('Submit')) { $captcha_point_form_id = $form_values['captcha_point_form_id']; $captcha_type = $form_values['captcha_type']; if ($captcha_type == 'none') { db_query("UPDATE {captcha_points} SET module = NULL, type = NULL WHERE form_id = '%s'", $captcha_point_form_id); } else { list($module, $type) = explode('/', $captcha_type); db_query("UPDATE {captcha_points} SET module = '%s', type = '%s' WHERE form_id = '%s'", $module, $type, $captcha_point_form_id); } drupal_set_message(t('Saved CAPTCHA settings.'), 'status'); } } /** * Helper function for checking if the CAPTCHA for the given form_id should * be skipped because of CAPTCHA persistence. */ function _captcha_persistence_skip($form_id) { $persistence = variable_get('captcha_persistence', CAPTCHA_PERSISTENCE_SHOW_ALWAYS); return ($persistence == CAPTCHA_PERSISTENCE_SKIP_ONCE_SUCCESSFUL && ($_SESSION['captcha']['success'] === TRUE)) || ($persistence == CAPTCHA_PERSISTENCE_SKIP_ONCE_SUCCESSFUL_PER_FORM && ($_SESSION['captcha'][$form_id]['success'] === TRUE)); } /** * Implementation of hook_form_alter(). * * This function adds a CAPTCHA to forms for untrusted users if needed and adds * CAPTCHA adminstration links for site adminstrators if this option is enabled. */ function captcha_form_alter($form_id, &$form) { if (!user_access('skip CAPTCHA')) { // Visitor does not have permission to skip the CAPTCHA // Get CAPTCHA type and module for this form. Return if no CAPTCHA was set. $result = db_query("SELECT module, type FROM {captcha_points} WHERE form_id = '%s'", $form_id); if (!$result) { return; } $captcha_point = db_fetch_object($result); if (!$captcha_point || !$captcha_point->type) { return; } // Prevent caching of the page with this CAPTCHA enabled form. // This needs to be done even if the CAPTCHA will be skipped (because of // persistence): other untrusted users should not get a cached page when // the current untrusted user can skip the current CAPTCHA. global $conf; $conf['cache'] = FALSE; // Do not present CAPTCHA if not CAPTCHA-persistent and user has already solved a CAPTCHA for this form if (_captcha_persistence_skip($form_id)) { return; } // Generate a CAPTCHA and its solution $captcha = module_invoke($captcha_point->module, 'captcha', 'generate', $captcha_point->type); if (!$captcha) { //The selected module returned nothing, maybe it is disabled or it's wrong, we should watchdog that and then quit. watchdog('CAPTCHA', t('CAPTCHA problem: hook_captcha() of module %module returned nothing when trying to retrieve challenge type %type for form %form_id.', array('%type' => $captcha_point->type, '%module' => $captcha_point->module, '%form_id' => $form_id)), WATCHDOG_ERROR); return; } // Add a CAPTCHA part to the form (depends on value of captcha_description) $captcha_description = _captcha_get_description(); if ($captcha_description) { // $captcha_description is not empty: CAPTCHA part is a fieldset with description $form['captcha'] = array( '#type' => 'fieldset', '#title' => t('CAPTCHA'), '#description' => $captcha_description, '#attributes' => array('class' => 'captcha') ); } else { // $captcha_description is empty: CAPTCHA part is an empty markup form element $form['captcha'] = array( '#type' => 'markup', '#prefix' => '