diff --git a/mailpoet/assets/js/src/public.tsx b/mailpoet/assets/js/src/public.tsx index 7aa9537ffc..6fd4568c7f 100644 --- a/mailpoet/assets/js/src/public.tsx +++ b/mailpoet/assets/js/src/public.tsx @@ -72,21 +72,37 @@ jQuery(($) => { } async function updateCaptcha(e?: Event) { + const captchaSessionId = document.querySelector( + '#mailpoet_captcha_form input[name="data[captcha_session_id]"]', + )?.value; const image = document.querySelector('img.mailpoet_captcha'); const audio = document.querySelector( '.mailpoet_captcha_player', ); - if (!image || !audio) { + + if (!captchaSessionId || !image || !audio) { return false; } - // page reload invalidates CAPTCHA, but we can do it with AJAX - await fetch(window.location.href); + const cachebust = `${new Date().getTime()}`; + + // regenerate captcha phrase + const url = new URL(window.location.href.split('?')[0]); + url.searchParams.set('mailpoet_router', ''); + url.searchParams.set('mailpoet_page', 'subscriptions'); + url.searchParams.set('endpoint', 'subscription'); + url.searchParams.set('action', 'captchaRefresh'); + url.searchParams.set( + 'data', + btoa(JSON.stringify({ captcha_session_id: captchaSessionId })), + ); + url.searchParams.set('cachebust', cachebust); + await fetch(url); // image if (image) { const imageUrl = new URL(image.getAttribute('src')); - imageUrl.searchParams.set('cachebust', `${new Date().getTime()}`); + imageUrl.searchParams.set('cachebust', cachebust); image.setAttribute('src', imageUrl.toString()); } @@ -94,7 +110,7 @@ jQuery(($) => { if (audio) { const audioSource = audio.querySelector('source'); const audioUrl = new URL(audioSource.getAttribute('src')); - audioUrl.searchParams.set('cachebust', `${new Date().getTime()}`); + audioUrl.searchParams.set('cachebust', cachebust); audioSource.setAttribute('src', audioUrl.toString()); audio.load(); } diff --git a/mailpoet/lib/Router/Endpoints/Subscription.php b/mailpoet/lib/Router/Endpoints/Subscription.php index 31162d8528..e8bd87c8d0 100644 --- a/mailpoet/lib/Router/Endpoints/Subscription.php +++ b/mailpoet/lib/Router/Endpoints/Subscription.php @@ -13,6 +13,7 @@ class Subscription { const ACTION_CAPTCHA = 'captcha'; const ACTION_CAPTCHA_IMAGE = 'captchaImage'; const ACTION_CAPTCHA_AUDIO = 'captchaAudio'; + const ACTION_CAPTCHA_REFRESH = 'captchaRefresh'; const ACTION_CONFIRM = 'confirm'; const ACTION_MANAGE = 'manage'; const ACTION_UNSUBSCRIBE = 'unsubscribe'; @@ -23,6 +24,7 @@ class Subscription { self::ACTION_CAPTCHA, self::ACTION_CAPTCHA_IMAGE, self::ACTION_CAPTCHA_AUDIO, + self::ACTION_CAPTCHA_REFRESH, self::ACTION_CONFIRM, self::ACTION_MANAGE, self::ACTION_UNSUBSCRIBE, @@ -82,6 +84,14 @@ class Subscription { exit; } + public function captchaRefresh($data): void { + $captchaSessionId = $data['captcha_session_id'] ?? null; + if (!$captchaSessionId) { + return; + } + $this->captchaRenderer->refreshPhrase($captchaSessionId); + } + public function confirm($data) { $subscription = $this->initSubscriptionPage(UserSubscription\Pages::ACTION_CONFIRM, $data); $subscription->confirm(); diff --git a/mailpoet/lib/Subscription/Captcha/CaptchaRenderer.php b/mailpoet/lib/Subscription/Captcha/CaptchaRenderer.php index 28faa966c8..799cf13e25 100644 --- a/mailpoet/lib/Subscription/Captcha/CaptchaRenderer.php +++ b/mailpoet/lib/Subscription/Captcha/CaptchaRenderer.php @@ -75,6 +75,10 @@ class CaptchaRenderer { $builder->output(); } + public function refreshPhrase(string $sessionId): string { + return $this->phrase->createPhrase($sessionId); + } + private function getPhrase(string $sessionId): string { $phrase = $this->phrase->getPhrase($sessionId); if (!$phrase) { diff --git a/mailpoet/tests/integration/Router/Endpoints/SubscriptionTest.php b/mailpoet/tests/integration/Router/Endpoints/SubscriptionTest.php index 775f7d3a14..aa0cc5c3c3 100644 --- a/mailpoet/tests/integration/Router/Endpoints/SubscriptionTest.php +++ b/mailpoet/tests/integration/Router/Endpoints/SubscriptionTest.php @@ -6,6 +6,7 @@ use Codeception\Stub; use Codeception\Stub\Expected; use MailPoet\Router\Endpoints\Subscription; use MailPoet\Subscription\Captcha\CaptchaRenderer; +use MailPoet\Subscription\Captcha\CaptchaSession; use MailPoet\Subscription\Pages; use MailPoet\Util\Request; use MailPoet\WP\Functions as WPFunctions; @@ -58,4 +59,13 @@ class SubscriptionTest extends \MailPoetTest { $subscription = new Subscription($pages, $this->wp, $this->captchaRenderer, $this->request); $subscription->unsubscribe($this->data); } + + public function testItRefreshesCaptcha(): void { + $captchaSession = $this->diContainer->get(CaptchaSession::class); + $captchaSession->setCaptchaHash('123', ['phrase' => 'abc']); + + $subscription = new Subscription($this->make(Pages::class), $this->wp, $this->captchaRenderer, $this->request); + $subscription->captchaRefresh(['captcha_session_id' => '123']); + $this->assertNotEquals('abc', $captchaSession->getCaptchaHash('123')['phrase']); + } } diff --git a/mailpoet/tests/integration/Subscription/Captcha/CaptchaRendererTest.php b/mailpoet/tests/integration/Subscription/Captcha/CaptchaRendererTest.php index 92372bdceb..7b96d2a8ba 100644 --- a/mailpoet/tests/integration/Subscription/Captcha/CaptchaRendererTest.php +++ b/mailpoet/tests/integration/Subscription/Captcha/CaptchaRendererTest.php @@ -30,4 +30,11 @@ class CaptchaRendererTest extends \MailPoetTest { $this->getActualOutputForAssertion() ); } + + public function testItRefreshesPhrase(): void { + $sessionId = '123'; + $this->session->setCaptchaHash($sessionId, ['phrase' => 'abc']); + $this->testee->refreshPhrase($sessionId); + $this->assertNotEquals('abc', $this->session->getCaptchaHash($sessionId)['phrase']); + } }