Commit 7d2f2bc7 authored by wundo's avatar wundo
Browse files

Issue #1949682 by jeqq, m1r1k, podarok, elachlan: Latest patch from the issue

parent c59b4860
File mode changed from 100644 to 100755
......@@ -5,17 +5,20 @@
* Functionality and helper functions for CAPTCHA administration.
*/
/**
* Return an array with the available CAPTCHA types, for use as options array
* for a select form elements.
/**
* Return an array with the available CAPTCHA types.
*
* For use as options array for a select form elements.
*
* @param $add_special_options if true: also add a 'none' and 'default' option
* @param bool $add_special_options
* If true: also add a 'none' and 'default' option.
*
* @return an associative array mapping "$module/$type" to
* "$type (from module $module)" with $module the module name implementing the CAPTCHA
* and $type the name of the CAPTCHA type.
* @return array
* An associative array mapping "$module/$type" to
* "$type (from module $module)" with $module the module name
* implementing the CAPTCHA and $type the name of the CAPTCHA type.
*/
function _captcha_available_challenge_types($add_special_options=TRUE) {
function _captcha_available_challenge_types($add_special_options = TRUE) {
$captcha_types = array();
if ($add_special_options) {
$captcha_types['none'] = t('- No challenge -');
......@@ -35,31 +38,34 @@ function _captcha_available_challenge_types($add_special_options=TRUE) {
}
/**
* Central handler for CAPTCHA point administration (adding, disabling, deleting)
* Central handler for CAPTCHA point administration.
*/
function captcha_point_admin($captcha_point_form_id=NULL, $op=NULL) {
function captcha_point_admin($captcha_point_form_id = NULL, $op = NULL) {
module_load_include('inc', 'captcha');
// if $captcha_point_form_id and action $op given: do the action
// If $captcha_point_form_id and action $op given: do the action.
if ($captcha_point_form_id) {
switch ($op) {
case 'disable':
return drupal_get_form('captcha_point_disable_confirm', $captcha_point_form_id, FALSE);
case 'delete':
return drupal_get_form('captcha_point_disable_confirm', $captcha_point_form_id, TRUE);
}
// return edit form for CAPTCHA point
return drupal_get_form('captcha_point_admin_form', $captcha_point_form_id);
}
// return add form for CAPTCHA point
return drupal_get_form('captcha_point_admin_form');
}
function captcha_point_admin_form($form, $form_state, $captcha_point_form_id=NULL) {
/**
* CAPTCHA settings form.
*/
function captcha_point_admin_form($form, $form_state, $captcha_point_form_id = NULL) {
$form = array();
$default_captcha_type = 'none';
if (isset($captcha_point_form_id)) {
// use given CAPTCHA point form_id
$form['captcha_point_form_id'] = array(
'#type' => 'textfield',
'#title' => t('Form ID'),
......@@ -73,14 +79,13 @@ function captcha_point_admin_form($form, $form_state, $captcha_point_form_id=NUL
}
}
else {
// textfield for CAPTCHA point form_id
$form['captcha_point_form_id'] = array(
'#type' => 'textfield',
'#title' => t('Form ID'),
'#description' => t('The Drupal form_id of the form to add the CAPTCHA to.'),
);
}
// select widget for CAPTCHA type
$form['captcha_type'] = array(
'#type' => 'select',
'#title' => t('Challenge type'),
......@@ -88,9 +93,9 @@ function captcha_point_admin_form($form, $form_state, $captcha_point_form_id=NUL
'#default_value' => $default_captcha_type,
'#options' => _captcha_available_challenge_types(),
);
// redirect to general CAPTCHA settings page after submission
// Redirect to general CAPTCHA settings page after submission.
$form['#redirect'] = 'admin/config/people/captcha';
// submit button
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array(
'#type' => 'submit',
......@@ -99,9 +104,8 @@ function captcha_point_admin_form($form, $form_state, $captcha_point_form_id=NUL
return $form;
}
/**
* validation function for captcha_point_admin_form
* Validation function for captcha_point_admin_form.
*/
function captcha_point_admin_form_validate($form, $form_state) {
if (!preg_match('/^[a-z0-9_]+$/', $form_state['values']['captcha_point_form_id'])) {
......@@ -109,7 +113,6 @@ function captcha_point_admin_form_validate($form, $form_state) {
}
}
/**
* Submit function for captcha_point_admin_form.
*/
......@@ -121,7 +124,7 @@ function captcha_point_admin_form_submit($form, $form_state) {
}
/**
* Confirm dialog for disabling/deleting a CAPTCHA point
* Confirm dialog for disabling/deleting a CAPTCHA point.
*/
function captcha_point_disable_confirm($form, &$form_state, $captcha_point_form_id, $delete) {
$form = array();
......@@ -173,7 +176,7 @@ function _captcha_generate_example_challenge($module, $type) {
}
/**
* Funtion for generating a page with CAPTCHA examples.
* Function for generating a page with CAPTCHA examples.
*
* If the arguments $module and $challenge are not set, generate a list with
* examples of the available CAPTCHA types.
......@@ -186,12 +189,12 @@ function captcha_examples($form, $form_state, $module, $challenge) {
$form = array();
if ($module && $challenge) {
// Generate 10 example challenges.
for ($i=0; $i<10; $i++) {
for ($i = 0; $i < 10; $i++) {
$form["challenge_{$i}"] = _captcha_generate_example_challenge($module, $challenge);
}
}
else {
// generate a list with examples of the available CAPTCHA types
// Generate a list with examples of the available CAPTCHA types.
$form['info'] = array(
'#markup' => t('This page gives an overview of all available challenge types, generated with their current settings.'),
);
......
......@@ -5,92 +5,59 @@
* General CAPTCHA functionality and helper functions.
*/
use Drupal\Component\Utility\Xss;
use Drupal\captcha\Entity\CaptchaPoint;
/**
* Helper function for adding/updating a CAPTCHA point.
*
* @param $form_id the form ID to configure.
* @param captcha_type the setting for the given form_id, can be:
* @param string $form_id
* the form ID to configure.
* @param string $captcha_type
* The setting for the given form_id, can be:
* - 'none' to disable CAPTCHA,
* - 'default' to use the default challenge type
* - NULL to remove the entry for the CAPTCHA type
* - something of the form 'image_captcha/Image'
* - an object with attributes $captcha_type->module and $captcha_type->captcha_type
* @return nothing
* - an object with attributes $captcha_type->module
* and $captcha_type->captcha_type.
*/
function captcha_set_form_id_setting($form_id, $captcha_type) {
// Handle 'none'.
if ($captcha_type == 'none') {
db_merge('captcha_points')
->key(array('form_id' => $form_id))
->fields(array('module' => NULL, 'captcha_type' => NULL))
->execute();
}
// Handle 'default'.
elseif ($captcha_type == 'default') {
db_merge('captcha_points')
->key(array('form_id' => $form_id))
->fields(array('module' => NULL, 'captcha_type' => 'default'))
->execute();
}
// Handle NULL.
elseif ($captcha_type == NULL) {
db_delete('captcha_points')->condition('form_id', $form_id)->execute();
}
// Handle a captcha_type object.
elseif (is_object($captcha_type) && isset($captcha_type->module) && isset($captcha_type->captcha_type)) {
db_merge('captcha_points')
->key(array('form_id' => $form_id))
->fields(array('module' => $captcha_type->module, 'captcha_type' => $captcha_type->captcha_type))
->execute();
}
// Handle a captcha_type string.
elseif (is_string($captcha_type) && substr_count($captcha_type, '/') == 1) {
list($module, $type) = explode('/', $captcha_type);
db_merge('captcha_points')
->key(array('form_id' => $form_id))
->fields(array('module' => $module, 'captcha_type' => $type))
->execute();
/* @var CaptchaPoint $captcha_point */
$captcha_point = CaptchaPoint::load($form_id);
if ($captcha_point) {
$captcha_point->setCaptchaType($captcha_type);
}
else {
drupal_set_message(t('Failed to set a CAPTCHA type for form %form_id: could not interpret value "@captcha_type"',
array('%form_id' => $form_id, '@captcha_type' => (string)$captcha_type)), 'warning');
$captcha_point = new CaptchaPoint(array('formId' => $form_id, 'captchaType' => $captcha_type), 'captcha_point');
}
$captcha_point->save();
}
/**
* Get the CAPTCHA setting for a given form_id.
*
* @param $form_id the form_id to query for
* @param $symbolic flag to return as (symbolic) strings instead of object.
* @param string $form_id
* The form_id to query for.
* @param bool $symbolic
* Flag to return as (symbolic) strings instead of object.
*
* @return NULL if no setting is known
* or a captcha_point object with fields 'module' and 'captcha_type'.
* If argument $symbolic is true, returns (symbolic) as 'none', 'default'
* @return NULL|CaptchaPoint
* NULL if no setting is known
* captcha point object with fields 'module' and 'captcha_type'.
* If argument $symbolic is true, returns 'none', 'default'
* or in the form 'captcha/Math'.
*/
function captcha_get_form_id_setting($form_id, $symbolic=FALSE) {
$result = db_query("SELECT module, captcha_type FROM {captcha_points} WHERE form_id = :form_id",
array(':form_id' => $form_id));
$captcha_point = $result->fetchObject();
if (!$captcha_point) {
$captcha_point = NULL;
}
elseif ($captcha_point->captcha_type == 'default') {
if (!$symbolic) {
list($module, $type) = explode('/', config('captcha.settings')->get('captcha_default_challenge'));
$captcha_point->module = $module;
$captcha_point->captcha_type = $type;
}
else {
$captcha_point = 'default';
}
}
elseif ($captcha_point->module == NULL && $captcha_point->captcha_type == NULL && $symbolic) {
$captcha_point = 'none';
}
elseif ($symbolic) {
$captcha_point = $captcha_point->module . '/' . $captcha_point->captcha_type;
function captcha_get_form_id_setting($form_id, $symbolic = FALSE) {
/* @var CaptchaPoint $captchaPoint */
$captcha_point = CaptchaPoint::load($form_id);
if ($symbolic) {
$captcha_point = $captcha_point->getCaptchaType();
}
return $captcha_point;
}
......@@ -98,18 +65,23 @@ function captcha_get_form_id_setting($form_id, $symbolic=FALSE) {
/**
* Helper function for generating a new CAPTCHA session.
*
* @param $form_id the form_id of the form to add a CAPTCHA to.
* @param $status the initial status of the CAPTHCA session.
* @return the session ID of the new CAPTCHA session.
* @param string $form_id
* The form_id of the form to add a CAPTCHA to.
* @param int $status
* The initial status of the CAPTHCA session.
*
* @return string
* The session ID of the new CAPTCHA session.
*/
function _captcha_generate_captcha_session($form_id=NULL, $status=CAPTCHA_STATUS_UNSOLVED) {
global $user;
function _captcha_generate_captcha_session($form_id = NULL, $status = CAPTCHA_STATUS_UNSOLVED) {
$user = \Drupal::currentUser();
// Initialize solution with random data.
$solution = md5(mt_rand());
// Insert an entry and thankfully receive the value of the autoincrement field 'csid'.
// Insert an entry and thankfully receive the value
// of the autoincrement field 'csid'.
$captcha_sid = db_insert('captcha_sessions')
->fields(array(
'uid' => $user->uid,
'uid' => $user->id(),
'sid' => session_id(),
'ip_address' => Drupal::request()->getClientIp(),
'timestamp' => REQUEST_TIME,
......@@ -125,8 +97,10 @@ function _captcha_generate_captcha_session($form_id=NULL, $status=CAPTCHA_STATUS
/**
* Helper function for updating the solution in the CAPTCHA session table.
*
* @param $captcha_sid the CAPTCHA session ID to update.
* @param $solution the new solution to associate with the given CAPTCHA session.
* @param string $captcha_sid
* The CAPTCHA session ID to update.
* @param string $solution
* The new solution to associate with the given CAPTCHA session.
*/
function _captcha_update_captcha_session($captcha_sid, $solution) {
db_update('captcha_sessions')
......@@ -139,13 +113,14 @@ function _captcha_update_captcha_session($captcha_sid, $solution) {
}
/**
* Helper function for checking if CAPTCHA is required for user,
* based on the CAPTCHA persistence setting, the CAPTCHA session ID and
* user session info.
* Helper function for checking if CAPTCHA is required for user.
*
* Based on the CAPTCHA persistence setting, the CAPTCHA session
* ID and user session info.
*/
function _captcha_required_for_user($captcha_sid, $form_id) {
// Get the CAPTCHA persistence setting.
$captcha_persistence = config('captcha.settings')->get('captcha_persistence');
$captcha_persistence = \Drupal::config('captcha.settings')->get('captcha_persistence');
// First check: should we always add a CAPTCHA?
if ($captcha_persistence == CAPTCHA_PERSISTENCE_SHOW_ALWAYS) {
......@@ -154,20 +129,23 @@ function _captcha_required_for_user($captcha_sid, $form_id) {
// Get the status of the current CAPTCHA session.
$captcha_session_status = db_query('SELECT status FROM {captcha_sessions} WHERE csid = :csid', array(':csid' => $captcha_sid))->fetchField();
// Second check: if the current session is already solved: omit further CAPTCHAs.
// Second check: if the current session is already
// solved: omit further CAPTCHAs.
if ($captcha_session_status == CAPTCHA_STATUS_SOLVED) {
return FALSE;
}
// Third check: look at the persistence level (per form instance, per form or per user).
// Third check: look at the persistence level
// (per form instance, per form or per user).
if ($captcha_persistence == CAPTCHA_PERSISTENCE_SKIP_ONCE_SUCCESSFUL_PER_FORM_INSTANCE) {
return TRUE;
}
else {
$captcha_success_form_ids = isset($_SESSION['captcha_success_form_ids']) ? (array)($_SESSION['captcha_success_form_ids']) : array();
$captcha_success_form_ids = isset($_SESSION['captcha_success_form_ids']) ? (array) ($_SESSION['captcha_success_form_ids']) : array();
switch ($captcha_persistence) {
case CAPTCHA_PERSISTENCE_SKIP_ONCE_SUCCESSFUL:
return (count($captcha_success_form_ids) == 0);
case CAPTCHA_PERSISTENCE_SKIP_ONCE_SUCCESSFUL_PER_FORM_TYPE:
return !isset($captcha_success_form_ids[$form_id]);
}
......@@ -179,56 +157,65 @@ function _captcha_required_for_user($captcha_sid, $form_id) {
/**
* Get the CAPTCHA description as configured on the general CAPTCHA
* settings page.
* Get the CAPTCHA description.
*
* If the locale module is enabled, the description will be returned
* for the current language the page is rendered for. This language
* can optionally been overriden with the $lang_code argument.
*
* @param $lang_code an optional language code to get the descripion for.
* @return a string with (localized) CAPTCHA description.
* @param string $langcode
* An optional language code to get the description for.
*
* @return string
* Localized CAPTCHA description.
*/
function _captcha_get_description($lang_code=NULL) {
function _captcha_get_description($langcode = NULL) {
// If no language code is given: use the language of the current page.
$language = Drupal::languageManager()->getLanguage();
$id = isset($lang_code) ? $lang_code : $language->id;
if ($langcode) {
/* @var \Drupal\core\Language\Language $language */
$language = \Drupal::languageManager()->getLanguage($langcode);
}
else {
/* @var \Drupal\core\Language\Language $language */
$language = \Drupal::languageManager()->getCurrentLanguage();
}
$id = $language->getId();
// The hardcoded but localizable default.
$default = t('This question is for testing whether or not you are a human visitor and to prevent automated spam submissions.', array(), array('id' => $lang_code));
// Look up the configured CAPTCHA description or fall back on the (localized) default.
$default = t('This question is for testing whether or not you are a human visitor and to prevent automated spam submissions.', array(), array('id' => $id));
// Look up the configured CAPTCHA description or
// fall back on the (localized) default.
if (Drupal::moduleHandler()->moduleExists('locale')) {
$description = config('captcha.settings')->get("captcha_description_$lang_code", $default);
$description = \Drupal::config('captcha.settings')->get("captcha_description_$id", $default);
}
else {
$description = config('captcha.settings')->get('captcha_description', $default);
$description = \Drupal::config('captcha.settings')->get('captcha_description', $default);
}
return filter_xss_admin($description);
return Xss::filter($description);
}
/**
* Parse or interpret the given captcha_type.
* @param $captcha_type string representation of the CAPTCHA type,
* e.g. 'default', 'none', 'captcha/Math', 'image_captcha/Image'
* @return list($captcha_module, $captcha_type)
*
* @param string $captcha_type
* representation of the CAPTCHA type,
* e.g. 'default', 'none', 'captcha/Math', 'image_captcha/Image'.
*
* @return array
* list($captcha_module, $captcha_type).
*/
function _captcha_parse_captcha_type($captcha_type) {
if ($captcha_type == 'none') {
return array(NULL, NULL);
}
if ($captcha_type == 'default') {
$captcha_type = config('captcha.settings')->get('captcha_default_challenge', 'captcha/Math');
$captcha_type = \Drupal::config('captcha.settings')->get('captcha_default_challenge', 'captcha/Math');
}
return explode('/', $captcha_type);
}
/**
* Helper function to get placement information for a given form_id.
* @param $form_id the form_id to get the placement information for.
* @param $form if a form corresponding to the given form_id, if there
* is no placement info for the given form_id, this form is examined to
* guess the placement.
* @return placement info array (@see _captcha_insert_captcha_element() for more
* info about the fields 'path', 'key' and 'weight'.
*/
function _captcha_get_captcha_placement($form_id, $form) {
// Get CAPTCHA placement map from cache. Two levels of cache:
......@@ -237,14 +224,16 @@ function _captcha_get_captcha_placement($form_id, $form) {
// Try first level cache.
if ($placement_map === NULL) {
// If first level cache missed: try second level cache.
$placement_map = Drupal::state()->get('captcha_placement_map_cache', NULL);
$placement_map = \Drupal::state()->get('captcha_placement_map_cache', NULL);
if ($placement_map === NULL) {
// If second level cache missed: initialize the placement map
// and let other modules hook into this with the hook_captcha_placement_map hook.
// By default however, probably all Drupal core forms are already correctly
// handled with the best effort guess based on the 'actions' element (see below).
$placement_map = module_invoke_all('captcha_placement_map');
// and let other modules hook into this with the
// hook_captcha_placement_map hook.
// By default however, probably all Drupal core forms
// are already correctly handled with the best effort guess
// based on the 'actions' element (see below).
$placement_map = \Drupal::moduleHandler()->invokeAll('captcha_placement_map');
}
}
......@@ -252,9 +241,11 @@ function _captcha_get_captcha_placement($form_id, $form) {
if (array_key_exists($form_id, $placement_map)) {
$placement = $placement_map[$form_id];
}
// If no placement info is available in placement map: make a best effort guess.
// If no placement info is available in placement map:
// make a best effort guess.
else {
// If there is an "actions" button group, a good placement is just before that.
// If there is an "actions" button group, a good placement
// is just before that.
if (isset($form['actions']) && isset($form['actions']['#type']) && $form['actions']['#type'] === 'actions') {
$placement = array(
'path' => array(),
......@@ -279,7 +270,7 @@ function _captcha_get_captcha_placement($form_id, $form) {
// Store calculated placement in cache.
$placement_map[$form_id] = $placement;
Drupal::state()->set('captcha_placement_map_cache', $placement_map);
\Drupal::state()->set('captcha_placement_map_cache', $placement_map);
}
return $placement;
......@@ -288,14 +279,18 @@ function _captcha_get_captcha_placement($form_id, $form) {
/**
* Helper function for searching the buttons in a form.
*
* @param $form the form to search button elements in
* @return an array of paths to the buttons.
* @param array $form
* The form to search button elements in.
*
* @return array
* Array of paths to the buttons.
* A path is an array of keys leading to the button, the last
* item in the path is the weight of the button element
* (or NULL if undefined).
*/
function _captcha_search_buttons($form) {
function _captcha_search_buttons(array $form) {
$buttons = array();
foreach (element_children($form) as $key) {
// Look for submit or button type elements.
if (isset($form[$key]['#type']) && ($form[$key]['#type'] == 'submit' || $form[$key]['#type'] == 'button')) {
......@@ -313,25 +308,30 @@ function _captcha_search_buttons($form) {
$buttons[] = $b;
}
}
return $buttons;
}
/**
* Helper function to insert a CAPTCHA element in a form before a given form element.
* @param $form the form to add the CAPTCHA element to.
* @param $placement information where the CAPTCHA element should be inserted.
* Helper function to insert a CAPTCHA element before a given form element.
*
* @param array $form
* the form to add the CAPTCHA element to.
* @param array $placement
* information where the CAPTCHA element should be inserted.
* $placement should be an associative array with fields:
* - 'path': path (array of path items) of the container in the form where the
* CAPTCHA element should be inserted.
* - 'path': path (array of path items) of the container in
* the form where the CAPTCHA element should be inserted.
* - 'key': the key of the element before which the CAPTCHA element
* should be inserted. If the field 'key' is undefined or NULL, the CAPTCHA will
* just be appended in the container.
* - 'weight': if 'key' is not NULL: should be the weight of the element defined by 'key'.
* If 'key' is NULL and weight is not NULL: set the weight property of the CAPTCHA element
* to this value.
* @param $captcha_element the CAPTCHA element to insert.
* should be inserted. If the field 'key' is undefined or NULL,
* the CAPTCHA willjust be appended in the container.
* - 'weight': if 'key' is not NULL: should be the weight of the
* element defined by 'key'. If 'key' is NULL and weight is not NULL:
* set the weight property of the CAPTCHA element to this value.
* @param array $captcha_element
* the CAPTCHA element to insert.
*/
function _captcha_insert_captcha_element(&$form, $placement, $captcha_element) {
function _captcha_insert_captcha_element(array &$form, array $placement, array $captcha_element) {
// Get path, target and target weight or use defaults if not available.
$target_key = isset($placement['key']) ? $placement['key'] : NULL;
$target_weight = isset($placement['weight']) ? $placement['weight'] : NULL;
......@@ -351,34 +351,36 @@ function _captcha_insert_captcha_element(&$form, $placement, $captcha_element) {
}
}
// If no target is available: just append the CAPTCHA element to the container.
// If no target is available: just append the CAPTCHA element
// to the container.
if ($target_key == NULL || !array_key_exists($target_key, $form_stepper)) {
// Optionally, set weight of CAPTCHA element.
if ($target_weight != NULL) {
$captcha_element['#weight'] = $target_weight;
}
$form_stepper['captcha'] = $captcha_element;
$form_stepper['captcha'] = $captcha_element;
}
// If there is a target available: make sure the CAPTCHA element comes right before it.
// If there is a target available: make sure the CAPTCHA element
// comes right before it.
else {
// If target has a weight: set weight of CAPTCHA element a bit smaller
// and just append the CAPTCHA: sorting will fix the ordering anyway.
if ($target_weight != NULL) {
$captcha_element['#weight'] = $target_weight - .1;
$form_stepper['captcha'] = $captcha_element;
$form_stepper['captcha'] = $captcha_element;
}
else {
// If we can't play with weights: insert the CAPTCHA element at the right position.
// Because PHP lacks a function for this (array_splice() comes close,
// but it does not preserve the key of the inserted element), we do it by hand:
// chop of the end, append the CAPTCHA element and put the end back.
// If we can't play with weights: insert the CAPTCHA element
// at the right position. Because PHP lacks a function for
// this (array_splice() comes close, but it does not preserve
// the key of the inserted element), we do it by hand: chop of
// the end, append the CAPTCHA element and put the end back.
$offset = array_search($target_key, array_keys($form_stepper));
$end = array_splice($form_stepper, $offset);
$form_stepper['captcha'] = $captcha_element;
$form_stepper['captcha'] = $captcha_element;
foreach ($end as $k => $v) {
$form_stepper[$k] = $v;
}
}
}
}
File mode changed from 100644 to 100755
......@@ -5,35 +5,12 @@
* Install, update and uninstall functions for the CAPTCHA module.
*/
use Drupal\captcha\Entity\CaptchaPoint;
/**
* Implementation of hook_schema().
* Implements hook_schema().
*/
function captcha_schema() {
// Table for positions and types of the challenges.
$schema['captcha_points'] = array(
'description' => 'This table describes which challenges should be added to which forms.',
'fields' => array(
'form_id' => array(
'description' => 'The form_id of the form to add a CAPTCHA to.',
'type' => 'varchar',
'length' => 128,
'not null' => TRUE,
'default' => '',
),
'module' => array(
'description' => 'The module that provides the challenge.',
'type' => 'varchar',
'length' => 64,
),
'captcha_type' => array(
'description' => 'The challenge type to use.',
'type' => 'varchar',
'length' => 64,
),