Commit 09cb4821 authored by Sascha Grossenbacher's avatar Sascha Grossenbacher Committed by Fabiano Sant'Ana
Browse files

Issue #2558243 by Berdir, tim.plunkett: Use a FormElement plugin instead of...

Issue #2558243 by Berdir, tim.plunkett: Use a FormElement plugin instead of the deprecated hook_element_info()
parent 2c528bb8
Loading
Loading
Loading
Loading
+0 −150
Changes for captcha.module: 0 added lines, 150 removed lines.
Original line number Diff line number Diff line
@@ -102,156 +102,6 @@ function captcha_cron() {
    ->execute();
}


/**
 * Implements hook_element_info().
 */
function captcha_element_info() {
  // Define the CAPTCHA form element with default properties.
  $captcha_element = [
    '#input' => TRUE,
    '#process' => ['captcha_element_process'],
    // The type of challenge: e.g. 'default', 'captcha/Math', etc.
    '#captcha_type' => 'default',
    '#default_value' => '',
    // CAPTCHA in admin mode: presolve the CAPTCHA and always show
    // it (despite previous successful responses).
    '#captcha_admin_mode' => FALSE,
    // The default CAPTCHA validation function.
    // TODO: should this be a single string or an array of strings?
    '#captcha_validate' => 'captcha_validate_strict_equality',
  ];
  // Override the default CAPTCHA validation function for case
  // insensitive validation.
  // TODO: shouldn't this be done somewhere else, e.g. in form_alter?
  if (CAPTCHA_DEFAULT_VALIDATION_CASE_INSENSITIVE == \Drupal::config('captcha.settings')->get('default_validation')) {
    $captcha_element['#captcha_validate'] = 'captcha_validate_case_insensitive_equality';
  }

  return ['captcha' => $captcha_element];
}

/**
 * Process callback for CAPTCHA form element.
 */
function captcha_element_process(array $element, FormStateInterface $form_state, array $complete_form) {
  // Add captcha.inc file
  module_load_include('inc', 'captcha');

  // Add Javascript for general CAPTCHA functionality.
  $element['#attached']['library'][] = 'captcha/base';

  // Get the form ID of the form we are currently processing (which is not
  // necessary the same form that is submitted (if any).
  $this_form_id = $complete_form['form_id']['#value'];

  // Get the CAPTCHA session ID.
  // If there is a submitted form: try to retrieve and reuse the
  // CAPTCHA session ID from the posted data.
  list($posted_form_id, $posted_captcha_sid) = _captcha_get_posted_captcha_info($element, $form_state, $this_form_id);
  if ($this_form_id == $posted_form_id && isset($posted_captcha_sid)) {
    $captcha_sid = $posted_captcha_sid;
  }
  else {
    // Generate a new CAPTCHA session if we could not reuse one from a posted form.
    $captcha_sid = _captcha_generate_captcha_session($this_form_id, CAPTCHA_STATUS_UNSOLVED);
  }

  // Store CAPTCHA session ID as hidden field.
  // Strictly speaking, it is not necessary to send the CAPTCHA session id
  // with the form, as the one time CAPTCHA token (see lower) is enough.
  // However, we still send it along, because it can help debugging
  // problems on live sites with only access to the markup.
  $element['captcha_sid'] = [
    '#type' => 'hidden',
    '#value' => $captcha_sid,
  ];

  // Additional one time CAPTCHA token: store in database and send with form.
  $captcha_token = md5(mt_rand());
  db_update('captcha_sessions')
    ->fields(['token' => $captcha_token])
    ->condition('csid', $captcha_sid)
    ->execute();
  $element['captcha_token'] = [
    '#type' => 'hidden',
    '#value' => $captcha_token,
  ];

  // Get implementing module and challenge for CAPTCHA.
  list($captcha_type_module, $captcha_type_challenge) = _captcha_parse_captcha_type($element['#captcha_type']);

  // Store CAPTCHA information for further processing in
  // - $form_state->get('captcha_info'), which survives a form rebuild (e.g. node
  //   preview), useful in _captcha_get_posted_captcha_info().
  // - $element['#captcha_info'], for post processing functions that do not
  //   receive a $form_state argument (e.g. the pre_render callback).
  $form_state->set('captcha_info', [
    'this_form_id' => $this_form_id,
    'posted_form_id' => $posted_form_id,
    'captcha_sid' => $captcha_sid,
    'module' => $captcha_type_module,
    'captcha_type' => $captcha_type_challenge,
  ]);
  $element['#captcha_info'] = [
    'form_id' => $this_form_id,
    'captcha_sid' => $captcha_sid,
  ];

  if (_captcha_required_for_user($captcha_sid, $this_form_id) || $element['#captcha_admin_mode']) {
    // Generate a CAPTCHA and its solution
    // (note that the CAPTCHA session ID is given as third argument).
    $captcha = \Drupal::moduleHandler()->invoke($captcha_type_module, 'captcha', ['generate', $captcha_type_challenge, $captcha_sid]);

    // @todo Isn't this moment a bit late to figure out that we don't need CAPTCHA?
    if(!isset($captcha)) {
      return $element;
    }

    if (!isset($captcha['form']) || !isset($captcha['solution'])) {
      // The selected module did not return what we expected: log about it and quit.
      \Drupal::logger('CAPTCHA')->error(
        'CAPTCHA problem: unexpected result from hook_captcha() of module %module when trying to retrieve challenge type %type for form %form_id.',
        ['%type' => $captcha_type_challenge, '%module' => $captcha_type_module, '%form_id' => $this_form_id]
      );

      return $element;
    }
    // Add form elements from challenge as children to CAPTCHA form element.
    $element['captcha_widgets'] = $captcha['form'];

    // Add a validation callback for the CAPTCHA form element
    // (when not in admin mode).
    if (!$element['#captcha_admin_mode']) {
      $element['#element_validate'] = ['captcha_validate'];
    }

    // Set a custom CAPTCHA validate function if requested.
    if (isset($captcha['captcha_validate'])) {
      $element['#captcha_validate'] = $captcha['captcha_validate'];
    }

    // Set the theme function.
    $element['#theme'] = 'captcha';

    // Add pre_render callback for additional CAPTCHA processing.
    if (!isset($element['#pre_render'])) {
      $element['#pre_render'] = [];
    }
    $element['#pre_render'][] = 'captcha_pre_render_process';

    // Store the solution in the #captcha_info array.
    $element['#captcha_info']['solution'] = $captcha['solution'];

    // Make sure we can use a top level form value
    // $form_state->getValue('captcha_response'), even if the form has #tree=true.
    $element['#tree'] = FALSE;
  }

  return $element;
}


/**
 * Theme function for a CAPTCHA element.
 *
+5 −3
Changes for src/Tests/CaptchaCacheTestCase.php: 5 added lines, 3 removed lines.
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ class CaptchaCacheTestCase extends CaptchaBaseWebTestCase {
   * Test the cache tags.
   */
  public function testCacheTags() {
    global $base_path;
    // Check caching without captcha as anonymous user.
    $this->drupalGet('');
    $this->assertEqual($this->drupalGetHeader('x-drupal-cache'), 'MISS');
@@ -67,11 +68,12 @@ class CaptchaCacheTestCase extends CaptchaBaseWebTestCase {
    // Check that we get a new image when vising the page again.
    $this->drupalGet('');
    $this->assertNotEqual($image_path, (string) $this->xpath('//div[@class="details-wrapper"]/img/@src')[0]);
    // Check image caching.
    $this->drupalGet($image_path);
    // Check image caching, remove the base path since drupalGet() expects the
    // internal path.
    $this->drupalGet(substr($image_path, strlen($base_path)));
    $this->assertResponse(200);
    // Request image twice to make sure no errors happen (due to page caching).
    $this->drupalGet($image_path);
    $this->drupalGet(substr($image_path, strlen($base_path)));
    $this->assertResponse(200);
  }