1(function ($, Drupal, drupalSettings, once) { 2 'use strict'; 3 4 function fetchHcaptchaToken(form, triggeringElemName) { 5 var promise = $.Deferred(); 6 7 const invisibleWidget = document.querySelector(`#${form.id} .h-captcha[data-size="invisible"]`); 8 if (!invisibleWidget) { 9 return promise.resolve(true); 10 } 11 12 const triggerElem = form.querySelector(`[name="${triggeringElemName}"]`); 13 if (triggerElem) { 14 // Disable the triggering element before 15 // Drupal.Ajax.prototype.beforeSend() does, since the hCaptcha token 16 // round-trip is potentially slow. Logic below copies some ideas from 17 // ...beforeSend(). 18 const triggerElemValue = ($.fieldValue && $.fieldValue(triggerElem)) || null; 19 triggerElem.setAttribute('disabled', true); 20 if (triggerElem.type !== 'submit' && triggerElemValue !== null) { 21 // This should only run for AJAX-submitted forms, triggering with 22 // non-submit elements. 23 const ajaxOpPreserve = form.querySelector('.hcaptcha-ajax-op-preserve') || document.createElement('input'); 24 ajaxOpPreserve.setAttribute("type", "hidden"); 25 ajaxOpPreserve.className = 'hcaptcha-ajax-op-preserve'; 26 ajaxOpPreserve.name = triggerElem.name; 27 ajaxOpPreserve.value = triggerElemValue; 28 triggerElem.before(ajaxOpPreserve); 29 } 30 } 31 32 hcaptcha.execute(invisibleWidget.dataset.hcaptchaWidgetId, { async: true }) 33 .then(function ({ response }) { 34 ["h-captcha-response", 'g-recaptcha-response'] 35 .map(elemName => form.querySelector(`[name="${elemName}"]`)) 36 .filter(v => v) // keep only actual elements. 37 .forEach(elem => elem.value = response); 38 promise.resolve(true); 39 }) 40 .catch(err => { 41 console.error(err); 42 // Simplistic handling of close/expired challenge; resolving the 43 // promise lets the caller continue the submission flow, which must 44 // then fail due to the missing hcaptcha response. 45 promise.resolve(false); 46 }); 47 48 return promise; 49 } 50 51 // Handle ajax forms. 52 var originalAjaxSubmit; 53 Drupal.behaviors.hcaptchaAjaxSubmitOverride = { 54 attach: function (context, settings) { 55 if (originalAjaxSubmit || !$.fn.ajaxSubmit) return; 56 57 originalAjaxSubmit = $.fn.ajaxSubmit; 58 $.fn.ajaxSubmit = function (options, data, dataType, onSuccess) { 59 var self = this; 60 fetchHcaptchaToken(self[0], options.data._triggering_element_name) 61 .then(() => { 62 const origComplete = options.complete; 63 options.complete = function(xmlhttprequest, status) { 64 // Clean-up: remove hidden element used to mirror the disabled 65 // element. 66 self[0].querySelector('.hcaptcha-ajax-op-preserve')?.remove(); 67 origComplete(xmlhttprequest, status); 68 }; 69 originalAjaxSubmit.call(self, options, data, dataType, onSuccess); 70 }); 71 }; 72 } 73 }; 74 75 Drupal.behaviors.hcaptchaInvisible = { 76 attach: function (context, settings) { 77 // Handle non-ajax forms. 78 let isFetchingToken = false; 79 80 const formsWithInvisible = Array.from(document.querySelectorAll('.h-captcha[data-size="invisible"]')) 81 .map((elem) => elem.closest('form')); 82 83 $(formsWithInvisible).one('submit', function (e) { 84 const self = this; 85 if (e.originalEvent.submitter.getAttribute('formnovalidate')) { 86 return; 87 } 88 89 if (isFetchingToken) return false; 90 isFetchingToken = true; 91 92 fetchHcaptchaToken(self, e.originalEvent.submitter && e.originalEvent.submitter.getAttribute('name')) 93 .then(() => { 94 isFetchingToken = false; 95 self.submit(); 96 }); 97 98 return false; 99 }); 100 101 // Calling formElement.submit() (see above) causes the POST data to not 102 // include [type=submit] elements. Let's inject a hidden element to mimic 103 // name + value for the type=submit element that was clicked to trigger 104 // the form submission. 105 once('hcaptcha-op-preserve', formsWithInvisible).forEach((theForm) => { 106 const opPreserve = document.createElement("input"); 107 opPreserve.setAttribute("type", "hidden"); 108 109 $(theForm).on('click', '[type=submit]', function(e) { 110 const submitId = e.currentTarget.id;
111 // Do not do this on AJAX-y submissions. 112 if (!drupalSettings.ajax || !drupalSettings.ajax.hasOwnProperty(submitId)) { 113 opPreserve.name = this.name; 114 opPreserve.value = this.value; 115 theForm.appendChild(opPreserve); 116 } 117 }); 118 }); 119 } 120 }; 121 122})(jQuery, Drupal, drupalSettings, once);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.