Handle captcha during subscription [MAILPOET-2015]
This commit is contained in:
@ -60,11 +60,22 @@ jQuery(function ($) { // eslint-disable-line func-names
|
||||
data: formData.data,
|
||||
})
|
||||
.fail(function handleFailedPost(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) {
|
||||
$('.mailpoet_captcha_update').click();
|
||||
}
|
||||
form.find('.mailpoet_validate_error').html(
|
||||
response.errors.map(function buildErrorMessage(error) {
|
||||
return error.message;
|
||||
}).join('<br />')
|
||||
).show();
|
||||
}
|
||||
})
|
||||
.done(function handleRecaptcha(response) {
|
||||
if (window.grecaptcha && formData.recaptcha) {
|
||||
@ -83,6 +94,10 @@ jQuery(function ($) { // eslint-disable-line func-names
|
||||
} else {
|
||||
// display success message
|
||||
form.find('.mailpoet_validate_success').show();
|
||||
// hide elements marked with a class
|
||||
form.find('.mailpoet_form_hide_on_success').each(function hideOnSuccess() {
|
||||
$(this).hide();
|
||||
});
|
||||
}
|
||||
|
||||
// reset form
|
||||
@ -109,5 +124,14 @@ jQuery(function ($) { // eslint-disable-line func-names
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
$('.mailpoet_captcha_update').click(function updateCaptcha(e) {
|
||||
var captcha = $('img.mailpoet_captcha');
|
||||
var captchaSrc = captcha.attr('src');
|
||||
var hashPos = captchaSrc.indexOf('#');
|
||||
var newSrc = hashPos > 0 ? captchaSrc.substring(0, hashPos) : captchaSrc;
|
||||
captcha.attr('src', newSrc + '#' + new Date().getTime());
|
||||
e.preventDefault();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -3,6 +3,7 @@ namespace MailPoet\API\JSON;
|
||||
|
||||
use MailPoet\Config\AccessControl;
|
||||
use MailPoet\Settings\SettingsController;
|
||||
use MailPoet\Subscription\Captcha;
|
||||
use MailPoetVendor\Psr\Container\ContainerInterface;
|
||||
use MailPoet\Util\Helpers;
|
||||
use MailPoet\Util\Security;
|
||||
@ -79,7 +80,7 @@ class API {
|
||||
$this->setRequestData($_POST);
|
||||
|
||||
$ignoreToken = (
|
||||
$this->settings->get('re_captcha.enabled') &&
|
||||
$this->settings->get('captcha.type') != Captcha::TYPE_DISABLED &&
|
||||
$this->_request_endpoint === 'subscribers' &&
|
||||
$this->_request_method === 'subscribe'
|
||||
);
|
||||
|
@ -18,7 +18,9 @@ use MailPoet\Settings\SettingsController;
|
||||
use MailPoet\Subscribers\RequiredCustomFieldValidator;
|
||||
use MailPoet\Subscribers\Source;
|
||||
use MailPoet\Subscribers\SubscriberActions;
|
||||
use MailPoet\Subscription\Captcha;
|
||||
use MailPoet\Subscription\Throttling as SubscriptionThrottling;
|
||||
use MailPoet\Subscription\Url as SubscriptionUrl;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
@ -46,6 +48,9 @@ class Subscribers extends APIEndpoint {
|
||||
/** @var Listing\Handler */
|
||||
private $listing_handler;
|
||||
|
||||
/** @var Captcha */
|
||||
private $subscription_captcha;
|
||||
|
||||
/** @var WPFunctions */
|
||||
private $wp;
|
||||
|
||||
@ -58,6 +63,7 @@ class Subscribers extends APIEndpoint {
|
||||
SubscriberActions $subscriber_actions,
|
||||
RequiredCustomFieldValidator $required_custom_field_validator,
|
||||
Listing\Handler $listing_handler,
|
||||
Captcha $subscription_captcha,
|
||||
WPFunctions $wp,
|
||||
SettingsController $settings
|
||||
) {
|
||||
@ -66,6 +72,7 @@ class Subscribers extends APIEndpoint {
|
||||
$this->subscriber_actions = $subscriber_actions;
|
||||
$this->required_custom_field_validator = $required_custom_field_validator;
|
||||
$this->listing_handler = $listing_handler;
|
||||
$this->subscription_captcha = $subscription_captcha;
|
||||
$this->wp = $wp;
|
||||
$this->settings = $settings;
|
||||
}
|
||||
@ -140,8 +147,6 @@ class Subscribers extends APIEndpoint {
|
||||
$form = Form::findOne($form_id);
|
||||
unset($data['form_id']);
|
||||
|
||||
$recaptcha = $this->settings->get('re_captcha');
|
||||
|
||||
if (!$form instanceof Form) {
|
||||
return $this->badRequest([
|
||||
APIError::BAD_REQUEST => WPFunctions::get()->__('Please specify a valid form ID.', 'mailpoet'),
|
||||
@ -153,31 +158,17 @@ class Subscribers extends APIEndpoint {
|
||||
]);
|
||||
}
|
||||
|
||||
if (!empty($recaptcha['enabled']) && empty($data['recaptcha'])) {
|
||||
return $this->badRequest([
|
||||
APIError::BAD_REQUEST => WPFunctions::get()->__('Please check the CAPTCHA.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
$captcha = $this->settings->get('captcha');
|
||||
|
||||
if (!empty($recaptcha['enabled'])) {
|
||||
$res = empty($data['recaptcha']) ? $data['recaptcha-no-js'] : $data['recaptcha'];
|
||||
$res = WPFunctions::get()->wpRemotePost('https://www.google.com/recaptcha/api/siteverify', [
|
||||
'body' => [
|
||||
'secret' => $recaptcha['secret_token'],
|
||||
'response' => $res,
|
||||
],
|
||||
]);
|
||||
if (is_wp_error($res)) {
|
||||
return $this->badRequest([
|
||||
APIError::BAD_REQUEST => WPFunctions::get()->__('Error while validating the CAPTCHA.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
$res = json_decode(wp_remote_retrieve_body($res));
|
||||
if (empty($res->success)) {
|
||||
return $this->badRequest([
|
||||
APIError::BAD_REQUEST => WPFunctions::get()->__('Error while validating the CAPTCHA.', 'mailpoet'),
|
||||
]);
|
||||
if (!empty($captcha['type']) && $captcha['type'] === Captcha::TYPE_BUILTIN) {
|
||||
if (empty($data['captcha'])) {
|
||||
// Save form data to session
|
||||
$_SESSION[Captcha::SESSION_FORM_KEY] = array_merge($data, ['form_id' => $form_id]);
|
||||
} elseif (!empty($_SESSION[Captcha::SESSION_FORM_KEY])) {
|
||||
// Restore form data from session
|
||||
$data = array_merge($_SESSION[Captcha::SESSION_FORM_KEY], ['captcha' => $data['captcha']]);
|
||||
}
|
||||
// Otherwise use the post data
|
||||
}
|
||||
|
||||
$data = $this->deobfuscateFormPayload($data);
|
||||
@ -201,6 +192,64 @@ class Subscribers extends APIEndpoint {
|
||||
]);
|
||||
}
|
||||
|
||||
$is_builtin_captcha_required = false;
|
||||
if (!empty($captcha['type']) && $captcha['type'] === Captcha::TYPE_BUILTIN) {
|
||||
$is_builtin_captcha_required = $this->subscription_captcha->isRequired(isset($data['email']) ? $data['email'] : '');
|
||||
if ($is_builtin_captcha_required && empty($data['captcha'])) {
|
||||
$meta = [];
|
||||
$meta['redirect_url'] = SubscriptionUrl::getCaptchaUrl();
|
||||
return $this->badRequest([
|
||||
APIError::BAD_REQUEST => WPFunctions::get()->__('Please check the CAPTCHA.', 'mailpoet'),
|
||||
], $meta);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($captcha['type']) && $captcha['type'] === Captcha::TYPE_RECAPTCHA && empty($data['recaptcha'])) {
|
||||
return $this->badRequest([
|
||||
APIError::BAD_REQUEST => WPFunctions::get()->__('Please check the CAPTCHA.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
|
||||
if (!empty($captcha['type'])) {
|
||||
if ($captcha['type'] === Captcha::TYPE_RECAPTCHA) {
|
||||
$res = empty($data['recaptcha']) ? $data['recaptcha-no-js'] : $data['recaptcha'];
|
||||
$res = WPFunctions::get()->wpRemotePost('https://www.google.com/recaptcha/api/siteverify', [
|
||||
'body' => [
|
||||
'secret' => $captcha['recaptcha_secret_token'],
|
||||
'response' => $res,
|
||||
],
|
||||
]);
|
||||
if (is_wp_error($res)) {
|
||||
return $this->badRequest([
|
||||
APIError::BAD_REQUEST => WPFunctions::get()->__('Error while validating the CAPTCHA.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
$res = json_decode(wp_remote_retrieve_body($res));
|
||||
if (empty($res->success)) {
|
||||
return $this->badRequest([
|
||||
APIError::BAD_REQUEST => WPFunctions::get()->__('Error while validating the CAPTCHA.', 'mailpoet'),
|
||||
]);
|
||||
}
|
||||
} elseif ($captcha['type'] === Captcha::TYPE_BUILTIN && $is_builtin_captcha_required) {
|
||||
if (empty($_SESSION[Captcha::SESSION_KEY])) {
|
||||
return $this->badRequest([
|
||||
APIError::BAD_REQUEST => WPFunctions::get()->__('Please regenerate the CAPTCHA.', 'mailpoet'),
|
||||
]);
|
||||
} elseif (!hash_equals(strtolower($data['captcha']), $_SESSION[Captcha::SESSION_KEY])) {
|
||||
$_SESSION[Captcha::SESSION_KEY] = null;
|
||||
$meta = [];
|
||||
$meta['refresh_captcha'] = true;
|
||||
return $this->badRequest([
|
||||
APIError::BAD_REQUEST => WPFunctions::get()->__('The characters entered do not match with the previous captcha.', 'mailpoet'),
|
||||
], $meta);
|
||||
} else {
|
||||
// Captcha has been verified, invalidate the session vars
|
||||
$_SESSION[Captcha::SESSION_KEY] = null;
|
||||
$_SESSION[Captcha::SESSION_FORM_KEY] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// only accept fields defined in the form
|
||||
$form_fields = $form->getFieldList();
|
||||
$data = array_intersect_key($data, array_flip($form_fields));
|
||||
|
@ -179,6 +179,7 @@ class Initializer {
|
||||
|
||||
function initialize() {
|
||||
try {
|
||||
$this->setupSession();
|
||||
$this->maybeDbUpdate();
|
||||
$this->setupInstaller();
|
||||
$this->setupUpdater();
|
||||
@ -207,6 +208,11 @@ class Initializer {
|
||||
define(self::INITIALIZED, true);
|
||||
}
|
||||
|
||||
function setupSession() {
|
||||
$session = new Session;
|
||||
$session->init();
|
||||
}
|
||||
|
||||
function maybeDbUpdate() {
|
||||
try {
|
||||
$current_db_version = $this->settings->get('db_version');
|
||||
|
@ -177,6 +177,9 @@ class Populator {
|
||||
'confirmation' => $mailpoet_page_id,
|
||||
'captcha' => $mailpoet_page_id,
|
||||
]);
|
||||
} elseif (empty($subscription['captcha'])) {
|
||||
// For existing installations
|
||||
$this->settings->set('subscription.pages', array_merge($subscription, ['captcha' => $mailpoet_page_id]));
|
||||
}
|
||||
}
|
||||
|
||||
|
13
lib/Config/Session.php
Normal file
13
lib/Config/Session.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace MailPoet\Config;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
class Session {
|
||||
function init() {
|
||||
if (!session_id()) {
|
||||
return session_start();
|
||||
}
|
||||
}
|
||||
}
|
@ -145,6 +145,7 @@ class ContainerConfigurator implements IContainerConfigurator {
|
||||
// User Flags
|
||||
$container->autowire(\MailPoet\Settings\UserFlagsController::class);
|
||||
// Subscription
|
||||
$container->autowire(\MailPoet\Subscription\Captcha::class)->setPublic(true);
|
||||
$container->autowire(\MailPoet\Subscription\Comment::class)->setPublic(true);
|
||||
$container->autowire(\MailPoet\Subscription\Form::class)->setPublic(true);
|
||||
$container->autowire(\MailPoet\Subscription\Manage::class)->setPublic(true);
|
||||
|
@ -2,6 +2,10 @@
|
||||
|
||||
namespace MailPoet\Subscription;
|
||||
|
||||
use MailPoet\Models\Subscriber;
|
||||
use MailPoet\Models\SubscriberIP;
|
||||
use MailPoet\Util\Helpers;
|
||||
use MailPoet\WP\Functions as WPFunctions;
|
||||
use MailPoetVendor\Gregwar\Captcha\CaptchaBuilder;
|
||||
|
||||
class Captcha {
|
||||
@ -12,11 +16,57 @@ class Captcha {
|
||||
const SESSION_KEY = 'mailpoet_captcha';
|
||||
const SESSION_FORM_KEY = 'mailpoet_captcha_form';
|
||||
|
||||
/** @var WPFunctions */
|
||||
private $wp;
|
||||
|
||||
function __construct(WPFunctions $wp = null) {
|
||||
if ($wp === null) {
|
||||
$wp = new WPFunctions;
|
||||
}
|
||||
$this->wp = $wp;
|
||||
}
|
||||
|
||||
function isSupported() {
|
||||
return extension_loaded('gd') && function_exists('imagettftext');
|
||||
}
|
||||
|
||||
function renderImage($width = null, $height = null) {
|
||||
function isRequired($subscriber_email = null) {
|
||||
if ($this->wp->isUserLoggedIn()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check limits per recipient
|
||||
$subscription_captcha_recipient_limit = $this->wp->applyFilters('mailpoet_subscription_captcha_recipient_limit', 1);
|
||||
if ($subscriber_email) {
|
||||
$subscriber = Subscriber::where('email', $subscriber_email)->findOne();
|
||||
if ($subscriber instanceof Subscriber
|
||||
&& $subscriber->count_confirmations >= $subscription_captcha_recipient_limit
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check limits per IP address
|
||||
$subscription_captcha_window = $this->wp->applyFilters('mailpoet_subscription_captcha_window', MONTH_IN_SECONDS);
|
||||
|
||||
$subscriber_ip = Helpers::getIP();
|
||||
|
||||
if (!empty($subscriber_ip)) {
|
||||
$subscription_count = SubscriberIP::where('ip', $subscriber_ip)
|
||||
->whereRaw(
|
||||
'(`created_at` >= NOW() - INTERVAL ? SECOND)',
|
||||
[(int)$subscription_captcha_window]
|
||||
)->count();
|
||||
|
||||
if ($subscription_count > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function renderImage($width = null, $height = null, $return = false) {
|
||||
if (!$this->isSupported()) {
|
||||
return false;
|
||||
}
|
||||
@ -36,6 +86,10 @@ class Captcha {
|
||||
|
||||
$_SESSION[self::SESSION_KEY] = $builder->getPhrase();
|
||||
|
||||
if ($return) {
|
||||
return $builder->get();
|
||||
}
|
||||
|
||||
header('Content-Type: image/jpeg');
|
||||
$builder->output();
|
||||
exit;
|
||||
|
@ -25,7 +25,9 @@ class Form {
|
||||
$form_id = (!empty($request_data['data']['form_id'])) ? (int)$request_data['data']['form_id'] : false;
|
||||
$response = $this->api->processRoute();
|
||||
if ($response->status !== APIResponse::STATUS_OK) {
|
||||
return $this->url_helper->redirectBack(
|
||||
return (isset($response->meta['redirect_url'])) ?
|
||||
$this->url_helper->redirectTo($response->meta['redirect_url']) :
|
||||
$this->url_helper->redirectBack(
|
||||
[
|
||||
'mailpoet_error' => ($form_id) ? $form_id : true,
|
||||
'mailpoet_success' => null,
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace MailPoet\Subscription;
|
||||
|
||||
use MailPoet\Models\Form as FormModel;
|
||||
use MailPoet\Models\Subscriber;
|
||||
use MailPoet\Models\SubscriberSegment;
|
||||
use MailPoet\Models\CustomField;
|
||||
@ -290,6 +291,8 @@ class Pages {
|
||||
);
|
||||
|
||||
$form_id = isset($_SESSION[Captcha::SESSION_FORM_KEY]['form_id']) ? (int)$_SESSION[Captcha::SESSION_FORM_KEY]['form_id'] : 0;
|
||||
$form_model = FormModel::findOne($form_id);
|
||||
$form_model = $form_model->asArray();
|
||||
|
||||
$form_html = '<form method="POST" ' .
|
||||
'action="' . admin_url('admin-post.php?action=mailpoet_subscription_form') . '" ' .
|
||||
@ -315,7 +318,7 @@ class Pages {
|
||||
$form_html .= FormRenderer::renderBlocks($form, $honeypot = false);
|
||||
$form_html .= '</div>';
|
||||
$form_html .= '<div class="mailpoet_message">';
|
||||
$form_html .= '<p class="mailpoet_validate_success" style="display:none;">Check your inbox or spam folder to confirm your subscription.</p>';
|
||||
$form_html .= '<p class="mailpoet_validate_success" style="display:none;">' . $form_model['settings']['success_message'] . '</p>';
|
||||
$form_html .= '<p class="mailpoet_validate_error" style="display:none;"></p>';
|
||||
$form_html .= '</div>';
|
||||
$form_html .= '</form>';
|
||||
|
@ -14,7 +14,6 @@ class Throttling {
|
||||
$subscription_limit_base = $wp->applyFilters('mailpoet_subscription_limit_base', MINUTE_IN_SECONDS);
|
||||
|
||||
$subscriber_ip = Helpers::getIP();
|
||||
$wp = new WPFunctions;
|
||||
|
||||
if ($subscription_limit_enabled && !$wp->isUserLoggedIn()) {
|
||||
if (!empty($subscriber_ip)) {
|
||||
@ -43,12 +42,14 @@ class Throttling {
|
||||
$ip->ip = $subscriber_ip;
|
||||
$ip->save();
|
||||
|
||||
self::purge($subscription_limit_window);
|
||||
self::purge();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static function purge($interval) {
|
||||
static function purge() {
|
||||
$wp = new WPFunctions;
|
||||
$interval = $wp->applyFilters('mailpoet_subscription_purge_window', MONTH_IN_SECONDS);
|
||||
return SubscriberIP::whereRaw(
|
||||
'(`created_at` < NOW() - INTERVAL ? SECOND)',
|
||||
[$interval]
|
||||
|
Reference in New Issue
Block a user