Skip to content

Commit 0683e73

Browse files
committed
Fix Snaptcha Captcha support for Ajax forms
1 parent de6694d commit 0683e73

File tree

8 files changed

+126
-39
lines changed

8 files changed

+126
-39
lines changed

src/controllers/FormsController.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,7 @@ public function actionRefreshTokens(): Response
384384
// Add captchas into the payload
385385
$formHandle = $this->request->getRequiredParam('form');
386386
$form = Formie::$plugin->getForms()->getFormByHandle($formHandle);
387+
387388
// Force fetch captchas because we're dealing with potential ajax forms
388389
// Normally, this function returns only if the `showAllPages` property is set.
389390
$captchas = Formie::$plugin->getIntegrations()->getAllEnabledCaptchasForForm($form, null, true);

src/integrations/captchas/Snaptcha.php

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use verbb\formie\elements\Form;
66

77
use Craft;
8+
use craft\helpers\Html;
89

910
use putyourlightson\snaptcha\models\SnaptchaModel;
1011
use putyourlightson\snaptcha\Snaptcha as SnaptchaPlugin;
@@ -40,15 +41,35 @@ public function getDescription(): string
4041
}
4142

4243
public function getFrontEndHtml(Form $form, $page = null): string
44+
{
45+
return Html::tag('div', null, [
46+
'class' => 'formie-snaptcha-captcha-placeholder',
47+
'data-snaptcha-captcha-placeholder' => true,
48+
]);
49+
}
50+
51+
public function getFrontEndJsVariables(Form $form, $page = null): ?array
4352
{
4453
$model = new SnaptchaModel();
4554
$fieldName = SnaptchaPlugin::$plugin->settings->fieldName;
4655
$fieldValue = SnaptchaPlugin::$plugin->snaptcha->getFieldValue($model) ?? '';
4756

48-
return '<input type="hidden" name="' . $fieldName . '" value="' . $fieldValue . '">';
57+
$settings = [
58+
'formId' => $form->getFormId(),
59+
'sessionKey' => $fieldName,
60+
'value' => $fieldValue,
61+
];
62+
63+
$src = Craft::$app->getAssetManager()->getPublishedUrl('@verbb/formie/web/assets/frontend/dist/', true, 'js/captchas/snaptcha.js');
64+
65+
return [
66+
'src' => $src,
67+
'module' => 'FormieSnaptchaCaptcha',
68+
'settings' => $settings,
69+
];
4970
}
5071

51-
public function getGqlVariables(Form $form, $page = null): array
72+
public function getRefreshJsVariables(Form $form, $page = null): array
5273
{
5374
$model = new SnaptchaModel();
5475
$fieldName = SnaptchaPlugin::$plugin->settings->fieldName;
@@ -60,5 +81,10 @@ public function getGqlVariables(Form $form, $page = null): array
6081
'value' => $fieldValue,
6182
];
6283
}
84+
85+
public function getGqlVariables(Form $form, $page = null): array
86+
{
87+
return $this->getRefreshJsVariables($form, $page);
88+
}
6389

6490
}

src/web/assets/frontend/dist/js/captchas/snaptcha.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/web/assets/frontend/dist/js/formie.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/web/assets/frontend/dist/mix-manifest.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"/js/captchas/friendly-captcha.js": "/js/captchas/friendly-captcha.js",
3232
"/js/captchas/turnstile.js": "/js/captchas/turnstile.js",
3333
"/js/captchas/captcha-eu.js": "/js/captchas/captcha-eu.js",
34+
"/js/captchas/snaptcha.js": "/js/captchas/snaptcha.js",
3435
"/js/payments/payment-provider.js": "/js/payments/payment-provider.js",
3536
"/js/payments/stripe.js": "/js/payments/stripe.js",
3637
"/js/payments/paypal.js": "/js/payments/paypal.js",
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { FormieCaptchaProvider } from './captcha-provider';
2+
import { eventKey } from '../utils/utils';
3+
4+
export class FormieSnaptchaCaptcha extends FormieCaptchaProvider {
5+
constructor(settings = {}) {
6+
super(settings);
7+
8+
this.$form = settings.$form;
9+
this.form = this.$form.form;
10+
this.sessionKey = settings.sessionKey;
11+
this.value = settings.value;
12+
13+
this.$placeholder = this.$form.querySelector('[data-snaptcha-captcha-placeholder]');
14+
15+
if (!this.$placeholder) {
16+
console.error('Unable to find Snaptcha Captcha placeholder for [data-snaptcha-captcha-placeholder]');
17+
18+
return;
19+
}
20+
21+
this.createInput();
22+
23+
// Each form submission, we should fetch a new token
24+
this.form.addEventListener(this.$form, eventKey('onAfterFormieSubmit', 'SnaptchaCaptcha'), this.onAfterSubmit.bind(this));
25+
}
26+
27+
createInput() {
28+
// We need to handle re-initializing, so always empty the placeholder to start fresh to prevent duplicate captchas
29+
this.$placeholder.innerHTML = '';
30+
31+
const $input = document.createElement('input');
32+
$input.setAttribute('type', 'text');
33+
$input.setAttribute('name', this.sessionKey);
34+
$input.value = this.value;
35+
36+
this.$placeholder.appendChild($input);
37+
}
38+
39+
onAfterSubmit(e) {
40+
// Refresh all tokens, but just grab the Snaptcha token
41+
Formie.refreshFormTokens(this.form, (data) => {
42+
this.sessionKey = data.captchas?.snaptcha?.sessionKey;
43+
this.value = data.captchas?.snaptcha?.value;
44+
45+
this.createInput();
46+
47+
// Update the form's hash (if using Formie's themed JS)
48+
if (this.form.formTheme) {
49+
this.form.formTheme.updateFormHash();
50+
}
51+
}, false);
52+
}
53+
}
54+
55+
window.FormieSnaptchaCaptcha = FormieSnaptchaCaptcha;

src/web/assets/frontend/src/js/formie-lib.js

Lines changed: 38 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ export class Formie {
321321
return CSRF.initPromise;
322322
}
323323

324-
refreshFormTokens(form, callback) {
324+
refreshFormTokens(form, callback, updateForm = true) {
325325
const { formHashId, formHandle } = form.config;
326326
const url = form.settings.refreshTokenUrl.replace('FORM_PLACEHOLDER', formHandle);
327327

@@ -348,50 +348,52 @@ export class Formie {
348348
// Fetch the form we want to deal with
349349
const { $form } = form;
350350

351-
// Update the CSRF input
352-
if (result.csrf && result.csrf.param) {
353-
const $csrfInput = $form.querySelector(`input[name="${result.csrf.param}"]`);
351+
if (updateForm) {
352+
// Update the CSRF input
353+
if (result.csrf && result.csrf.param) {
354+
const $csrfInput = $form.querySelector(`input[name="${result.csrf.param}"]`);
354355

355-
if ($csrfInput) {
356-
$csrfInput.value = result.csrf.token;
356+
if ($csrfInput) {
357+
$csrfInput.value = result.csrf.token;
357358

358-
if (form.settings.outputConsoleMessages) {
359-
console.log(`${formHashId}: Refreshed CSRF input %o.`, result.csrf);
359+
if (form.settings.outputConsoleMessages) {
360+
console.log(`${formHashId}: Refreshed CSRF input %o.`, result.csrf);
361+
}
362+
} else {
363+
console.error(`${formHashId}: Unable to locate CSRF input for "${result.csrf.param}".`);
360364
}
361365
} else {
362-
console.error(`${formHashId}: Unable to locate CSRF input for "${result.csrf.param}".`);
366+
console.error(`${formHashId}: Missing CSRF token information in cache-refresh response.`);
363367
}
364-
} else {
365-
console.error(`${formHashId}: Missing CSRF token information in cache-refresh response.`);
366-
}
367368

368-
// Update any captchas
369-
if (result.captchas) {
370-
Object.entries(result.captchas).forEach(([key, value]) => {
371-
// In some cases, the captcha input might not have loaded yet, as some are dynamically created
372-
// (see Duplicate and JS captchas). So wait for the element to exist first
373-
waitForElement(`input[name="${value.sessionKey}"]`, $form).then(($captchaInput) => {
374-
if (value.value) {
375-
$captchaInput.value = value.value;
376-
377-
if (form.settings.outputConsoleMessages) {
378-
console.log(`${formHashId}: Refreshed "${key}" captcha input %o.`, value);
369+
// Update any captchas
370+
if (result.captchas) {
371+
Object.entries(result.captchas).forEach(([key, value]) => {
372+
// In some cases, the captcha input might not have loaded yet, as some are dynamically created
373+
// (see Duplicate and JS captchas). So wait for the element to exist first
374+
waitForElement(`input[name="${value.sessionKey}"]`, $form).then(($captchaInput) => {
375+
if (value.value) {
376+
$captchaInput.value = value.value;
377+
378+
if (form.settings.outputConsoleMessages) {
379+
console.log(`${formHashId}: Refreshed "${key}" captcha input %o.`, value);
380+
}
379381
}
380-
}
381-
});
382+
});
382383

383-
// Add a timeout purely for logging, in case the element doesn't resolve in a reasonable time
384-
setTimeout(() => {
385-
if (!$form.querySelector(`input[name="${value.sessionKey}"]`)) {
386-
console.error(`${formHashId}: Unable to locate captcha input for "${key}".`);
387-
}
388-
}, 10000);
389-
});
390-
}
384+
// Add a timeout purely for logging, in case the element doesn't resolve in a reasonable time
385+
setTimeout(() => {
386+
if (!$form.querySelector(`input[name="${value.sessionKey}"]`)) {
387+
console.error(`${formHashId}: Unable to locate captcha input for "${key}".`);
388+
}
389+
}, 10000);
390+
});
391+
}
391392

392-
// Update the form's hash (if using Formie's themed JS)
393-
if (form.formTheme) {
394-
form.formTheme.updateFormHash();
393+
// Update the form's hash (if using Formie's themed JS)
394+
if (form.formTheme) {
395+
form.formTheme.updateFormHash();
396+
}
395397
}
396398

397399
// Fire a callback for users to do other bits

src/web/assets/frontend/webpack.mix.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ mix.js('./src/js/captchas/hcaptcha.js', 'js/captchas');
4646
mix.js('./src/js/captchas/friendly-captcha.js', 'js/captchas');
4747
mix.js('./src/js/captchas/turnstile.js', 'js/captchas');
4848
mix.js('./src/js/captchas/captcha-eu.js', 'js/captchas');
49+
mix.js('./src/js/captchas/snaptcha.js', 'js/captchas');
4950
mix.js('./src/js/payments/payment-provider.js', 'js/payments');
5051
mix.js('./src/js/payments/stripe.js', 'js/payments');
5152
mix.js('./src/js/payments/paypal.js', 'js/payments');

0 commit comments

Comments
 (0)