Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • project/recaptcha
  • issue/recaptcha-3201620
  • issue/recaptcha-3035883
  • issue/recaptcha-3272700
  • issue/recaptcha-3298352
  • issue/recaptcha-3306750
  • issue/recaptcha-2493183
  • issue/recaptcha-3315920
  • issue/recaptcha-3330959
  • issue/recaptcha-3346050
  • issue/recaptcha-3350588
  • issue/recaptcha-3358223
  • issue/recaptcha-2852269
  • issue/recaptcha-3124353
  • issue/recaptcha-3408102
  • issue/recaptcha-3113837
  • issue/recaptcha-3122036
  • issue/recaptcha-3408281
  • issue/recaptcha-3189603
  • issue/recaptcha-3152474
  • issue/recaptcha-3135188
  • issue/recaptcha-3179035
  • issue/recaptcha-3408388
  • issue/recaptcha-3346065
  • issue/recaptcha-3408409
  • issue/recaptcha-3446000
  • issue/recaptcha-3452452
  • issue/recaptcha-3469111
  • issue/recaptcha-3481076
  • issue/recaptcha-3486443
  • issue/recaptcha-3494190
  • issue/recaptcha-3412344
  • issue/recaptcha-3497036
33 results
Show changes
Commits on Source (7)
Showing
with 585 additions and 143 deletions
################
# DrupalCI GitLabCI template
#
# Gitlab-ci.yml to replicate DrupalCI testing for Contrib
#
# With thanks to:
# * The GitLab Acceleration Initiative participants
# * DrupalSpoons
################
################
# Guidelines
#
# This template is designed to give any Contrib maintainer everything they need to test, without requiring modification. It is also designed to keep up to date with Core Development automatically through the use of include files that can be centrally maintained.
#
# However, you can modify this template if you have additional needs for your project.
################
################
# Includes
#
# Additional configuration can be provided through includes.
# One advantage of include files is that if they are updated upstream, the changes affect all pipelines using that include.
#
# Includes can be overridden by re-declaring anything provided in an include, here in gitlab-ci.yml
# https://docs.gitlab.com/ee/ci/yaml/includes.html#override-included-configuration-values
################
include:
################
# DrupalCI includes:
# As long as you include this, any future includes added by the Drupal Association will be accessible to your pipelines automatically.
# View these include files at https://git.drupalcode.org/project/gitlab_templates/
################
- project: $_GITLAB_TEMPLATES_REPO
ref: $_GITLAB_TEMPLATES_REF
file:
- '/includes/include.drupalci.main.yml'
# EXPERIMENTAL: For Drupal 7, remove the above line and uncomment the below.
# - '/includes/include.drupalci.main-d7.yml'
- '/includes/include.drupalci.variables.yml'
- '/includes/include.drupalci.workflows.yml'
################
# Pipeline configuration variables
#
# These are the variables provided to the Run Pipeline form that a user may want to override.
#
# Docs at https://git.drupalcode.org/project/gitlab_templates/-/blob/1.0.x/includes/include.drupalci.variables.yml
################
# variables:
# SKIP_ESLINT: '1'
###################################################################################
#
# *
# /(
# ((((,
# /(((((((
# ((((((((((*
# ,(((((((((((((((
# ,(((((((((((((((((((
# ((((((((((((((((((((((((*
# *(((((((((((((((((((((((((((((
# ((((((((((((((((((((((((((((((((((*
# *(((((((((((((((((( .((((((((((((((((((
# ((((((((((((((((((. /(((((((((((((((((*
# /((((((((((((((((( .(((((((((((((((((,
# ,(((((((((((((((((( ((((((((((((((((((
# .(((((((((((((((((((( .(((((((((((((((((
# ((((((((((((((((((((((( ((((((((((((((((/
# (((((((((((((((((((((((((((/ ,(((((((((((((((*
# .((((((((((((((/ /(((((((((((((. ,(((((((((((((((
# *(((((((((((((( ,(((((((((((((/ *((((((((((((((.
# ((((((((((((((, /(((((((((((((. ((((((((((((((,
# (((((((((((((/ ,(((((((((((((* ,(((((((((((((,
# *((((((((((((( .((((((((((((((( ,(((((((((((((
# ((((((((((((/ /((((((((((((((((((. ,((((((((((((/
# ((((((((((((( *(((((((((((((((((((((((* *((((((((((((
# ((((((((((((( ,(((((((((((((..((((((((((((( *((((((((((((
# ((((((((((((, /((((((((((((* /((((((((((((/ ((((((((((((
# ((((((((((((( /((((((((((((/ (((((((((((((* ((((((((((((
# (((((((((((((/ /(((((((((((( ,((((((((((((, *((((((((((((
# (((((((((((((( *(((((((((((/ *((((((((((((. ((((((((((((/
# *((((((((((((((((((((((((((, /(((((((((((((((((((((((((
# ((((((((((((((((((((((((( ((((((((((((((((((((((((,
# .(((((((((((((((((((((((/ ,(((((((((((((((((((((((
# ((((((((((((((((((((((/ ,(((((((((((((((((((((/
# *((((((((((((((((((((( (((((((((((((((((((((,
# ,(((((((((((((((((((((, ((((((((((((((((((((/
# ,(((((((((((((((((((((* /((((((((((((((((((((
# ((((((((((((((((((((((, ,/((((((((((((((((((((,
# ,(((((((((((((((((((((((((((((((((((((((((((((((((((
# .(((((((((((((((((((((((((((((((((((((((((((((
# .((((((((((((((((((((((((((((((((((((,.
# .,(((((((((((((((((((((((((.
#
###################################################################################
variables:
# Broaden test coverage.
OPT_IN_TEST_PREVIOUS_MINOR: 1
OPT_IN_TEST_NEXT_MINOR: 1
OPT_IN_TEST_NEXT_MAJOR: 1
OPT_IN_TEST_MAX_PHP: 1
_CSPELL_WORDS: 'grecaptcha, sitekey'
......@@ -19,7 +19,7 @@
},
"license": "GPL-2.0-or-later",
"require": {
"drupal/captcha": "^1.4 || ^2",
"google/recaptcha": "^1.2"
"drupal/captcha": "^1.15 || ^2.0",
"google/recaptcha": "^1.3"
}
}
/**
* @file
* Contains the definition of the behaviour recaptcha.
*/
(function ($, Drupal) {
Drupal.behaviors.recaptcha = {
attach(context) {
$('.g-recaptcha', context).each(function () {
if (
typeof grecaptcha === 'undefined' ||
typeof grecaptcha.render !== 'function'
) {
return;
}
if ($(this).closest('body').length > 0) {
if ($(this).hasClass('recaptcha-processed')) {
grecaptcha.reset();
} else {
grecaptcha.render(this, $(this).data());
$(this).addClass('recaptcha-processed');
}
}
});
},
};
window.drupalRecaptchaOnload = function () {
$('.g-recaptcha').each(function () {
if (!$(this).hasClass('recaptcha-processed')) {
grecaptcha.render(this, $(this).data());
$(this).addClass('recaptcha-processed');
}
});
};
})(jQuery, Drupal);
......@@ -2,7 +2,7 @@ name: 'reCAPTCHA'
type: module
description: 'Protect your website from spam and abuse while letting real people pass through with ease.'
package: Spam control
core_version_requirement: '^8.9 || ^9 || ^10'
core_version_requirement: ^10 || ^11
configure: recaptcha.admin_settings_form
dependencies:
- captcha:captcha
recaptcha:
js:
js/recaptcha.js: {}
dependencies:
- core/drupal
- core/jquery
......@@ -8,7 +8,6 @@
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Template\Attribute;
use Drupal\Core\Url;
use Drupal\recaptcha\ReCaptcha\RequestMethod\Drupal8Post;
use ReCaptcha\ReCaptcha;
/**
......@@ -27,7 +26,7 @@ function recaptcha_help($route_name, RouteMatchInterface $route_match) {
$output .= '</dl>';
$output .= '<h3>' . t('Configuration') . '</h3>';
$output .= '<ol>';
$output .= '<li>' . t('Enable reCAPTCHA and CAPTCHA modules in Adminstration > Extend') . '</li>';
$output .= '<li>' . t('Enable reCAPTCHA and CAPTCHA modules in Administration > 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>';
......@@ -82,16 +81,14 @@ function recaptcha_captcha($op, $captcha_type = '') {
// 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')) {
// Check if reCAPTCHA use globally is enabled.
$recaptcha_src_fallback = 'https://www.google.com/recaptcha/api/fallback';
if ($recaptcha_use_globally) {
$recaptcha_src_fallback = 'https://www.recaptcha.net/recaptcha/api/fallback';
}
$recaptcha_widget_noscript = [
'#theme' => 'recaptcha_widget_noscript',
'#widget' => [
......@@ -117,28 +114,15 @@ function recaptcha_captcha($op, $captcha_type = '') {
'#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',
],
'library' => [
'recaptcha/recaptcha',
'recaptcha/google.recaptcha_' . \Drupal::service('language_manager')->getCurrentLanguage()->getId(),
],
],
'#cache' => [
'tags' => ['library_info'],
'contexts' => ['languages'],
],
];
}
else {
......@@ -153,6 +137,46 @@ function recaptcha_captcha($op, $captcha_type = '') {
}
}
/**
* Implements hook_library_info_build().
*/
function recaptcha_library_info_build() {
$libraries = [];
$languages = \Drupal::service('language_manager')->getLanguages();
$config = \Drupal::config('recaptcha.settings');
$use_globally = $config->get('use_globally');
$recaptcha_src = 'https://www.google.com/recaptcha/api.js';
if ($use_globally) {
$recaptcha_src = 'https://www.recaptcha.net/recaptcha/api.js';
}
foreach ($languages as $lang => $language) {
$url = Url::fromUri($recaptcha_src, [
'query' => [
'hl' => $lang,
'render' => 'explicit',
'onload' => 'drupalRecaptchaOnload',
],
'absolute' => TRUE,
])->toString();
$libraries['google.recaptcha_' . $lang] = [
'version' => '1.x',
'header' => TRUE,
'js' => [
$url => [
'type' => 'external',
'minified' => TRUE,
'attributes' => [
'async' => TRUE,
'defer' => TRUE,
],
],
],
];
}
return $libraries;
}
/**
* CAPTCHA Callback; Validates the reCAPTCHA code.
*/
......@@ -167,12 +191,13 @@ function recaptcha_captcha_validation($solution, $response, $element, $form_stat
}
// Use Drupal::httpClient() to circumvent all issues with the Google library.
$recaptcha = new ReCaptcha($recaptcha_secret_key, new Drupal8Post());
$drupal8post = Drupal::service('recaptcha.drupal8post');
$recaptcha = new ReCaptcha($recaptcha_secret_key, $drupal8post);
// Ensures the hostname matches. Required if "Domain Name Validation" is
// disabled for credentials.
if ($config->get('verify_hostname')) {
$recaptcha->setExpectedHostname($_SERVER['SERVER_NAME']);
$recaptcha->setExpectedHostname(\Drupal::request()->getHost());
}
$resp = $recaptcha->verify(
......@@ -228,12 +253,11 @@ function recaptcha_captcha_validation($solution, $response, $element, $form_stat
function template_preprocess_recaptcha_widget_noscript(&$variables) {
$variables['sitekey'] = $variables['widget']['sitekey'];
$variables['language'] = $variables['widget']['language'];
$options = [
$variables['url'] = Url::fromUri($variables['widget']['recaptcha_src_fallback'], [
'query' => [
'k' => $variables['widget']['sitekey'],
'hl' => $variables['widget']['language'],
],
'absolute' => TRUE,
];
$variables['url'] = Url::fromUri($variables['widget']['recaptcha_src_fallback'], $options)->toString();
])->toString();
}
services:
recaptcha.config_subscriber:
class: Drupal\recaptcha\EventSubscriber\RecaptchaSettingsConfigSubscriber
arguments: ['@cache_tags.invalidator']
tags:
- { name: event_subscriber }
recaptcha.drupal8post:
class: Drupal\recaptcha\ReCaptcha\RequestMethod\Drupal8Post
arguments: ['@http_client']
<?php
namespace Drupal\recaptcha\EventSubscriber;
use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
use Drupal\Core\Config\ConfigCrudEvent;
use Drupal\Core\Config\ConfigEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* The recaptcha settings config subscriber.
*
* A subscriber rebuilding the library info when the "use_globally" setting is
* updated.
*/
class RecaptchaSettingsConfigSubscriber implements EventSubscriberInterface {
/**
* The cache tags invalidator.
*
* @var \Drupal\Core\Cache\CacheTagsInvalidatorInterface
*/
protected $cacheTagsInvalidator;
/**
* Constructs a RecaptchaSettingsConfigSubscriber object.
*
* @param \Drupal\Core\Cache\CacheTagsInvalidatorInterface $cache_tags_invalidator
* The cache tags invalidator.
*/
public function __construct(CacheTagsInvalidatorInterface $cache_tags_invalidator) {
$this->cacheTagsInvalidator = $cache_tags_invalidator;
}
/**
* Invalidates the library_info tag.
*
* Invalidates the library_info tag when the value of recaptcha.settings
* use_globally is changed.
*
* @param \Drupal\Core\Config\ConfigCrudEvent $event
* The Event to process.
*/
public function onSave(ConfigCrudEvent $event) {
if ($event->getConfig()->getName() === 'recaptcha.settings') {
if ($event->isChanged('use_globally')) {
$this->cacheTagsInvalidator->invalidateTags(['library_info']);
}
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
$events[ConfigEvents::SAVE][] = ['onSave'];
return $events;
}
}
......@@ -38,7 +38,7 @@ class ReCaptchaAdminSettingsForm extends ConfigFormBase {
$form['general']['recaptcha_site_key'] = [
'#default_value' => $config->get('site_key'),
'#description' => $this->t('The site key given to you when you <a href=":url">register for reCAPTCHA</a>.', [':url' => 'https://www.google.com/recaptcha/admin']),
'#description' => $this->t('The site key given to you when you <a href=":url" target="_blank">register for reCAPTCHA</a>.', [':url' => 'https://www.google.com/recaptcha/admin']),
'#maxlength' => 40,
'#required' => TRUE,
'#title' => $this->t('Site key'),
......@@ -47,7 +47,7 @@ class ReCaptchaAdminSettingsForm extends ConfigFormBase {
$form['general']['recaptcha_secret_key'] = [
'#default_value' => $config->get('secret_key'),
'#description' => $this->t('The secret key given to you when you <a href=":url">register for reCAPTCHA</a>.', [':url' => 'https://www.google.com/recaptcha/admin']),
'#description' => $this->t('The secret key given to you when you <a href=":url" target="_blank">register for reCAPTCHA</a>.', [':url' => 'https://www.google.com/recaptcha/admin']),
'#maxlength' => 40,
'#required' => TRUE,
'#title' => $this->t('Secret key'),
......
......@@ -2,6 +2,7 @@
namespace Drupal\recaptcha\ReCaptcha\RequestMethod;
use GuzzleHttp\ClientInterface;
use ReCaptcha\ReCaptcha;
use ReCaptcha\RequestMethod;
use ReCaptcha\RequestParameters;
......@@ -11,6 +12,21 @@ use ReCaptcha\RequestParameters;
*/
class Drupal8Post implements RequestMethod {
/**
* Guzzle\Client instance.
*
* @var \GuzzleHttp\ClientInterface
*/
protected $httpClient;
/**
* {@inheritdoc}
*/
public function __construct(ClientInterface $http_client) {
$this->httpClient = $http_client;
}
/**
* Submit the POST request with the specified parameters.
*
......@@ -32,7 +48,7 @@ class Drupal8Post implements RequestMethod {
'http_errors' => FALSE,
];
$response = \Drupal::httpClient()->post(ReCaptcha::SITE_VERIFY_URL, $options);
$response = $this->httpClient->post(ReCaptcha::SITE_VERIFY_URL, $options);
if ($response->getStatusCode() == 200) {
// The service request was successful.
......
langcode: en
status: true
formId: recaptcha_test_ajax_form
captchaType: recaptcha/reCAPTCHA
label: recaptcha_test_ajax_form
dependencies:
enforced:
module:
- recaptcha_test
name: 'reCAPTCHA Test'
type: module
description: 'Test module for the recaptcha module.'
package: Testing
core_version_requirement: ^8.9 || ^9 || ^10 || ^11
hidden: true
dependencies:
- fences:recaptcha
recaptcha_test.page:
path: '/recaptcha-test'
defaults:
_controller: '\Drupal\recaptcha_test\Controller\RecaptchaTestAjaxFormController::button'
_title: 'reCAPTCHA Test Ajax Page'
requirements:
# This route is only meant for testing purposes, no permission check needed:
_access: 'TRUE'
recaptcha_test.ajax:
path: '/recaptcha-test/ajax'
defaults:
_controller: '\Drupal\recaptcha_test\Controller\RecaptchaTestAjaxFormController::ajaxForm'
_title: 'reCAPTCHA Test Ajax Controller'
requirements:
# This route is only meant for testing purposes, no permission check needed:
_access: 'TRUE'
recaptcha_test.form:
path: '/recaptcha-test/form'
defaults:
_form: '\Drupal\recaptcha_test\Form\RecaptchaTestAjaxForm'
_title: 'reCAPTCHA Test Ajax Form'
requirements:
# This route is only meant for testing purposes, no permission check needed:
_access: 'TRUE'
<?php
namespace Drupal\recaptcha_test\Controller;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\ReplaceCommand;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Form\FormBuilderInterface;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Recaptcha test AJAX form controller.
*/
class RecaptchaTestAjaxFormController extends ControllerBase implements ContainerInjectionInterface {
/**
* Constructor for form builder.
*/
public function __construct(FormBuilderInterface $form_builder) {
$this->formBuilder = $form_builder;
}
/**
* Container creation method.
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('form_builder')
);
}
/**
* Button rendering method.
*/
public function button() {
$output = [];
$output['container'] = [
'#type' => 'container',
'#attributes' => [
'id' => 'recaptcha-test-container',
],
];
$url = Url::fromRoute('recaptcha_test.ajax', []);
$output['container']['ajax_link'] = [
'#id' => 'load-ajax-form',
'#type' => 'link',
'#title' => $this->t('Load Ajax Form'),
'#url' => $url,
'#attributes' => [
'class' => ['use-ajax', 'button', 'secondary', 'btn', 'btn-secondary'],
],
];
$output['#attached']['library'][] = 'core/drupal.ajax';
// @see https://api.drupal.org/api/drupal/core%21core.api.php/group/ajax/8.2.x
return $output;
}
/**
* Ajax callback returning a form.
*
* @return \Drupal\Core\Ajax\AjaxResponse
* The AJAX response.
*/
public function ajaxForm() {
$form = $this->formBuilder->getForm('Drupal\recaptcha_test\Form\RecaptchaTestAjaxForm');
$ajax = new AjaxResponse();
$ajax->addCommand(new ReplaceCommand('#recaptcha-test-container', $form));
return $ajax;
}
}
<?php
namespace Drupal\recaptcha_test\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
/**
* Recaptcha test AJAX form class.
*/
class RecaptchaTestAjaxForm extends FormBase {
use StringTranslationTrait;
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'recaptcha_test_ajax_form';
}
/**
* Build form method for RecaptchaTestAjaxForm.
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form = [];
$form['messages'] = [
'#type' => 'status_messages',
];
$form['email'] = [
'#type' => 'email',
'#title' => $this->t('Email'),
'#required' => TRUE,
];
$form['submit'] = [
'#type' => 'submit',
'#value' => $this->t('Submit'),
'#validate' => ['::validateForm'],
'#ajax' => [
'callback' => '::ajaxCallback',
'wrapper' => 'recaptcha-test-ajax-form-wrapper',
],
];
$form['#prefix'] = '<div id="recaptcha-test-ajax-form-wrapper">';
$form['#suffix'] = '</div>';
return $form;
}
/**
* Ajax callback method for form.
*/
public function ajaxCallback(array &$form, FormStateInterface $form_state) {
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
$email = $form_state->getValue('email');
if ($email == 'invalid@example.com') {
$form_state->setError($form['email'], 'Invalid email');
}
}
/**
* Form submission method.
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->messenger()->addStatus('Form submit successful.');
}
}
......@@ -115,20 +115,22 @@ class ReCaptchaBasicTest extends BrowserTestBase {
public function testReCaptchaOnLoginForm() {
$site_key = $this->randomMachineName(40);
$secret_key = $this->randomMachineName(40);
$grecaptcha = '<div class="g-recaptcha" data-sitekey="' . $site_key . '" data-theme="light" data-type="image"></div>';
$grecaptchaSelector = "div.g-recaptcha[data-sitekey=$site_key][data-theme=light][data-type=image]";
// Test if login works.
$this->drupalLogin($this->normalUser);
$this->drupalLogout();
$this->drupalGet('user/login');
$this->assertSession()->responseNotContains($grecaptcha);
// reCAPTCHA is not shown on form.
$this->assertSession()->elementNotExists('css', $grecaptchaSelector);
// Enable 'captcha/Math' CAPTCHA on login form.
captcha_set_form_id_setting('user_login_form', 'captcha/Math');
$this->drupalGet('user/login');
$this->assertSession()->responseNotContains($grecaptcha);
// reCAPTCHA is not shown on form.
$this->assertSession()->elementNotExists('css', $grecaptchaSelector);
// Enable 'recaptcha/reCAPTCHA' on login form.
captcha_set_form_id_setting('user_login_form', 'recaptcha/reCAPTCHA');
......@@ -148,21 +150,26 @@ class ReCaptchaBasicTest extends BrowserTestBase {
// Check if there is a reCAPTCHA on the login form.
$this->drupalGet('user/login');
$this->assertSession()->responseContains($grecaptcha);
$options_2 = [
// reCAPTCHA is shown on form.
$this->assertSession()->elementExists('css', $grecaptchaSelector);
$options = [
'query' => [
'hl' => \Drupal::service('language_manager')->getCurrentLanguage()->getId(),
'render' => 'explicit',
'onload' => 'drupalRecaptchaOnload',
],
'absolute' => TRUE,
];
$this->assertSession()->responseContains('<script src="' . Url::fromUri('https://www.google.com/recaptcha/api.js', $options_2)->toString() . '" async defer></script>');
$this->assertSession()->responseNotContains($grecaptcha . '<noscript>');
$this->assertSession()->responseContains(Html::escape(Url::fromUri('https://www.google.com/recaptcha/api.js', $options)->toString()));
// NoScript code is not enabled for the reCAPTCHA.
$this->assertSession()->elementNotExists('css', "$grecaptchaSelector + noscript");
// Test if the fall back url is properly build and noscript code added.
$this->config('recaptcha.settings')->set('widget.noscript', 1)->save();
$this->drupalGet('user/login');
$this->assertSession()->responseContains($grecaptcha . "\n" . '<noscript>');
// NoScript for reCAPTCHA is shown on form.
$this->assertSession()->elementExists('css', "$grecaptchaSelector + noscript");
$options = [
'query' => [
'k' => $site_key,
......@@ -175,13 +182,22 @@ class ReCaptchaBasicTest extends BrowserTestBase {
// Check if there is a reCAPTCHA with global url on the login form.
$this->config('recaptcha.settings')->set('use_globally', TRUE)->save();
$this->drupalGet('user/login');
$options_2 = [
$options = [
'query' => [
'hl' => \Drupal::service('language_manager')->getCurrentLanguage()->getId(),
'render' => 'explicit',
'onload' => 'drupalRecaptchaOnload',
],
'absolute' => TRUE,
];
$this->assertSession()->responseContains(Html::escape(Url::fromUri('https://www.recaptcha.net/recaptcha/api.js', $options)->toString()), '[testReCaptchaOnLoginForm]: Global reCAPTCHA is shown on form.');
$options = [
'query' => [
'k' => $site_key,
'hl' => \Drupal::service('language_manager')->getCurrentLanguage()->getId(),
],
'absolute' => TRUE,
];
$this->assertSession()->responseContains('<script src="' . Url::fromUri('https://www.recaptcha.net/recaptcha/api.js', $options_2)->toString() . '" async defer></script>');
$this->assertSession()->responseContains(Html::escape(Url::fromUri('https://www.recaptcha.net/recaptcha/api/fallback', $options)->toString()));
// Check that data-size attribute does not exists.
......
<?php
namespace Drupal\Tests\recaptcha\Functional;
use Drupal\Core\Url;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
/**
* Test the recaptcha module using JavaScript.
*
* @see https://developers.google.com/recaptcha/docs/faq#id-like-to-run-automated-tests-with-recaptcha-what-should-i-do
*
* @group reCAPTCHA
*
* @dependencies recaptcha
*/
class RecaptchaJavascriptTest extends WebDriverTestBase {
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = ['recaptcha_test'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
// These are test keys that will always validate.
protected const SITE_KEY = '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI';
protected const SECRET_KEY = '6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->config('recaptcha.settings')
->set('site_key', self::SITE_KEY)
->set('secret_key', self::SECRET_KEY)
->save();
}
/**
* Test the recaptcha on a form loaded via ajax that also submits via ajax.
*/
public function testRecaptchaOnAjaxForm() {
// Load the /recaptcha-test page with the AJAX button.
$path = Url::fromRoute('recaptcha_test.page')->toString();
$this->drupalGet($path);
// No recaptcha JS on the page.
$this->assertSession()->responseNotContains('https://www.google.com/recaptcha/api.js', 'reCAPTCHA js is not present before the form is loaded via AJAX.');
// Click the button.
$this->click('a#load-ajax-form');
// Once the form is loaded.
$this->getSession()->wait(2000, '(jQuery("form[data-drupal-selector^=recaptcha-test-ajax-form]").length > 0)');
$this->assertJsCondition('Drupal.behaviors.recaptcha', 100, 'recaptcha Drupal behaviors found.');
// The recaptcha should be on the page.
$this->assertSession()->responseContains('https://www.google.com/recaptcha/api.js', 'reCAPTCHA js has been added.');
$grecaptcha = $this->getSession()->getPage()->find('css', 'form .g-recaptcha');
$this->assertJsCondition('window.grecaptcha !== undefined', 1000, 'The Google recaptcha library is loaded.');
$this->assertNotEmpty($grecaptcha, 'g-recaptcha element is found.');
// Test form submission.
// First, try a submission that will trigger the validation error handler.
$this->submitForm([
'email' => 'invalid@example.com',
], 'Submit');
$messages = $this->getMessages();
$this->assertNotEmpty($messages);
$this->assertStringContainsString('Invalid email', $messages);
$this->assertStringContainsString('The answer you entered for the CAPTCHA was not correct.', $messages);
$this->assertStringNotContainsString('Form submit successful.', $messages);
// Now submit again with a valid email.
$this->submitForm([
'email' => 'valid@example.com',
], 'Submit');
$messages = $this->getMessages();
$this->assertStringContainsString('The answer you entered for the CAPTCHA was not correct.', $messages);
$this->assertStringNotContainsString('Form submit successful.', $messages);
// We need to re-validate the captcha;
// So click it,.
$this->clickRecaptcha();
// And submit for the last time.
$this->submitForm([
'email' => 'valid@email.com',
], 'Submit');
$messages = $this->getMessages();
$this->assertStringNotContainsString('The answer you entered for the CAPTCHA was not correct.', $messages);
$this->assertStringContainsString('Form submit successful.', $messages);
}
/**
* {@inheritdoc}
*/
protected function submitForm(array $edit, $submit, $form_html_id = NULL) {
parent::submitForm($edit, $submit, $form_html_id);
// Because we're submitting the form via AJAX give it 500ms before we test
// anything else with the response.
$this->getSession()->wait(500);
}
/**
* Click the captcha checkbox element and wait for it to be validated.
*
* @param int $timeout
* The time to wait for the recaptcha to get validated (in miliseconds).
*
* @throws \Behat\Mink\Exception\DriverException
* @throws \Behat\Mink\Exception\UnsupportedDriverActionException
*/
protected function clickRecaptcha($timeout = 2000) {
$driver = $this->getSession()->getDriver();
$recaptchaIFrame = $this->getSession()->getPage()->find('css', 'form .g-recaptcha iframe');
$driver->switchToIFrame($recaptchaIFrame->getAttribute('name'));
$recaptchaCheckbox = $driver->find('//span[@id="recaptcha-anchor"]');
if (!empty($recaptchaCheckbox)) {
$recaptchaCheckbox[0]->click();
$this->getSession()->wait($timeout, 'document.getElementById("recaptcha-anchor").attributes["aria-checked"].value === true;');
}
else {
$this->fail('Unable to find recaptcha checkbox.');
}
$driver->switchToWindow();
$this->assertJsCondition('grecaptcha.getResponse() !== ""', $timeout, 'grecaptcha has response.');
}
/**
* Search for messages in the last html page response.
*
* @return string
* The message.
*/
public function getMessages($timeout = 2000) {
$this->getSession()->wait($timeout, '(jQuery("div[data-drupal-messages]").length > 0)');
$page = $this->getSession()->getPage();
$messages = $page->findAll('css', 'div[data-drupal-messages]');
$text = '';
if (isset($messages)) {
foreach ($messages as $message) {
$text .= $message->getText();
}
}
return $text;
}
}
......@@ -21,6 +21,6 @@ class ValidateD7MigrationStateTest extends MigrateDrupal7TestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['recaptcha'];
protected static $modules = ['recaptcha'];
}