You are here

function captcha_validate in CAPTCHA 8

Same name and namespace in other branches
  1. 5.3 captcha.module \captcha_validate()
  2. 6.2 captcha.module \captcha_validate()
  3. 6 captcha.module \captcha_validate()
  4. 7 captcha.module \captcha_validate()

CAPTCHA validation handler.

This function is placed in the main captcha.module file to make sure that it is available (even for cached forms, which don't fire captcha_form_alter(), and subsequently don't include additional include files).

1 string reference to 'captcha_validate'
Captcha::processCaptchaElement in src/Element/Captcha.php
Process callback for CAPTCHA form element.

File

./captcha.module, line 473
This module enables basic CAPTCHA functionality.

Code

function captcha_validate($element, FormStateInterface &$form_state) {
  $captcha_info = $form_state
    ->get('captcha_info');
  $form_id = $captcha_info['this_form_id'];

  // Get CAPTCHA response.
  $captcha_response = $form_state
    ->getValue('captcha_response');

  // Get CAPTCHA session from CAPTCHA info
  // TODO: is this correct in all cases: see comments in previous revisions?
  $csid = $captcha_info['captcha_sid'];

  // Bypass captcha validation if access attribute value is false.
  if (empty($captcha_info['access'])) {
    return FALSE;
  }

  // If the form is cacheable where all solution validation is handed off or if
  // we found a session with a solution then continue with validation.
  $is_cacheable = (bool) $form_state
    ->getValue('captcha_cacheable', FALSE);
  if ($is_cacheable) {

    // Completely ignore the captcha_sessions table,
    // since the captcha_sid can get reused by the cache.
    $solution = FALSE;
    $captcha_validate = $element['#captcha_validate'];
    if (!function_exists($captcha_validate)) {

      // Cacheable CAPTCHAs must provide their own validation function.
      $form_state
        ->setErrorByName('captcha', t('CAPTCHA configuration error: Contact the site administrator.'));
      \Drupal::logger('CAPTCHA')
        ->error('CAPTCHA configuration error: cacheable CAPTCHA type %challenge did not provide a validation function.', [
        '%challenge' => $captcha_info['captcha_type'],
      ]);
    }

    // Check the response with the CAPTCHA validation function.
    // Apart from the traditional expected $solution and received $response,
    // we also provide the CAPTCHA $element and $form_state
    // arrays for more advanced use cases.
    if (!$captcha_validate($solution, $captcha_response, $element, $form_state)) {

      // Wrong answer.
      $form_state
        ->setErrorByName('captcha_response', _captcha_get_error_message());

      // Update wrong response counter.
      if (\Drupal::config('captcha.settings')
        ->get('enable_stats', FALSE)) {
        Drupal::state()
          ->set('captcha.wrong_response_counter', Drupal::state()
          ->get('captcha.wrong_response_counter', 0) + 1);
      }
      if (\Drupal::config('captcha.settings')
        ->get('log_wrong_responses', FALSE)) {
        \Drupal::logger('CAPTCHA')
          ->notice('%form_id post blocked by CAPTCHA module: challenge %challenge (by module %module).', [
          '%form_id' => $form_id,
          '%challenge' => $captcha_info['captcha_type'],
          '%module' => $captcha_info['module'],
        ]);
      }
    }
  }
  else {
    $solution = \Drupal::database()
      ->select('captcha_sessions', 'cs')
      ->fields('cs', [
      'solution',
    ])
      ->condition('csid', $csid)
      ->execute()
      ->fetchField();
    if ($solution !== FALSE) {

      // Get CAPTCHA validate function or fall back on strict equality.
      $captcha_validate = $element['#captcha_validate'];
      if (!function_exists($captcha_validate)) {
        $captcha_validate = 'captcha_validate_strict_equality';
      }

      // Check the response with the CAPTCHA validation function.
      // Apart from the traditional expected $solution and received $response,
      // we also provide the CAPTCHA $element and $form_state
      // arrays for more advanced use cases.
      if ($captcha_validate($solution, $captcha_response, $element, $form_state)) {

        // Get the CAPTCHA persistence setting.
        $captcha_persistence = \Drupal::config('captcha.settings')
          ->get('persistence');
        if (in_array($captcha_persistence, [
          CAPTCHA_PERSISTENCE_SKIP_ONCE_SUCCESSFUL,
          CAPTCHA_PERSISTENCE_SKIP_ONCE_SUCCESSFUL_PER_FORM_TYPE,
        ])) {

          // Only save the success in $_SESSION if it is actually needed for
          // further validation in _captcha_required_for_user(). Setting
          // this kills the page cache so let's not be cavalier about it.
          $_SESSION['captcha_success_form_ids'][$form_id] = $form_id;
        }

        // Record success.
        \Drupal::database()
          ->update('captcha_sessions')
          ->condition('csid', $csid)
          ->fields([
          'status' => CAPTCHA_STATUS_SOLVED,
        ])
          ->expression('attempts', 'attempts + 1')
          ->execute();
      }
      else {

        // Wrong answer.
        \Drupal::database()
          ->update('captcha_sessions')
          ->condition('csid', $csid)
          ->expression('attempts', 'attempts + 1')
          ->execute();
        $form_state
          ->setErrorByName('captcha_response', _captcha_get_error_message());

        // Update wrong response counter.
        if (\Drupal::config('captcha.settings')
          ->get('enable_stats', FALSE)) {
          Drupal::state()
            ->set('captcha.wrong_response_counter', Drupal::state()
            ->get('captcha.wrong_response_counter', 0) + 1);
        }
        if (\Drupal::config('captcha.settings')
          ->get('log_wrong_responses', FALSE)) {
          \Drupal::logger('CAPTCHA')
            ->notice('%form_id post blocked by CAPTCHA module: challenge %challenge (by module %module), user answered "@response", but the solution was "@solution".', [
            '%form_id' => $form_id,
            '@response' => $captcha_response,
            '@solution' => $solution,
            '%challenge' => $captcha_info['captcha_type'],
            '%module' => $captcha_info['module'],
          ]);
        }
      }
    }
    else {

      // If the session is gone and we can't confirm a solution error.
      // Note: _captcha_get_posted_captcha_info() validates and triggers session
      // rebuilds for re-use attacks during element processing so this should be
      // rare if it ever happens.
      $form_state
        ->setErrorByName('captcha', t('CAPTCHA validation error: unknown CAPTCHA session ID. Contact the site administrator if this problem persists.'));
      \Drupal::logger('CAPTCHA')
        ->error('CAPTCHA validation error: unknown CAPTCHA session ID (%csid).', [
        '%csid' => var_export($csid, TRUE),
      ]);
    }
  }
}