| 1 |
<?php
|
| 2 |
// $Id: captcha.inc,v 1.10 2009/06/19 13:04:00 soxofaan Exp $
|
| 3 |
|
| 4 |
/**
|
| 5 |
* Helper function for adding/updating a CAPTCHA point.
|
| 6 |
*
|
| 7 |
* @param $form_id the form ID to configure.
|
| 8 |
* @param captcha_type the setting for the given form_id, can be:
|
| 9 |
* - 'none' to disable CAPTCHA,
|
| 10 |
* - 'default' to use the default challenge type
|
| 11 |
* - NULL to remove the entry for the CAPTCHA type
|
| 12 |
* - something of the form 'image_captcha/Image'
|
| 13 |
* - an object with attributes $captcha_type->module and $captcha_type->type
|
| 14 |
* @return nothing
|
| 15 |
*/
|
| 16 |
function captcha_set_form_id_setting($form_id, $captcha_type) {
|
| 17 |
if ($captcha_type == 'none') {
|
| 18 |
db_query("DELETE FROM {captcha_points} WHERE form_id = '%s'", $form_id);
|
| 19 |
db_query("INSERT INTO {captcha_points} (form_id, module, type) VALUES ('%s', NULL, NULL)", $form_id);
|
| 20 |
}
|
| 21 |
elseif ($captcha_type == 'default') {
|
| 22 |
db_query("DELETE FROM {captcha_points} WHERE form_id = '%s'", $form_id);
|
| 23 |
db_query("INSERT INTO {captcha_points} (form_id, module, type) VALUES ('%s', NULL, '%s')", $form_id, 'default');
|
| 24 |
}
|
| 25 |
elseif ($captcha_type == NULL) {
|
| 26 |
db_query("DELETE FROM {captcha_points} WHERE form_id = '%s'", $form_id);
|
| 27 |
}
|
| 28 |
elseif (is_object($captcha_type) && isset($captcha_type->module) && isset($captcha_type->type)) {
|
| 29 |
db_query("DELETE FROM {captcha_points} WHERE form_id = '%s'", $form_id);
|
| 30 |
db_query("INSERT INTO {captcha_points} (form_id, module, type) VALUES ('%s', '%s', '%s')", $form_id, $captcha_type->module, $captcha_type->type);
|
| 31 |
}
|
| 32 |
elseif (is_string($captcha_type) && substr_count($captcha_type, '/') == 1) {
|
| 33 |
list($module, $type) = explode('/', $captcha_type);
|
| 34 |
db_query("DELETE FROM {captcha_points} WHERE form_id = '%s'", $form_id);
|
| 35 |
db_query("INSERT INTO {captcha_points} (form_id, module, type) VALUES ('%s', '%s', '%s')", $form_id, $module, $type);
|
| 36 |
}
|
| 37 |
else {
|
| 38 |
drupal_set_message(t('Failed to set a CAPTCHA type for form %form_id: could not interpret value "@captcha_type"',
|
| 39 |
array('%form_id' => $form_id, '@captcha_type' => (string)$captcha_type)), 'warning');
|
| 40 |
}
|
| 41 |
}
|
| 42 |
|
| 43 |
/**
|
| 44 |
* Get the CAPTCHA setting for a given form_id.
|
| 45 |
*
|
| 46 |
* @param $form_id the form_id to query for
|
| 47 |
* @param $symbolic flag to return as (symbolic) strings instead of object.
|
| 48 |
*
|
| 49 |
* @return NULL if no setting is known
|
| 50 |
* or a captcha_point object with fields 'module' and 'type'.
|
| 51 |
* If argument $symbolic is true, returns (symbolic) as 'none', 'default'
|
| 52 |
* or in the form 'captcha/Math'.
|
| 53 |
*/
|
| 54 |
function captcha_get_form_id_setting($form_id, $symbolic=FALSE) {
|
| 55 |
$result = db_query("SELECT module, type FROM {captcha_points} WHERE form_id = '%s'", $form_id);
|
| 56 |
if (!$result) {
|
| 57 |
return NULL;
|
| 58 |
}
|
| 59 |
$captcha_point = db_fetch_object($result);
|
| 60 |
if (!$captcha_point) {
|
| 61 |
$captcha_point = NULL;
|
| 62 |
}
|
| 63 |
elseif ($captcha_point->type == 'default') {
|
| 64 |
if (!$symbolic) {
|
| 65 |
list($module, $type) = explode('/', variable_get('captcha_default_challenge', 'captcha/Math'));
|
| 66 |
$captcha_point->module = $module;
|
| 67 |
$captcha_point->type = $type;
|
| 68 |
}
|
| 69 |
else {
|
| 70 |
$captcha_point = 'default';
|
| 71 |
}
|
| 72 |
}
|
| 73 |
elseif ($captcha_point->module == NULL && $captcha_point->type == NULL && $symbolic) {
|
| 74 |
$captcha_point = 'none';
|
| 75 |
}
|
| 76 |
elseif ($symbolic) {
|
| 77 |
$captcha_point = $captcha_point->module .'/'. $captcha_point->type;
|
| 78 |
}
|
| 79 |
return $captcha_point;
|
| 80 |
}
|
| 81 |
|
| 82 |
|
| 83 |
/**
|
| 84 |
* Helper function for generating a new CAPTCHA session.
|
| 85 |
*
|
| 86 |
* @param $form_id the form_id of the form to add a CAPTCHA to.
|
| 87 |
* @param $status the initial status of the CAPTHCA session.
|
| 88 |
* @return the session ID of the new CAPTCHA session.
|
| 89 |
*/
|
| 90 |
function _captcha_generate_captcha_session($form_id=NULL, $status=CAPTCHA_STATUS_UNSOLVED) {
|
| 91 |
global $user;
|
| 92 |
db_query("INSERT into {captcha_sessions} (uid, sid, ip_address, timestamp, form_id, solution, status, attempts) VALUES (%d, '%s', '%s', %d, '%s', '%s', %d, %d)", $user->uid, session_id(), ip_address(), time(), $form_id, 'undefined', $status, 0);
|
| 93 |
$captcha_sid = db_last_insert_id('captcha_sessions', 'csid');
|
| 94 |
return $captcha_sid;
|
| 95 |
}
|
| 96 |
|
| 97 |
/**
|
| 98 |
* Helper function for updating the solution in the CAPTCHA session table.
|
| 99 |
*
|
| 100 |
* @param $captcha_sid the CAPTCHA session ID to update.
|
| 101 |
* @param $solution the new solution to associate with the given CAPTCHA session.
|
| 102 |
*/
|
| 103 |
function _captcha_update_captcha_session($captcha_sid, $solution) {
|
| 104 |
db_query("UPDATE {captcha_sessions} SET timestamp=%d, solution='%s' WHERE csid=%d", time(), $solution, $captcha_sid);
|
| 105 |
}
|
| 106 |
|
| 107 |
/**
|
| 108 |
* Helper function for checking if CAPTCHA is required for user,
|
| 109 |
* based on CAPTCHA session ID and user session info.
|
| 110 |
*/
|
| 111 |
function _captcha_required_for_user($captcha_sid, $form_id) {
|
| 112 |
$captcha_session_status = db_result(db_query("SELECT status FROM {captcha_sessions} WHERE csid = %d", $captcha_sid));
|
| 113 |
|
| 114 |
$captcha_success_form_ids = isset($_SESSION['captcha_success_form_ids']) ? (array)($_SESSION['captcha_success_form_ids']) : array();
|
| 115 |
|
| 116 |
switch (variable_get('captcha_persistence', CAPTCHA_PERSISTENCE_SHOW_ALWAYS)) {
|
| 117 |
case CAPTCHA_PERSISTENCE_SKIP_ONCE_SUCCESSFUL:
|
| 118 |
$captcha_persistence_status = (count($captcha_success_form_ids) > 0);
|
| 119 |
break;
|
| 120 |
case CAPTCHA_PERSISTENCE_SKIP_ONCE_SUCCESSFUL_PER_FORM:
|
| 121 |
$captcha_persistence_status = isset($captcha_success_form_ids[$form_id]);
|
| 122 |
break;
|
| 123 |
default:
|
| 124 |
$captcha_persistence_status = FALSE;
|
| 125 |
}
|
| 126 |
|
| 127 |
return ($captcha_session_status == CAPTCHA_STATUS_UNSOLVED) && !$captcha_persistence_status;
|
| 128 |
}
|
| 129 |
|
| 130 |
/**
|
| 131 |
* Get the CAPTCHA description as configured on the general CAPTCHA
|
| 132 |
* settings page.
|
| 133 |
*
|
| 134 |
* If the locale module is enabled, the description will be returned
|
| 135 |
* for the current language the page is rendered for. This language
|
| 136 |
* can optionally been overriden with the $lang_code argument.
|
| 137 |
*
|
| 138 |
* @param $lang_code an optional language code to get the descripion for.
|
| 139 |
* @return a string with (localized) CAPTCHA description.
|
| 140 |
*/
|
| 141 |
function _captcha_get_description($lang_code=NULL) {
|
| 142 |
// If no language code is given: use the language of the current page.
|
| 143 |
global $language;
|
| 144 |
$lang_code = isset($lang_code) ? $lang_code : $language->language;
|
| 145 |
// The hardcoded but localizable default.
|
| 146 |
$default = t('This question is for testing whether you are a human visitor and to prevent automated spam submissions.', array(), $lang_code);
|
| 147 |
// Look up the configured CAPTCHA description or fall back on the (localized) default.
|
| 148 |
if (module_exists('locale')) {
|
| 149 |
$description = variable_get("captcha_description_$lang_code", $default);
|
| 150 |
}
|
| 151 |
else {
|
| 152 |
$description = variable_get('captcha_description', $default);
|
| 153 |
}
|
| 154 |
return $description;
|
| 155 |
}
|
| 156 |
|
| 157 |
/**
|
| 158 |
* Parse or interpret the given captcha_type.
|
| 159 |
* @param $captcha_type string representation of the CAPTCHA type,
|
| 160 |
* e.g. 'default', 'none', 'captcha/Math', 'image_captcha/Image'
|
| 161 |
* @return list($captcha_module, $captcha_type)
|
| 162 |
*/
|
| 163 |
function _captcha_parse_captcha_type($captcha_type) {
|
| 164 |
if ($captcha_type == 'none') {
|
| 165 |
return array(NULL, NULL);
|
| 166 |
}
|
| 167 |
if ($captcha_type == 'default') {
|
| 168 |
$captcha_type = variable_get('captcha_default_challenge', 'captcha/Math');
|
| 169 |
}
|
| 170 |
return explode('/', $captcha_type);
|
| 171 |
}
|
| 172 |
|
| 173 |
/**
|
| 174 |
* Helper function to get placement information for a given form_id.
|
| 175 |
* @param $form_id the form_id to get the placement information for.
|
| 176 |
* @param $form if a form corresponding to the given form_id, if there
|
| 177 |
* is no placement info for the given form_id, this form is examined to
|
| 178 |
* guess the placement.
|
| 179 |
* @return placement info array (@see _captcha_insert_captcha_element() for more
|
| 180 |
* info about the fields 'path', 'key' and 'weight'.
|
| 181 |
*/
|
| 182 |
function _captcha_get_captcha_placement($form_id, $form) {
|
| 183 |
// Get CAPTCHA placement map from cache. Two levels of cache:
|
| 184 |
// static variable in this function and storage in the variables table.
|
| 185 |
static $placement_map = NULL;
|
| 186 |
// Try first level cache.
|
| 187 |
if ($placement_map === NULL) {
|
| 188 |
// If first level cache missed: try second level cache.
|
| 189 |
$placement_map = variable_get('captcha_placement_map_cache', NULL);
|
| 190 |
// TODO: add UI in admin UI for flushing this cache.
|
| 191 |
|
| 192 |
if ($placement_map === NULL) {
|
| 193 |
// If second level cache missed: start from a fresh placement map.
|
| 194 |
$placement_map = array();
|
| 195 |
// Prefill with some hard coded default entries.
|
| 196 |
|
| 197 |
// The comment form can have a 'Preview' button, or both a 'Submit' and 'Preview' button,
|
| 198 |
// which is tricky for automatic placement detection. Luckily, Drupal core sets their
|
| 199 |
// weight (19 and 20), so we just have to specify a slightly smaller weight.
|
| 200 |
$placement_map['comment_form'] = array('path' => array(), 'key' => NULL, 'weight' => 18.9);
|
| 201 |
// Additional note: the node forms also have the posibility to only show a 'Preview' button.
|
| 202 |
// However, the 'Submit' button is always present, but is just not rendered ('#access' = FALSE)
|
| 203 |
// in those cases. The the automatic button detection should be sufficient for node forms.
|
| 204 |
|
| 205 |
// $placement_map['user_login'] = array('path' => array(), 'key' => NULL, 'weight' => 1.9);
|
| 206 |
// TODO: also make the placement 'overridable' from the admin UI?
|
| 207 |
}
|
| 208 |
}
|
| 209 |
|
| 210 |
// Query the placement map.
|
| 211 |
if (array_key_exists($form_id, $placement_map)) {
|
| 212 |
$placement = $placement_map[$form_id];
|
| 213 |
}
|
| 214 |
// If no placement info is available in placement map:
|
| 215 |
// search the form for buttons and guess placement from it.
|
| 216 |
else {
|
| 217 |
$buttons = _captcha_search_buttons($form);
|
| 218 |
if (count($buttons)) {
|
| 219 |
// Pick first button.
|
| 220 |
// TODO: make this more sofisticated? Use cases needed.
|
| 221 |
$placement = $buttons[0];
|
| 222 |
}
|
| 223 |
else {
|
| 224 |
// Use NULL when no buttons were found.
|
| 225 |
$placement = NULL;
|
| 226 |
}
|
| 227 |
|
| 228 |
// Store calculated placement in caches.
|
| 229 |
$placement_map[$form_id] = $placement;
|
| 230 |
variable_set('captcha_placement_map_cache', $placement_map);
|
| 231 |
}
|
| 232 |
|
| 233 |
return $placement;
|
| 234 |
}
|
| 235 |
|
| 236 |
/**
|
| 237 |
* Helper function for searching the buttons in a form.
|
| 238 |
*
|
| 239 |
* @param $form the form to search button elements in
|
| 240 |
* @return an array of paths to the buttons.
|
| 241 |
* A path is an array of keys leading to the button, the last
|
| 242 |
* item in the path is the weight of the button element
|
| 243 |
* (or NULL if undefined).
|
| 244 |
*/
|
| 245 |
function _captcha_search_buttons($form) {
|
| 246 |
$buttons = array();
|
| 247 |
foreach (element_children($form) as $key) {
|
| 248 |
// Look for submit or button type elements.
|
| 249 |
if (isset($form[$key]['#type']) && ($form[$key]['#type'] == 'submit' || $form[$key]['#type'] == 'button')) {
|
| 250 |
$weight = isset($form[$key]['#weight']) ? $form[$key]['#weight'] : NULL;
|
| 251 |
$buttons[] = array(
|
| 252 |
'path' => array(),
|
| 253 |
'key' => $key,
|
| 254 |
'weight' => $weight,
|
| 255 |
);
|
| 256 |
}
|
| 257 |
// Process children recurively.
|
| 258 |
$children_buttons = _captcha_search_buttons($form[$key]);
|
| 259 |
foreach ($children_buttons as $b) {
|
| 260 |
$b['path'] = array_merge(array($key), $b['path']);
|
| 261 |
$buttons[] = $b;
|
| 262 |
}
|
| 263 |
}
|
| 264 |
return $buttons;
|
| 265 |
}
|
| 266 |
|
| 267 |
/**
|
| 268 |
* Helper function to insert a CAPTCHA element in a form before a given form element.
|
| 269 |
* @param $form the form to add the CAPTCHA element to.
|
| 270 |
* @param $placement information where the CAPTCHA element should be inserted.
|
| 271 |
* $placement should be an associative array with fields:
|
| 272 |
* - 'path': path (array of path items) of the container in the form where the
|
| 273 |
* CAPTCHA element should be inserted.
|
| 274 |
* - 'key': the key of the element before which the CAPTCHA element
|
| 275 |
* should be inserted. If the field 'key' is undefined or NULL, the CAPTCHA will
|
| 276 |
* just be appended to the container.
|
| 277 |
* - 'weight': if 'key' is not NULL: should be the weight of the element defined by 'key'.
|
| 278 |
* If 'key' is NULL and weight is not NULL: set the weight property of the CAPTCHA element
|
| 279 |
* to this value.
|
| 280 |
* @param $captcha_element the CAPTCHA element to insert.
|
| 281 |
*/
|
| 282 |
function _captcha_insert_captcha_element(&$form, $placement, $captcha_element) {
|
| 283 |
// Get path, target and target weight or use defaults if not available.
|
| 284 |
$target_key = isset($placement['key']) ? $placement['key'] : NULL;
|
| 285 |
$target_weight = isset($placement['weight']) ? $placement['weight'] : NULL;
|
| 286 |
$path = isset($placement['path']) ? $placement['path'] : array();
|
| 287 |
|
| 288 |
// Walk through the form along the path.
|
| 289 |
$form_stepper = &$form;
|
| 290 |
foreach ($path as $step) {
|
| 291 |
if (isset($form_stepper[$step])) {
|
| 292 |
$form_stepper = & $form_stepper[$step];
|
| 293 |
}
|
| 294 |
else {
|
| 295 |
// Given path is invalid: stop stepping and
|
| 296 |
// continue in best effort (append instead of insert).
|
| 297 |
$target_key = NULL;
|
| 298 |
break;
|
| 299 |
}
|
| 300 |
}
|
| 301 |
|
| 302 |
// If no target is available: just append the CAPTCHA element to the container.
|
| 303 |
if ($target_key == NULL || !array_key_exists($target_key, $form_stepper)) {
|
| 304 |
// Optionally, set weight of CAPTCHA element.
|
| 305 |
if ($target_weight != NULL) {
|
| 306 |
$captcha_element['#weight'] = $target_weight;
|
| 307 |
}
|
| 308 |
$form_stepper['captcha'] = $captcha_element;
|
| 309 |
}
|
| 310 |
// If there is a target available: make sure the CAPTCHA element comes right before it.
|
| 311 |
else {
|
| 312 |
// If target has a weight: set weight of CAPTCHA element a bit smaller
|
| 313 |
// and just append the CAPTCHA: sorting will fix the ordering anyway.
|
| 314 |
if ($target_weight != NULL) {
|
| 315 |
$captcha_element['#weight'] = $target_weight - .1;
|
| 316 |
$form_stepper['captcha'] = $captcha_element;
|
| 317 |
}
|
| 318 |
else {
|
| 319 |
// If we can't play with weights: insert the CAPTCHA element at the right position.
|
| 320 |
// Because PHP lacks a function for this (array_splice() comes close,
|
| 321 |
// but it does not preserve the key of the inserted element), we do it by hand:
|
| 322 |
// chop of the end, append the CAPTCHA element and put the end back.
|
| 323 |
$offset = array_search($target_key, array_keys($form_stepper));
|
| 324 |
$end = array_splice($form_stepper, $offset);
|
| 325 |
$form_stepper['captcha'] = $captcha_element;
|
| 326 |
foreach($end as $k => $v) {
|
| 327 |
$form_stepper[$k] = $v;
|
| 328 |
}
|
| 329 |
}
|
| 330 |
}
|
| 331 |
}
|
| 332 |
|