You are here

recaptcha.module in reCAPTCHA 8.2

Verifies if user is a human without necessity to solve a CAPTCHA.

File

recaptcha.module
View source
<?php

/**
 * @file
 * Verifies if user is a human without necessity to solve a CAPTCHA.
 */
use ReCaptcha\RequestMethod\Drupal8Post;
use ReCaptcha\ReCaptcha;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Template\Attribute;
use Drupal\Core\Url;
require_once dirname(__FILE__) . '/recaptcha-php/src/ReCaptcha/ReCaptcha.php';
require_once dirname(__FILE__) . '/recaptcha-php/src/ReCaptcha/RequestMethod.php';
require_once dirname(__FILE__) . '/recaptcha-php/src/ReCaptcha/RequestParameters.php';
require_once dirname(__FILE__) . '/recaptcha-php/src/ReCaptcha/Response.php';
require_once dirname(__FILE__) . '/src/ReCaptcha/RequestMethod/Drupal8Post.php';

/**
 * Implements hook_help().
 */
function recaptcha_help($route_name, RouteMatchInterface $route_match) {
  switch ($route_name) {
    case 'help.page.recaptcha':
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
      $output .= '<p>' . t('Google <a href=":url">reCAPTCHA</a> is a free service to protect your website from spam and abuse. reCAPTCHA uses an advanced risk analysis engine and adaptive CAPTCHAs to keep automated software from engaging in abusive activities on your site. It does this while letting your valid users pass through with ease.', [
        ':url' => 'https://www.google.com/recaptcha',
      ]) . '</p>';
      $output .= '<h3>' . t('Uses') . '</h3>';
      $output .= '<dl>';
      $output .= '<dt>' . t('Protects and defends') . '</dt>';
      $output .= '<dd>' . t('reCAPTCHA is built for security. Armed with state of the art technology, it always stays at the forefront of spam and abuse fighting trends. reCAPTCHA is on guard for you, so you can rest easy.') . '</dd>';
      $output .= '</dl>';
      $output .= '<h3>' . t('Configuration') . '</h3>';
      $output .= '<ol>';
      $output .= '<li>' . t('Enable reCAPTCHA and CAPTCHA modules in Adminstration > Extend') . '</li>';
      $output .= '<li>' . t('You will now find a reCAPTCHA tab in the CAPTCHA administration page available at: Administration > Configuration > People > CAPTCHA module settings > reCAPTCHA') . '</li>';
      $output .= '<li>' . t('Register your web site at <a href=":url">https://www.google.com/recaptcha/admin/create</a>', [
        ':url' => 'https://www.google.com/recaptcha/admin/create',
      ]) . '</li>';
      $output .= '<li>' . t('Input the site and private keys into the reCAPTCHA settings.') . '</li>';
      $output .= '<li>' . t('Visit the Captcha administration page and set where you want the reCAPTCHA form to be presented: Administration > Configuration > People > CAPTCHA module settings') . '</li>';
      $output .= '</ol>';
      return $output;
  }
}

/**
 * Implements hook_theme().
 */
function recaptcha_theme() {
  return [
    'recaptcha_widget_noscript' => [
      'variables' => [
        'widget' => NULL,
      ],
      'template' => 'recaptcha-widget-noscript',
    ],
  ];
}

/**
 * Implements hook_captcha().
 */
function recaptcha_captcha($op, $captcha_type = '') {
  switch ($op) {
    case 'list':
      return [
        'reCAPTCHA',
      ];
    case 'generate':
      $captcha = [];
      if ($captcha_type == 'reCAPTCHA') {
        $config = \Drupal::config('recaptcha.settings');
        $renderer = \Drupal::service('renderer');
        $recaptcha_site_key = $config
          ->get('site_key');
        $recaptcha_secret_key = $config
          ->get('secret_key');
        $recaptcha_use_globally = $config
          ->get('use_globally');
        if (!empty($recaptcha_site_key) && !empty($recaptcha_secret_key)) {

          // Build the reCAPTCHA captcha form if site_key and secret_key are
          // configured. Captcha requires TRUE to be returned in solution.
          $captcha['solution'] = TRUE;
          $captcha['captcha_validate'] = 'recaptcha_captcha_validation';
          $captcha['form']['captcha_response'] = [
            '#type' => 'hidden',
            '#value' => 'Google no captcha',
          ];

          // As the validate callback does not depend on sid or solution, this
          // captcha type can be displayed on cached pages.
          $captcha['cacheable'] = TRUE;

          // Check if reCAPTCHA use globally is enabled.
          $recaptcha_src = 'https://www.google.com/recaptcha/api.js';
          $recaptcha_src_fallback = 'https://www.google.com/recaptcha/api/fallback';
          if ($recaptcha_use_globally) {
            $recaptcha_src = 'https://www.recaptcha.net/recaptcha/api.js';
            $recaptcha_src_fallback = 'https://www.recaptcha.net/recaptcha/api/fallback';
          }
          $noscript = '';
          if ($config
            ->get('widget.noscript')) {
            $recaptcha_widget_noscript = [
              '#theme' => 'recaptcha_widget_noscript',
              '#widget' => [
                'sitekey' => $recaptcha_site_key,
                'recaptcha_src_fallback' => $recaptcha_src_fallback,
                'language' => \Drupal::service('language_manager')
                  ->getCurrentLanguage()
                  ->getId(),
              ],
            ];
            $noscript = $renderer
              ->render($recaptcha_widget_noscript);
          }
          $attributes = [
            'class' => 'g-recaptcha',
            'data-sitekey' => $recaptcha_site_key,
            'data-theme' => $config
              ->get('widget.theme'),
            'data-type' => $config
              ->get('widget.type'),
            'data-size' => $config
              ->get('widget.size'),
            'data-tabindex' => $config
              ->get('widget.tabindex'),
          ];

          // Filter out empty tabindex/size.
          $attributes = array_filter($attributes);
          $captcha['form']['recaptcha_widget'] = [
            '#markup' => '<div' . new Attribute($attributes) . '></div>',
            '#suffix' => $noscript,
            '#attached' => [
              'html_head' => [
                [
                  [
                    '#tag' => 'script',
                    '#attributes' => [
                      'src' => Url::fromUri($recaptcha_src, [
                        'query' => [
                          'hl' => \Drupal::service('language_manager')
                            ->getCurrentLanguage()
                            ->getId(),
                        ],
                        'absolute' => TRUE,
                      ])
                        ->toString(),
                      'async' => TRUE,
                      'defer' => TRUE,
                    ],
                  ],
                  'recaptcha_api',
                ],
              ],
            ],
          ];
        }
        else {

          // Fallback to Math captcha as reCAPTCHA is not configured.
          $captcha = captcha_captcha('generate', 'Math');
        }

        // If module configuration changes the form cache need to be refreshed.
        $renderer
          ->addCacheableDependency($captcha['form'], $config);
      }
      return $captcha;
  }
}

/**
 * CAPTCHA Callback; Validates the reCAPTCHA code.
 */
function recaptcha_captcha_validation($solution, $response, $element, $form_state) {
  $config = \Drupal::config('recaptcha.settings');
  $recaptcha_secret_key = $config
    ->get('secret_key');
  if (empty($_POST['g-recaptcha-response']) || empty($recaptcha_secret_key)) {
    return FALSE;
  }

  // Use Drupal::httpClient() to circumvent all issues with the Google library.
  $recaptcha = new ReCaptcha($recaptcha_secret_key, new Drupal8Post());

  // Ensures the hostname matches. Required if "Domain Name Validation" is
  // disabled for credentials.
  if ($config
    ->get('verify_hostname')) {
    $recaptcha
      ->setExpectedHostname($_SERVER['SERVER_NAME']);
  }
  $resp = $recaptcha
    ->verify($_POST['g-recaptcha-response'], \Drupal::request()
    ->getClientIp());
  if ($resp
    ->isSuccess()) {

    // Verified!
    return TRUE;
  }
  else {

    // Error code reference, https://developers.google.com/recaptcha/docs/verify
    $error_codes = [
      'action-mismatch' => t('Expected action did not match.'),
      'apk_package_name-mismatch' => t('Expected APK package name did not match.'),
      'bad-response' => t('Did not receive a 200 from the service.'),
      'bad-request' => t('The request is invalid or malformed.'),
      'connection-failed' => t('Could not connect to service.'),
      'invalid-input-response' => t('The response parameter is invalid or malformed.'),
      'invalid-input-secret' => t('The secret parameter is invalid or malformed.'),
      'invalid-json' => t('The json response is invalid or malformed.'),
      'missing-input-response' => t('The response parameter is missing.'),
      'missing-input-secret' => t('The secret parameter is missing.'),
      'hostname-mismatch' => t('Expected hostname did not match.'),
      'unknown-error' => t('Not a success, but no error codes received!'),
    ];
    $info_codes = [
      'challenge-timeout' => t('Challenge timeout.'),
      'score-threshold-not-met' => t('Score threshold not met.'),
      'timeout-or-duplicate' => t('The challenge response timed out or was already verified.'),
    ];
    foreach ($resp
      ->getErrorCodes() as $code) {
      if (isset($info_codes[$code])) {
        \Drupal::logger('reCAPTCHA web service')
          ->info('@info', [
          '@info' => $info_codes[$code],
        ]);
      }
      else {
        if (!isset($error_codes[$code])) {
          $code = 'unknown-error';
        }
        \Drupal::logger('reCAPTCHA web service')
          ->error('@error', [
          '@error' => $error_codes[$code],
        ]);
      }
    }
  }
  return FALSE;
}

/**
 * Process variables for recaptcha-widget-noscript.tpl.php.
 *
 * @see recaptcha-widget-noscript.tpl.php
 */
function template_preprocess_recaptcha_widget_noscript(&$variables) {
  $variables['sitekey'] = $variables['widget']['sitekey'];
  $variables['language'] = $variables['widget']['language'];
  $variables['url'] = Url::fromUri($variables['widget']['recaptcha_src_fallback'], [
    'query' => [
      'k' => $variables['widget']['sitekey'],
      'hl' => $variables['widget']['language'],
    ],
    'absolute' => TRUE,
  ])
    ->toString();
}

Functions

Namesort descending Description
recaptcha_captcha Implements hook_captcha().
recaptcha_captcha_validation CAPTCHA Callback; Validates the reCAPTCHA code.
recaptcha_help Implements hook_help().
recaptcha_theme Implements hook_theme().
template_preprocess_recaptcha_widget_noscript Process variables for recaptcha-widget-noscript.tpl.php.