Add code to display the invisible ReCaptcha in the frontend
This commit adds the required code to display the invisible ReCaptcha in the frontend when a form is rendered and this type of captcha is selected in the admin. [MAILPOET-4145]
This commit is contained in:
@@ -77,6 +77,76 @@ jQuery(($) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function submitSubscribeForm(form, formData, parsley) {
|
||||||
|
form.addClass('mailpoet_form_sending');
|
||||||
|
// ajax request
|
||||||
|
MailPoet.Ajax.post({
|
||||||
|
url: window.MailPoetForm.ajax_url,
|
||||||
|
token: formData.token,
|
||||||
|
api_version: formData.api_version,
|
||||||
|
endpoint: 'subscribers',
|
||||||
|
action: 'subscribe',
|
||||||
|
data: formData.data,
|
||||||
|
})
|
||||||
|
.fail((response) => {
|
||||||
|
if (
|
||||||
|
response.meta !== undefined &&
|
||||||
|
response.meta.redirect_url !== undefined
|
||||||
|
) {
|
||||||
|
// go to page
|
||||||
|
window.top.location.href = response.meta.redirect_url;
|
||||||
|
} else {
|
||||||
|
if (response.meta && response.meta.refresh_captcha) {
|
||||||
|
updateCaptcha();
|
||||||
|
}
|
||||||
|
form
|
||||||
|
.find('.mailpoet_validate_error')
|
||||||
|
.html(response.errors.map((error) => error.message).join('<br />'))
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.done((response) => {
|
||||||
|
if (window.grecaptcha && formData.recaptcha) {
|
||||||
|
window.grecaptcha.reset(formData.recaptcha);
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
})
|
||||||
|
.done((response) => {
|
||||||
|
// successfully subscribed
|
||||||
|
if (
|
||||||
|
response.meta !== undefined &&
|
||||||
|
response.meta.redirect_url !== undefined
|
||||||
|
) {
|
||||||
|
setFormCookieAfterSubscription(form);
|
||||||
|
// go to page
|
||||||
|
window.location.href = response.meta.redirect_url;
|
||||||
|
} else {
|
||||||
|
displaySuccessMessage(form);
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset form
|
||||||
|
form.trigger('reset');
|
||||||
|
// reset validation
|
||||||
|
parsley.reset();
|
||||||
|
// reset captcha
|
||||||
|
if (window.grecaptcha && formData.recaptcha) {
|
||||||
|
window.grecaptcha.reset(formData.recaptcha);
|
||||||
|
}
|
||||||
|
|
||||||
|
// resize iframe
|
||||||
|
if (
|
||||||
|
window.frameElement !== null &&
|
||||||
|
MailPoet !== undefined &&
|
||||||
|
MailPoet.Iframe
|
||||||
|
) {
|
||||||
|
MailPoet.Iframe.autoSize(window.frameElement);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.always(() => {
|
||||||
|
form.removeClass('mailpoet_form_sending');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function renderCaptcha(element, iteration) {
|
function renderCaptcha(element, iteration) {
|
||||||
if (!window.recaptcha || !window.grecaptcha.ready) {
|
if (!window.recaptcha || !window.grecaptcha.ready) {
|
||||||
if (iteration < 20) {
|
if (iteration < 20) {
|
||||||
@@ -85,12 +155,31 @@ jQuery(($) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const recaptcha = $(element);
|
const recaptcha = $(element);
|
||||||
|
const form = $(recaptcha).parent('form');
|
||||||
const sitekey = recaptcha.attr('data-sitekey');
|
const sitekey = recaptcha.attr('data-sitekey');
|
||||||
|
let size = recaptcha.attr('data-size');
|
||||||
|
|
||||||
|
// Users should not be able to change the size if it is equal to 'invisible' as this would
|
||||||
|
// change the type of the ReCaptcha.
|
||||||
|
if (size !== 'invisible') {
|
||||||
|
size = Hooks.applyFilters('mailpoet_re_captcha_size', 'compact');
|
||||||
|
}
|
||||||
|
|
||||||
const container = recaptcha.find('> .mailpoet_recaptcha_container').get(0);
|
const container = recaptcha.find('> .mailpoet_recaptcha_container').get(0);
|
||||||
const field = recaptcha.find('> .mailpoet_recaptcha_field');
|
const field = recaptcha.find('> .mailpoet_recaptcha_field');
|
||||||
if (sitekey) {
|
if (sitekey) {
|
||||||
const size = Hooks.applyFilters('mailpoet_re_captcha_size', 'compact');
|
const params = { sitekey, size };
|
||||||
const widgetId = window.grecaptcha.render(container, { sitekey, size });
|
|
||||||
|
if (size === 'invisible') {
|
||||||
|
params.callback = function invisibleReCaptchaCallback(token) {
|
||||||
|
const formData = form.mailpoetSerializeObject() || {};
|
||||||
|
formData.data.recaptcha = token;
|
||||||
|
|
||||||
|
submitSubscribeForm(form, formData, form.parsley());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const widgetId = window.grecaptcha.render(container, params);
|
||||||
field.val(widgetId);
|
field.val(widgetId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -299,88 +388,36 @@ jQuery(($) => {
|
|||||||
}, 2500);
|
}, 2500);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const formData = form.mailpoetSerializeObject() || {};
|
|
||||||
// check if we're on the same domain
|
// check if we're on the same domain
|
||||||
if (isSameDomain(window.MailPoetForm.ajax_url) === false) {
|
if (isSameDomain(window.MailPoetForm.ajax_url) === false) {
|
||||||
// non ajax post request
|
// non ajax post request
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const formData = form.mailpoetSerializeObject() || {};
|
||||||
|
const size = form.children('.mailpoet_recaptcha').attr('data-size');
|
||||||
|
|
||||||
if (window.grecaptcha && formData.recaptcha) {
|
if (window.grecaptcha && formData.recaptcha) {
|
||||||
formData.data.recaptcha = window.grecaptcha.getResponse(
|
// The API for the invisible and checkbox ReCaptchas is slightly different. For the
|
||||||
formData.recaptcha,
|
// former, we need to call execute() and then the ReCaptcha API calls the callback set
|
||||||
);
|
// inside renderCaptcha() with a token if the captcha was solved successfully. The
|
||||||
|
// callback then calls submitSubscribeForm() with the token. For the latter, we get the
|
||||||
|
// token here after calling getResponse() and then we can call submitSubscribeForm()
|
||||||
|
// directly.
|
||||||
|
if (size === 'invisible') {
|
||||||
|
window.grecaptcha.execute();
|
||||||
|
} else {
|
||||||
|
formData.data.recaptcha = window.grecaptcha.getResponse(
|
||||||
|
formData.recaptcha,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
form.addClass('mailpoet_form_sending');
|
if (size !== 'invisible') {
|
||||||
// ajax request
|
submitSubscribeForm(form, formData, parsley);
|
||||||
MailPoet.Ajax.post({
|
}
|
||||||
url: window.MailPoetForm.ajax_url,
|
|
||||||
token: formData.token,
|
|
||||||
api_version: formData.api_version,
|
|
||||||
endpoint: 'subscribers',
|
|
||||||
action: 'subscribe',
|
|
||||||
data: formData.data,
|
|
||||||
})
|
|
||||||
.fail((response) => {
|
|
||||||
if (
|
|
||||||
response.meta !== undefined &&
|
|
||||||
response.meta.redirect_url !== undefined
|
|
||||||
) {
|
|
||||||
// go to page
|
|
||||||
window.top.location.href = response.meta.redirect_url;
|
|
||||||
} else {
|
|
||||||
if (response.meta && response.meta.refresh_captcha) {
|
|
||||||
updateCaptcha();
|
|
||||||
}
|
|
||||||
form
|
|
||||||
.find('.mailpoet_validate_error')
|
|
||||||
.html(
|
|
||||||
response.errors.map((error) => error.message).join('<br />'),
|
|
||||||
)
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.done((response) => {
|
|
||||||
if (window.grecaptcha && formData.recaptcha) {
|
|
||||||
window.grecaptcha.reset(formData.recaptcha);
|
|
||||||
}
|
|
||||||
return response;
|
|
||||||
})
|
|
||||||
.done((response) => {
|
|
||||||
// successfully subscribed
|
|
||||||
if (
|
|
||||||
response.meta !== undefined &&
|
|
||||||
response.meta.redirect_url !== undefined
|
|
||||||
) {
|
|
||||||
setFormCookieAfterSubscription(form);
|
|
||||||
// go to page
|
|
||||||
window.location.href = response.meta.redirect_url;
|
|
||||||
} else {
|
|
||||||
displaySuccessMessage(form);
|
|
||||||
}
|
|
||||||
|
|
||||||
// reset form
|
|
||||||
form.trigger('reset');
|
|
||||||
// reset validation
|
|
||||||
parsley.reset();
|
|
||||||
// reset captcha
|
|
||||||
if (window.grecaptcha && formData.recaptcha) {
|
|
||||||
window.grecaptcha.reset(formData.recaptcha);
|
|
||||||
}
|
|
||||||
|
|
||||||
// resize iframe
|
|
||||||
if (
|
|
||||||
window.frameElement !== null &&
|
|
||||||
MailPoet !== undefined &&
|
|
||||||
MailPoet.Iframe
|
|
||||||
) {
|
|
||||||
MailPoet.Iframe.autoSize(window.frameElement);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.always(() => {
|
|
||||||
form.removeClass('mailpoet_form_sending');
|
|
||||||
});
|
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -33,7 +33,7 @@ class AssetsController {
|
|||||||
public function printScripts() {
|
public function printScripts() {
|
||||||
ob_start();
|
ob_start();
|
||||||
$captcha = $this->settings->get('captcha');
|
$captcha = $this->settings->get('captcha');
|
||||||
if (!empty($captcha['type']) && $captcha['type'] === Captcha::TYPE_RECAPTCHA) {
|
if (!empty($captcha['type']) && Captcha::isReCaptcha($captcha['type'])) {
|
||||||
echo '<script src="' . self::RECAPTCHA_API_URL . '" async defer></script>';
|
echo '<script src="' . self::RECAPTCHA_API_URL . '" async defer></script>';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ class AssetsController {
|
|||||||
|
|
||||||
public function setupFrontEndDependencies() {
|
public function setupFrontEndDependencies() {
|
||||||
$captcha = $this->settings->get('captcha');
|
$captcha = $this->settings->get('captcha');
|
||||||
if (!empty($captcha['type']) && $captcha['type'] === Captcha::TYPE_RECAPTCHA) {
|
if (!empty($captcha['type']) && Captcha::isRecaptcha($captcha['type'])) {
|
||||||
$this->wp->wpEnqueueScript(
|
$this->wp->wpEnqueueScript(
|
||||||
'mailpoet_recaptcha',
|
'mailpoet_recaptcha',
|
||||||
self::RECAPTCHA_API_URL
|
self::RECAPTCHA_API_URL
|
||||||
|
@@ -70,7 +70,7 @@ class Renderer {
|
|||||||
if (
|
if (
|
||||||
$captchaEnabled
|
$captchaEnabled
|
||||||
&& $block['type'] === FormEntity::SUBMIT_BLOCK_TYPE
|
&& $block['type'] === FormEntity::SUBMIT_BLOCK_TYPE
|
||||||
&& $this->settings->get('captcha.type') === Captcha::TYPE_RECAPTCHA
|
&& Captcha::isRecaptcha($this->settings->get('captcha.type'))
|
||||||
) {
|
) {
|
||||||
$html .= $this->renderReCaptcha();
|
$html .= $this->renderReCaptcha();
|
||||||
}
|
}
|
||||||
@@ -89,8 +89,15 @@ class Renderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private function renderReCaptcha(): string {
|
private function renderReCaptcha(): string {
|
||||||
$siteKey = $this->settings->get('captcha.recaptcha_site_token');
|
if ($this->settings->get('captcha.type') === Captcha::TYPE_RECAPTCHA) {
|
||||||
return '<div class="mailpoet_recaptcha" data-sitekey="' . $siteKey . '">
|
$siteKey = $this->settings->get('captcha.recaptcha_site_token');
|
||||||
|
$size = '';
|
||||||
|
} else {
|
||||||
|
$siteKey = $this->settings->get('captcha.recaptcha_invisible_site_token');
|
||||||
|
$size = 'invisible';
|
||||||
|
}
|
||||||
|
|
||||||
|
$html = '<div class="mailpoet_recaptcha" data-sitekey="' . $siteKey . '" ' . ($size === 'invisible' ? 'data-size="invisible"' : '') . '>
|
||||||
<div class="mailpoet_recaptcha_container"></div>
|
<div class="mailpoet_recaptcha_container"></div>
|
||||||
<noscript>
|
<noscript>
|
||||||
<div>
|
<div>
|
||||||
@@ -108,5 +115,7 @@ class Renderer {
|
|||||||
</noscript>
|
</noscript>
|
||||||
<input class="mailpoet_recaptcha_field" type="hidden" name="recaptcha">
|
<input class="mailpoet_recaptcha_field" type="hidden" name="recaptcha">
|
||||||
</div>';
|
</div>';
|
||||||
|
|
||||||
|
return $html;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -216,15 +216,21 @@ class SubscriberSubscribeController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($captchaSettings['type'] === Captcha::TYPE_RECAPTCHA && empty($data['recaptcha'])) {
|
if (Captcha::isReCaptcha($captchaSettings['type']) && empty($data['recaptcha'])) {
|
||||||
return ['error' => __('Please check the CAPTCHA.', 'mailpoet')];
|
return ['error' => __('Please check the CAPTCHA.', 'mailpoet')];
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($captchaSettings['type'] === Captcha::TYPE_RECAPTCHA) {
|
if (Captcha::isReCaptcha($captchaSettings['type'])) {
|
||||||
|
if ($captchaSettings['type'] === Captcha::TYPE_RECAPTCHA_INVISIBLE) {
|
||||||
|
$secretToken = $captchaSettings['recaptcha_invisible_secret_token'];
|
||||||
|
} else {
|
||||||
|
$secretToken = $captchaSettings['recaptcha_secret_token'];
|
||||||
|
}
|
||||||
|
|
||||||
$response = empty($data['recaptcha']) ? $data['recaptcha-no-js'] : $data['recaptcha'];
|
$response = empty($data['recaptcha']) ? $data['recaptcha-no-js'] : $data['recaptcha'];
|
||||||
$response = $this->wp->wpRemotePost('https://www.google.com/recaptcha/api/siteverify', [
|
$response = $this->wp->wpRemotePost('https://www.google.com/recaptcha/api/siteverify', [
|
||||||
'body' => [
|
'body' => [
|
||||||
'secret' => $captchaSettings['recaptcha_secret_token'],
|
'secret' => $secretToken,
|
||||||
'response' => $response,
|
'response' => $response,
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
@@ -11,6 +11,7 @@ use MailPoetVendor\Gregwar\Captcha\CaptchaBuilder;
|
|||||||
class Captcha {
|
class Captcha {
|
||||||
const TYPE_BUILTIN = 'built-in';
|
const TYPE_BUILTIN = 'built-in';
|
||||||
const TYPE_RECAPTCHA = 'recaptcha';
|
const TYPE_RECAPTCHA = 'recaptcha';
|
||||||
|
const TYPE_RECAPTCHA_INVISIBLE = 'recaptcha-invisible';
|
||||||
const TYPE_DISABLED = null;
|
const TYPE_DISABLED = null;
|
||||||
|
|
||||||
/** @var WPFunctions */
|
/** @var WPFunctions */
|
||||||
@@ -22,6 +23,10 @@ class Captcha {
|
|||||||
/** @var SubscriberIPsRepository */
|
/** @var SubscriberIPsRepository */
|
||||||
private $subscriberIPsRepository;
|
private $subscriberIPsRepository;
|
||||||
|
|
||||||
|
public static function isReCaptcha(?string $captchaType) {
|
||||||
|
return in_array($captchaType, [self::TYPE_RECAPTCHA, self::TYPE_RECAPTCHA_INVISIBLE]);
|
||||||
|
}
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
SubscriberIPsRepository $subscriberIPsRepository,
|
SubscriberIPsRepository $subscriberIPsRepository,
|
||||||
WPFunctions $wp = null,
|
WPFunctions $wp = null,
|
||||||
|
@@ -697,6 +697,18 @@ class SubscribersTest extends \MailPoetTest {
|
|||||||
$this->settings->set('captcha', []);
|
$this->settings->set('captcha', []);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testItCannotSubscribeWithoutInvisibleReCaptchaWhenEnabled() {
|
||||||
|
$this->settings->set('captcha', ['type' => Captcha::TYPE_RECAPTCHA_INVISIBLE]);
|
||||||
|
$response = $this->endpoint->subscribe([
|
||||||
|
$this->obfuscatedEmail => 'toto@mailpoet.com',
|
||||||
|
'form_id' => $this->form->getId(),
|
||||||
|
$this->obfuscatedSegments => [$this->segment1->getId(), $this->segment2->getId()],
|
||||||
|
]);
|
||||||
|
expect($response->status)->equals(APIResponse::STATUS_BAD_REQUEST);
|
||||||
|
expect($response->errors[0]['message'])->equals('Please check the CAPTCHA.');
|
||||||
|
$this->settings->set('captcha', []);
|
||||||
|
}
|
||||||
|
|
||||||
public function testItCannotSubscribeWithoutBuiltInCaptchaWhenEnabled() {
|
public function testItCannotSubscribeWithoutBuiltInCaptchaWhenEnabled() {
|
||||||
$this->settings->set('captcha', ['type' => Captcha::TYPE_BUILTIN]);
|
$this->settings->set('captcha', ['type' => Captcha::TYPE_BUILTIN]);
|
||||||
$email = 'toto@mailpoet.com';
|
$email = 'toto@mailpoet.com';
|
||||||
|
Reference in New Issue
Block a user