This issue is closely related to #463002: Drop preprocess op of hook_captcha?

Case insensitive validation is now part of CAPTCHA core, but there are other kinds of CAPTCHA validation that go further than a simple equality test. For example:
http://drupal.org/node/440490#comment-1601638 (handling multiple possible responses)
#472450: image_captcha RTL support

I'm currently thinking along the lines of adding a '#captcha_validate' property to the generate op of hook_captcha like for example

   case 'generate':
     if ($captcha_type == 'Foo CAPTCHA') {
        $captcha = array();
        $captcha['solution'] = 'foo';
        $captcha['form']['captcha_response'] = array(
          '#type' => 'textfield',
          '#title' => t('Enter "foo"'),
        );
        $captcha['form']['#captcha_validate'] = 'foo_custom_validation';
        return $captcha;

We just can't reuse the '#validate' property because the arguments we should pass differ from a standard Drupal FAPI validate function. e.g.

function foo_custom_validation($solution, $response) {
}

Maybe we could also pass the CAPTCHA session id? But we could add this later too.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

soxofaan’s picture

Priority: Normal » Critical

this one should be fixed before 6.x-2.0-rc2

thekevinday’s picture

poke. subscribing.

soxofaan’s picture

marked #496806: Do more with $result as duplicate

awolfey’s picture

subscribe

soxofaan’s picture

Here is a first patch to implement this.

For challenge implementing modules: custom CAPTCHA validation can be defined as follows:

function foo_captcha(...) {
...
  case 'generate':
        $captcha = array();
        $captcha['solution'] = ...
        $captcha['form'] = ...
        $captcha['captcha_validate'] = 'foo_custom_validation';
        return $captcha;
...

function foo_custom_validation($solution, $response) {
  return $response == "foo $solution foo";
}

function foo_custom_validation() will be called instead of the traditional equality test, it should return TRUE/FALSE for correct/wrong response.

Also attached: proof of concept example for the image CAPTCHA module to make it only accept the answer 'foo'.

Also note that the global case sensitive/insensitive option is implemented through this mechanism now.

edit: typo

awolfey’s picture

Status: Active » Needs review

This works perfectly form me. For validating a response against multiple possible solutions in the riddler module I used:

// Custom captcha validation. 
function riddler_captcha_validate($solution, $response) {
  $solution = str_ireplace(',', '', $solution);
  $solution = explode(' ', $solution);
  return in_array($response, $solution);
}

I did see that captcha will pass an empty response to the custom validation, which could require checking there. I'm not sure if there is a case where you would want to see an empty response, so maybe it's better to check for that before response is passed.

Thanks!

soxofaan’s picture

@awolfey: thanks for testing

I did see that captcha will pass an empty response to the custom validation, which could require checking there.

I'm not sure I understand what you mean, but to block empty responses you can flag a texfield as required, which will make drupal complain about the empty response, even before CAPTCHA kicks in. Take the simple math challenge for example:

 $result['form']['captcha_response'] = array(
          '#type' => 'textfield',
          '#title' => t('Math question'),
          '#description' => t('Solve this simple math problem and enter the result. E.g. for 1+3, enter 4.'),
          '#field_prefix' => t('@x + @y = ', array('@x' => $x, '@y' => $y)),
          '#size' => 4,
          '#maxlength' => 2,
          '#required' => TRUE,
        );

note the '#required' => TRUE, as the last property.

awolfey’s picture

I have required set to TRUE. I first did my test this way:

function riddler_captcha_validate($solution, $response) {
  drupal_set_message('at riddler validate '. $solution . ' response '. $response);
  return stristr($solution, $response);
}

Which, on an empty response, gave me this php warning: warning: stristr() [function.stristr]: Empty delimiter in C:\wamp\www\sandbox\sites\all\modules\riddler\riddler.module on line 180. There is ALSO a message that the field is required.

Returning FALSE on !$response catches it before stristr(). It's not a problem for in_array().

Thanks

soxofaan’s picture

That warning is because of the behavior of the stristr() function when the second argument is an empty string, it has nothing to do with the CAPTCHA module or even Drupal.
In this case you should indeed catch it before calling stristr().
Note that you are not limited to oneliner validation functions (maybe the examples suggest this): you can add for loops and if test all you want. As long as they return TRUE on success en FALSE on failure.

awolfey’s picture

Right. I wasn't expecting empty results to get passed to the validation function. Not a big deal.

Anyway, looking forward to this being committed.

Thanks.

soxofaan’s picture

Priority: Critical » Normal
Status: Needs review » Active

The patch from #5 went in with some further tweaks:
http://drupal.org/cvs?commit=232866

Question to the module developers that want to use this functionality:
the custom CAPTCHA validation function are expected to be of the form:

function foo_custom_validation($solution, $response) {
...
}

Is this enough or would there be interest to receive more input, like $form, $form_state?

awolfey’s picture

Working for me. Updated the D6 version of riddler to be compatible. For that module there's no need for $form, $form_state that I can see now.

Thanks.

soxofaan’s picture

Status: Active » Fixed

added optional $element and $form_state for captcha validate functions as suggested in #11
http://drupal.org/cvs?commit=256822

this issue can finally be closed

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.