Skip to content

Commit 3656e35

Browse files
committed
Allow developers to handle form submission separately.
1 parent cfb35f7 commit 3656e35

File tree

4 files changed

+163
-17
lines changed

4 files changed

+163
-17
lines changed

README.md

+45
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ UndefinedOffset\NoCaptcha\Forms\NocaptchaField:
4545
default_type: "image" #Default captcha type (optional, image or audio, defaults to image)
4646
default_size: "normal" #Default size (optional, normal, compact or invisible, defaults to normal)
4747
default_badge: "bottomright" #Default badge position (bottomright, bottomleft or inline, defaults to bottomright)
48+
default_handle_submit: true #Default setting for whether nocaptcha should handle form submission. See "Handling form submission" below.
4849
proxy_server: "" #Your proxy server address (optional)
4950
proxy_port: "" #Your proxy server address port (optional)
5051
proxy_auth: "" #Your proxy server authentication information (optional)
@@ -124,6 +125,50 @@ $captchaField->setMinimumScore(0.2);
124125
For more information about version 3, including how to implement custom actions
125126
see https://developers.google.com/recaptcha/docs/v3
126127

128+
## Handling form submission
129+
By default, the javascript included with this module will add a submit event handler to your form.
130+
131+
If you need to handle form submissions in a special way (for example to support front-end validation),
132+
you can choose to handle form submit events yourself.
133+
134+
This can be configured site-wide using the Config API
135+
```yml
136+
UndefinedOffset\NoCaptcha\Forms\NocaptchaField:
137+
default_handle_submit: false
138+
```
139+
140+
Or on a per form basis:
141+
```php
142+
$captchaField = $form->Fields()->fieldByName('Captcha');
143+
$captchaField->setHandleSubmitEvents(false);
144+
```
145+
146+
With this configuration no event handlers will be added by this module to your form. Instead, a
147+
function will be provided called `nocaptcha_handleCaptcha` which you can call from your code
148+
when you're ready to submit your form. It has the following signature:
149+
```js
150+
function nocaptcha_handleCaptcha(form, callback)
151+
```
152+
`form` must be the form element, and `callback` should be a function that finally submits the form,
153+
though it is optional.
154+
155+
In the simplest case, you can use it like this:
156+
```js
157+
document.addEventListener("DOMContentLoaded", function(event) {
158+
// where formID is the element ID for your form
159+
const form = document.getElementById(formID);
160+
const submitListener = function(event) {
161+
event.preventDefault();
162+
let valid = true;
163+
/* Your validation logic here */
164+
if (valid) {
165+
nocaptcha_handleCaptcha(form, form.submit.bind(form));
166+
}
167+
};
168+
form.addEventListener('submit', submitListener);
169+
});
170+
```
171+
127172
## Reporting an issue
128173

129174
When you're reporting an issue please ensure you specify what version of
+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
var _noCaptchaFields=_noCaptchaFields || [];
2+
3+
// form isn't necessary for v2, but we have it here to keep the function signature consistent.
4+
function nocaptcha_handleCaptcha(form, callback) {
5+
if (callback) {
6+
callback();
7+
}
8+
};
9+
10+
function noCaptchaFieldRender() {
11+
var render = function(field) {
12+
var options={
13+
'sitekey': field.getAttribute('data-sitekey'),
14+
'theme': field.getAttribute('data-theme'),
15+
'type': field.getAttribute('data-type'),
16+
'size': field.getAttribute('data-size'),
17+
'badge': field.getAttribute('data-badge'),
18+
};
19+
20+
var widget_id = grecaptcha.render(field, options);
21+
field.setAttribute("data-widgetid", widget_id);
22+
}
23+
24+
for(var i=0;i<_noCaptchaFields.length;i++) {
25+
render(document.getElementById('Nocaptcha-'+_noCaptchaFields[i]));
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
function nocaptcha_handleCaptcha(form, callback) {
2+
var input = document.getElementById('Nocaptcha-' + form.getAttribute('id'));
3+
grecaptcha.execute(input.getAttribute('data-sitekey'), {action: 'submit'}).then(function (token) {
4+
input.value = token;
5+
if (callback) {
6+
callback();
7+
}
8+
});
9+
};

src/Forms/NocaptchaField.php

+82-17
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,14 @@ class NocaptchaField extends FormField {
8282
*/
8383
private static $default_size='normal';
8484

85+
/**
86+
* Whether form submit events are handled directly by this module.
87+
* If false, a function is provided that can be called by user code submit handlers.
88+
* @var boolean
89+
* @default true
90+
*/
91+
private static $default_handle_submit = true;
92+
8593
/**
8694
* Recaptcha Site Key
8795
* Configurable via Injector config
@@ -150,6 +158,13 @@ class NocaptchaField extends FormField {
150158
*/
151159
protected $minimumScore;
152160

161+
/**
162+
* Whether form submit events are handled directly by this module.
163+
* If false, a function is provided that can be called by user code submit handlers.
164+
* @var boolean
165+
*/
166+
private $handleSubmitEvents;
167+
153168
/**
154169
* Creates a new Recaptcha 2 field.
155170
* @param string $name The internal field name, passed to forms.
@@ -165,6 +180,7 @@ public function __construct($name, $title=null, $value=null) {
165180
$this->_captchaType=self::config()->default_type;
166181
$this->_captchaSize=self::config()->default_size;
167182
$this->_captchaBadge=self::config()->default_badge;
183+
$this->handleSubmitEvents = self::config()->default_handle_submit;
168184
}
169185

170186
/**
@@ -180,44 +196,71 @@ public function Field($properties=array()) {
180196
user_error('You must configure Nocaptcha.site_key and Nocaptcha.secret_key, you can retrieve these at https://google.com/recaptcha', E_USER_ERROR);
181197
}
182198

183-
$form = $this->getForm();
184-
185199
if ($this->config()->get('recaptcha_version') == 2) {
186-
$exemptActionsString = implode("' , '", $form->getValidationExemptActions());
200+
$this->configureRequirementsForV2();
201+
} else {
202+
$this->configureRequirementsForV3();
203+
}
187204

205+
return parent::Field($properties);
206+
}
207+
208+
/**
209+
* Configure any javascript and css requirements that are specific for recaptcha v2.
210+
*/
211+
protected function configureRequirementsForV2()
212+
{
213+
Requirements::customScript(
214+
"(function() {\n" .
215+
"var gr = document.createElement('script'); gr.type = 'text/javascript'; gr.async = true;\n" .
216+
"gr.src = ('https:' == document.location.protocol ? 'https://www' : 'http://www') + " .
217+
"'.google.com/recaptcha/api.js?render=explicit&hl=" .
218+
Locale::getPrimaryLanguage(i18n::get_locale()) .
219+
"&onload=noCaptchaFieldRender';\n" .
220+
"var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(gr, s);\n" .
221+
"})();\n",
222+
'NocaptchaField-lib'
223+
);
224+
if ($this->getHandleSubmitEvents()) {
225+
$exemptActionsString = implode("' , '", $this->getForm()->getValidationExemptActions());
188226
Requirements::javascript('undefinedoffset/silverstripe-nocaptcha:javascript/NocaptchaField.js');
189227
Requirements::customScript(
190228
"var _noCaptchaFields=_noCaptchaFields || [];_noCaptchaFields.push('".$this->ID()."');" .
191229
"var _noCaptchaValidationExemptActions=_noCaptchaValidationExemptActions || [];" .
192230
"_noCaptchaValidationExemptActions.push('" . $exemptActionsString . "');",
193231
"NocaptchaField-" . $this->ID()
194232
);
233+
} else {
195234
Requirements::customScript(
196-
"(function() {\n" .
197-
"var gr = document.createElement('script'); gr.type = 'text/javascript'; gr.async = true;\n" .
198-
"gr.src = ('https:' == document.location.protocol ? 'https://www' : 'http://www') + " .
199-
"'.google.com/recaptcha/api.js?render=explicit&hl=" .
200-
Locale::getPrimaryLanguage(i18n::get_locale()) .
201-
"&onload=noCaptchaFieldRender';\n" .
202-
"var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(gr, s);\n" .
203-
"})();\n",
204-
'NocaptchaField-lib'
235+
"var _noCaptchaFields=_noCaptchaFields || [];_noCaptchaFields.push('".$this->ID()."');",
236+
"NocaptchaField-" . $this->ID()
205237
);
206-
} else {
207-
Requirements::javascript('https://www.google.com/recaptcha/api.js?render=' . urlencode($siteKey) . '&onload=noCaptchaFormRender');
238+
Requirements::javascript('undefinedoffset/silverstripe-nocaptcha:javascript/NocaptchaField_noHandler_v2.js');
239+
}
240+
}
241+
242+
/**
243+
* Configure any javascript and css requirements that are specific for recaptcha v3.
244+
*/
245+
protected function configureRequirementsForV3()
246+
{
247+
Requirements::customCSS('.nocaptcha { display: none !important; }', self::class);
248+
if ($this->getHandleSubmitEvents()) {
249+
Requirements::javascript('https://www.google.com/recaptcha/api.js?render=' . urlencode($this->getSiteKey()) . '&onload=noCaptchaFormRender');
208250
Requirements::javascript('undefinedoffset/silverstripe-nocaptcha:javascript/NocaptchaField_v3.js');
209-
Requirements::customCSS('.nocaptcha { display: none !important; }', self::class);
210251

252+
$form = $this->getForm();
211253
$helper = $form->getTemplateHelper();
212254
$id = $helper->generateFormID($form);
213255

214256
Requirements::customScript(
215257
"var _noCaptchaForms=_noCaptchaForms || [];_noCaptchaForms.push('". $id . "');",
216258
'NocaptchaForm-' . $id
217259
);
260+
} else {
261+
Requirements::javascript('https://www.google.com/recaptcha/api.js?render=' . urlencode($this->getSiteKey()));
262+
Requirements::javascript('undefinedoffset/silverstripe-nocaptcha:javascript/NocaptchaField_noHandler_v3.js');
218263
}
219-
220-
return parent::Field($properties);
221264
}
222265

223266
/**
@@ -294,6 +337,28 @@ public function validate($validator) {
294337
return true;
295338
}
296339

340+
/**
341+
* Sets whether form submit events are handled directly by this module.
342+
*
343+
* @param boolean $value
344+
* @return NocaptchaField
345+
*/
346+
public function setHandleSubmitEvents(bool $value)
347+
{
348+
$this->handleSubmitEvents = $value;
349+
return $this;
350+
}
351+
352+
/**
353+
* Get whether form submit events are handled directly by this module.
354+
*
355+
* @return boolean
356+
*/
357+
public function getHandleSubmitEvents(): bool
358+
{
359+
return $this->handleSubmitEvents;
360+
}
361+
297362
/**
298363
* Sets the theme for this captcha
299364
* @param string $value Theme to set it to, currently the api supports light and dark

0 commit comments

Comments
 (0)