diff --git a/config/install/recaptcha.settings.yml b/config/install/recaptcha.settings.yml index 00ec248..6c7165d 100644 --- a/config/install/recaptcha.settings.yml +++ b/config/install/recaptcha.settings.yml @@ -4,5 +4,6 @@ widget: theme: 'light' type: 'image' size: '' + badge: 'bottomright' tabindex: 0 noscript: false diff --git a/config/schema/recaptcha.schema.yml b/config/schema/recaptcha.schema.yml index dd90863..866c02f 100644 --- a/config/schema/recaptcha.schema.yml +++ b/config/schema/recaptcha.schema.yml @@ -23,6 +23,9 @@ recaptcha.settings: size: type: string label: 'Size' + badge: + type: string + label: 'Badge' tabindex: type: integer label: 'Tabindex' diff --git a/js/recaptcha.invisible.js b/js/recaptcha.invisible.js new file mode 100644 index 0000000..d9bd24d --- /dev/null +++ b/js/recaptcha.invisible.js @@ -0,0 +1,87 @@ +/** + * @file + * Invisible reCaptcha behaviors. + */ +/** + * The submit object that was clicked. + * + * @type {object} + */ +var clickedSubmit = ''; +var clickedSubmitEvent = ''; + +/** + * reCaptcha data-callback that submits the form. + * + * @param token + * The validation token. + */ +function recapthcaOnInvisibleSubmit(token) { + jQuery(clickedSubmit).trigger(clickedSubmitEvent); + clickedSubmit = ''; +} + +(function ($, Drupal) { + /** + * Handles the submission of the form with the invisible reCaptcha. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches the behavior for the invisible reCaptcha. + */ + Drupal.behaviors.invisibleRecaptcha = { + attach: function (context) { + if (Drupal.hasOwnProperty('Ajax')) { + Drupal.Ajax.prototype.beforeSubmit = function (form_values, element, options) { + if ($(this.element).is(clickedSubmit) && grecaptcha.getResponse().length === 0) { + options.needsRevalidate = true; + } + else if ($(this.element).is(clickedSubmit) && grecaptcha.getResponse().length !== 0) { + this.progress.type = 'none'; + } + }; + $(document).ajaxSend(function( event, jqxhr, settings ) { + if (settings.needsRevalidate) { + jqxhr.abort(); + } + }); + } + $('form', context).each(function () { + var $form = $(this); + if ($form.find('.g-recaptcha[data-size="invisible"]').length) { + $form.find(':submit').on({ + mousedown: function ( e ) { + preventFormSubmit(e, this); + $(this).trigger('click'); + }, + click: function ( e ) { + preventFormSubmit(e, this); + } + }); + } + }); + function preventFormSubmit(event, elem) { + if (clickedSubmit.length === 0) { + // We need validate form, to avoid prevention of html5 validation. + if ($(elem).closest('form')[0].checkValidity()) { + event.preventDefault(); + event.stopPropagation(); + clickedSubmitEvent = event.type; + validateInvisibleCaptcha(elem); + } + } + } + /** + * Triggers the reCaptcha to validate the form. + * + * @param {object} button + * The submit button object was clicked. + */ + function validateInvisibleCaptcha(button) { + clickedSubmit = button; + grecaptcha.execute(); + } + } + } +})(jQuery, Drupal); diff --git a/recaptcha.libraries.yml b/recaptcha.libraries.yml new file mode 100644 index 0000000..6ecef6d --- /dev/null +++ b/recaptcha.libraries.yml @@ -0,0 +1,7 @@ +recaptcha.invisible: + version: VERSION + js: + js/recaptcha.invisible.js: {} + dependencies: + - core/jquery + - core/drupal \ No newline at end of file diff --git a/recaptcha.module b/recaptcha.module index 674fa36..f05d7c1 100644 --- a/recaptcha.module +++ b/recaptcha.module @@ -102,6 +102,14 @@ function recaptcha_captcha($op, $captcha_type = '') { 'data-size' => $config->get('widget.size'), 'data-tabindex' => $config->get('widget.tabindex'), ]; + + // Set the callback for invisible captcha. + if ('invisible' === $config->get('widget.size')) { + $attributes['data-badge'] = $config->get('widget.badge'); + $attributes['data-callback'] = 'recapthcaOnInvisibleSubmit'; + $captcha['form']['#attached']['library'][] = 'recaptcha/recaptcha.invisible'; + } + // Filter out empty tabindex/size. $attributes = array_filter($attributes); diff --git a/src/Form/ReCaptchaAdminSettingsForm.php b/src/Form/ReCaptchaAdminSettingsForm.php index fd2ca6d..8a7013d 100644 --- a/src/Form/ReCaptchaAdminSettingsForm.php +++ b/src/Form/ReCaptchaAdminSettingsForm.php @@ -87,10 +87,27 @@ class ReCaptchaAdminSettingsForm extends ConfigFormBase { '#options' => [ '' => $this->t('Normal (default)'), 'compact' => $this->t('Compact'), + 'invisible' => $this->t('Invisible'), ], '#title' => $this->t('Size'), '#type' => 'select', ]; + $form['widget']['recaptcha_badge'] = [ + '#default_value' => $config->get('widget.badge'), + '#description' => $this->t('Reposition the reCAPTCHA badge. "Inline" allows you to control the CSS.'), + '#options' => [ + 'bottomright' => $this->t('Bottom Right (default)'), + 'bottomleft' => $this->t('Bottom Left'), + 'inline' => $this->t('Inline'), + ], + '#title' => $this->t('Badge'), + '#type' => 'select', + '#states' => array( + 'visible' => array( + ':input[name="recaptcha_size"]' => array('value' => 'invisible'), + ), + ), + ]; $form['widget']['recaptcha_tabindex'] = [ '#default_value' => $config->get('widget.tabindex'), '#description' => $this->t('Set the tabindex of the widget and challenge (Default = 0). If other elements in your page use tabindex, it should be set to make user navigation easier.', [':tabindex' => Url::fromUri('http://www.w3.org/TR/html4/interact/forms.html', ['fragment' => 'adef-tabindex'])->toString()]), @@ -120,6 +137,7 @@ class ReCaptchaAdminSettingsForm extends ConfigFormBase { ->set('widget.theme', $form_state->getValue('recaptcha_theme')) ->set('widget.type', $form_state->getValue('recaptcha_type')) ->set('widget.size', $form_state->getValue('recaptcha_size')) + ->set('widget.badge', $form_state->getValue('recaptcha_badge')) ->set('widget.tabindex', $form_state->getValue('recaptcha_tabindex')) ->set('widget.noscript', $form_state->getValue('recaptcha_noscript')) ->save();