Problem/Motivation

Short history: reCatpcha still doesn't work correctly with AJAX Webforms. Once you submit the webform with an error (or don't complete the reCaptcha), the reCaptcha is gone and so the form can't be submitted anymore.

This is fixed in #2493183: Ajax support / Use behaviors (RTBC). If the patch is applied, the COOKiES Captcha protection doesn't work anymore. The captcha is always visible.

Steps to reproduce

See above

Proposed resolution

Make cookies_recapchta handle both situations, with the old code and with the new (patch) code. So we're compatible in both directions.

Remaining tasks

User interface changes

API changes

Data model changes

Issue fork cookies-3296032

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

Anybody created an issue. See original summary.

anybody’s picture

StatusFileSize
new25.61 KB

I had a deeper look and found the reason:

in cookies_recaptcha.module we can find this code:

/**
 * Implements hook_captcha().
 */
function cookies_recaptcha_captcha($op, $captcha_type = '') {
  switch ($op) {
    case 'list':
      return ['reCAPTCHA'];

    case 'generate':
      $doKo = CookiesKnockOutService::getInstance()->doKnockOut();
      if ($doKo) {
        $captcha = recaptcha_captcha($op, 'reCAPTCHA');
        if(isset($captcha["form"]["recaptcha_widget"]["#attached"]["html_head"])) {
          foreach ($captcha["form"]["recaptcha_widget"]["#attached"]["html_head"] as $key => $head_tag) {
            if (in_array('recaptcha_api', $head_tag)) {
              $captcha["form"]["recaptcha_widget"]["#attached"]["html_head"][$key][0]['#attributes'] += [
                'type' => 'application/json',
                'id' => 'cookies_recaptcha',
              ];
              $captcha["form"]["recaptcha_widget"]["#attached"]["library"][] = 'cookies_recaptcha/default';
              break;
            }
          }
        }
        return $captcha;
      }
  }
}

But in the patch the #attached part is changed and $captcha["form"]["recaptcha_widget"]["#attached"] is no more as expected:
recaptcha ajax

It now uses libraries (which is good), but our code has to be aligned accordingly. As written above it should be able to handle both cases for now. I guess we should add a further if and comments.

anybody’s picture

Status: Active » Needs work

@JFeltkamp, I'm unsure if that also means we have to knockout the recaptcha libraries... what do you think? Is there any similar example? All I found use html_head.

"recaptcha/google.recaptcha_XXX" is even external. And dynamically generated, see this part of the patch:

 /**
+ * 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');
+    $default_src = $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;
+ }
guido_s’s picture

Yes, libraries need to be blocked if they contain external resources or set cookies.

Maybe something like this?

cookies_recaptcha.module

/**
 * Implements hook_captcha().
 */
function cookies_recaptcha_captcha($op, $captcha_type = '') {
  switch ($op) {
    case 'list':
      return ['reCAPTCHA'];

    case 'generate':
      $doKo = CookiesKnockOutService::getInstance()->doKnockOut();
      if ($doKo) {
        $captcha = recaptcha_captcha($op, 'reCAPTCHA');
        if(isset($captcha["form"]["recaptcha_widget"]["#attached"]["html_head"])) {
          foreach ($captcha["form"]["recaptcha_widget"]["#attached"]["html_head"] as $key => $head_tag) {
            if (in_array('recaptcha_api', $head_tag)) {
              $captcha["form"]["recaptcha_widget"]["#attached"]["html_head"][$key][0]['#attributes'] += [
                'type' => 'text/plain',
                'id' => 'cookies_recaptcha',
              ];
              $captcha["form"]["recaptcha_widget"]["#attached"]["library"][] = 'cookies_recaptcha/default';
              break;
            }
          }
        }
        // If the recaptcha Ajax patch is used check for libraries instead.
        if(isset($captcha["form"]["recaptcha_widget"]["#attached"]["library"])) {
          foreach ($captcha["form"]["recaptcha_widget"]["#attached"]["library"] as $key => $library) {
            if (strpos($library, 'recaptcha/') === 0) {
              /**
               * List the removed libraries in drupalSettings to make them
               * available for ajax requests.
               */
              $captcha["form"]["recaptcha_widget"]["#attached"]["drupalSettings"]["cookies_recaptcha"]["libraries"][] = $library;
              unset($captcha["form"]["recaptcha_widget"]["#attached"]["library"][$key]);
            }
          }
          $captcha["form"]["recaptcha_widget"]["#attached"]["library"][] = 'cookies_recaptcha/default';
        }
        return $captcha;
      }
  }
}

Create an Ajax Controller

src/Controller/AjaxController.php

namespace Drupal\cookies_recaptcha\Controller;

use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

/**
 * Returns responses for Cookies recaptcha routes.
 */
class AjaxController extends ControllerBase {

  /**
   * Load requested libraries.
   *
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The HTTP request.
   *
   * @return \Drupal\Core\Ajax\AjaxResponse
   *   The AjaxResponse object.
   */
  public static function ajaxLoadLibraries(Request $request) {
    $libraries = $request->request->get('libraries');
    if (!isset($libraries)) {
      throw new NotFoundHttpException();
    }
    $libraries = json_decode($libraries);
    if (isset($libraries) && is_array($libraries)) {
      $attachments = [
        'library' => [],
      ];
      foreach ($libraries as $key => $library) {
        $attachments['library'][] = $library;
      }
      $response = new AjaxResponse();
      $response->addAttachments($attachments);
      return $response;
    }
    else {
      throw new NotFoundHttpException();
    }
  }

}

Create a route for this:

cookies_recaptcha.routing.yml

cookies_recaptcha.load_libraries:
  path: '/cookies-recaptcha/load-libraries'
  defaults:
    _title: 'Ajax Callback'
    _controller: '\Drupal\cookies_recaptcha\Controller\AjaxController::ajaxLoadLibraries'
  methods: [POST]
  requirements:
    _permission: 'access content'
    _format: json

And do some ajax request in js/cookies_recaptcha.js if libraries are set in drupalSettings?

if (typeof drupalSettings.cookies_recaptcha.libraries !== 'undefined') {
}

But I couldn't get this to work yet.
Can anyone help with this?
We need to get this working for GDPR / DSGVO.

myha’s picture

I played further with @Guido_S code and generated patch. I didn't play with legacy recaptcha code so the patch will work only with recaptcha patch .

anybody’s picture

@myha thanks, could you prepare this as MR?

guido_s’s picture

It seems the patch doesn't apply anymore and needs an update for the current cookies module version 1.2.2.

frondeau’s picture

dilnaekio’s picture

Hello,

I tried to apply this patch on 1.2.3 version and it's not working anymore :(
It needs a new update for the current version :)

berramou’s picture

This patch works for me with:
Custom form (built with form API)
Webform
Webform in a modal with Ajax

phannphong’s picture

This patch works with the version 1.2.13 when the ticket #2493183: Ajax support / Use behaviors is fixed.