diff --git a/mailpoet/lib/Form/DisplayFormInWPContent.php b/mailpoet/lib/Form/DisplayFormInWPContent.php index 4f6bed85a5..537d56d632 100644 --- a/mailpoet/lib/Form/DisplayFormInWPContent.php +++ b/mailpoet/lib/Form/DisplayFormInWPContent.php @@ -5,6 +5,8 @@ namespace MailPoet\Form; use MailPoet\API\JSON\API; use MailPoet\Config\Renderer as TemplateRenderer; use MailPoet\Entities\FormEntity; +use MailPoet\Subscribers\SubscribersRepository; +use MailPoet\Subscribers\SubscriberSubscribeController; use MailPoet\Util\Security; use MailPoet\WP\Functions as WPFunctions; @@ -19,6 +21,12 @@ class DisplayFormInWPContent { FormEntity::DISPLAY_TYPE_SLIDE_IN, ]; + const WITH_COOKIE_TYPES = [ + FormEntity::DISPLAY_TYPE_POPUP, + FormEntity::DISPLAY_TYPE_FIXED_BAR, + FormEntity::DISPLAY_TYPE_SLIDE_IN, + ]; + const SUPPORTED_POST_TYPES = [ 'post', 'product', @@ -40,18 +48,28 @@ class DisplayFormInWPContent { /** @var TemplateRenderer */ private $templateRenderer; + /** @var SubscribersRepository */ + private $subscribersRepository; + + /** @var SubscriberSubscribeController */ + private $subscriberSubscribeController; + public function __construct( WPFunctions $wp, FormsRepository $formsRepository, Renderer $formRenderer, AssetsController $assetsController, - TemplateRenderer $templateRenderer + TemplateRenderer $templateRenderer, + SubscriberSubscribeController $subscriberSubscribeController, + SubscribersRepository $subscribersRepository ) { $this->wp = $wp; $this->formsRepository = $formsRepository; $this->formRenderer = $formRenderer; $this->assetsController = $assetsController; $this->templateRenderer = $templateRenderer; + $this->subscriberSubscribeController = $subscriberSubscribeController; + $this->subscribersRepository = $subscribersRepository; } /** @@ -177,6 +195,25 @@ class DisplayFormInWPContent { return $this->templateRenderer->render('form/front_end_form.html', $templateData); } + /** + * Checks if the form should be displayed for current WordPress user + * + * @param FormEntity $form The form to check + * @param string $formType Display type of the current form, from self::TYPES + * @return bool False if form can be dismissed and user is subscribed to any of the form's lists + */ + private function shouldDisplayFormForWPUser(FormEntity $form, string $formType): bool { + if (!in_array($formType, self::WITH_COOKIE_TYPES, true)) return true; + + $subscriber = $this->subscribersRepository->getCurrentWPUser(); + if (!$subscriber) return true; + + if ($this->subscriberSubscribeController->isSubscribedToAnyFormSegments($form, $subscriber)) { + return false; + } + return true; + } + private function shouldDisplayFormType(FormEntity $form, string $formType): bool { $settings = $form->getSettings(); // check the structure just to be sure @@ -192,6 +229,8 @@ class DisplayFormInWPContent { return false; } + if (!$this->shouldDisplayFormForWPUser($form, $formType)) return false; + if ($this->wp->isSingular($this->wp->applyFilters('mailpoet_display_form_supported_post_types', self::SUPPORTED_POST_TYPES))) { if ($this->shouldDisplayFormOnPost($setup, 'posts')) return true; if ($this->shouldDisplayFormOnCategory($setup)) return true; diff --git a/mailpoet/lib/Subscribers/SubscriberSubscribeController.php b/mailpoet/lib/Subscribers/SubscriberSubscribeController.php index 043c2787d4..2422ee4042 100644 --- a/mailpoet/lib/Subscribers/SubscriberSubscribeController.php +++ b/mailpoet/lib/Subscribers/SubscriberSubscribeController.php @@ -3,9 +3,11 @@ namespace MailPoet\Subscribers; use MailPoet\Entities\FormEntity; +use MailPoet\Entities\SubscriberEntity; use MailPoet\Form\FormsRepository; use MailPoet\Form\Util\FieldNameObfuscator; use MailPoet\NotFoundException; +use MailPoet\Segments\SubscribersFinder; use MailPoet\Settings\SettingsController; use MailPoet\Statistics\StatisticsFormsRepository; use MailPoet\Subscription\Captcha; @@ -49,10 +51,14 @@ class SubscriberSubscribeController { /** @var StatisticsFormsRepository */ private $statisticsFormsRepository; + /** @var SubscribersFinder */ + private $subscribersFinder; + public function __construct( Captcha $subscriptionCaptcha, CaptchaSession $captchaSession, SubscriberActions $subscriberActions, + SubscribersFinder $subscribersFinder, SubscriptionUrlFactory $subscriptionUrlFactory, SubscriptionThrottling $throttling, FieldNameObfuscator $fieldNameObfuscator, @@ -70,6 +76,7 @@ class SubscriberSubscribeController { $this->fieldNameObfuscator = $fieldNameObfuscator; $this->settings = $settings; $this->subscriberActions = $subscriberActions; + $this->subscribersFinder = $subscribersFinder; $this->wp = $wp; $this->throttling = $throttling; $this->statisticsFormsRepository = $statisticsFormsRepository; @@ -153,6 +160,22 @@ class SubscriberSubscribeController { return $meta; } + /** + * Checks if the subscriber is subscribed to any segments in the form + * + * @param FormEntity $form The form entity + * @param SubscriberEntity $subscriber The subscriber entity + * @return bool True if the subscriber is subscribed to any of the segments in the form + */ + public function isSubscribedToAnyFormSegments(FormEntity $form, SubscriberEntity $subscriber): bool { + $formSegments = array_merge( $form->getSegmentBlocksSegmentIds(), $form->getSettingsSegmentIds()); + + $subscribersFound = $this->subscribersFinder->findSubscribersInSegments([$subscriber->getId()], $formSegments); + if (!empty($subscribersFound)) return true; + + return false; + } + private function deobfuscateFormPayload($data): array { return $this->fieldNameObfuscator->deobfuscateFormPayload($data); } diff --git a/mailpoet/tests/unit/Form/DisplayFormInWPContentTest.php b/mailpoet/tests/unit/Form/DisplayFormInWPContentTest.php index bc8a19c4f6..a68584d7e7 100644 --- a/mailpoet/tests/unit/Form/DisplayFormInWPContentTest.php +++ b/mailpoet/tests/unit/Form/DisplayFormInWPContentTest.php @@ -4,6 +4,9 @@ namespace MailPoet\Form; use MailPoet\Config\Renderer as TemplateRenderer; use MailPoet\Entities\FormEntity; +use MailPoet\Entities\SubscriberEntity; +use MailPoet\Subscribers\SubscribersRepository; +use MailPoet\Subscribers\SubscriberSubscribeController; use MailPoet\WP\Functions as WPFunctions; use PHPUnit\Framework\MockObject\MockObject; @@ -27,6 +30,12 @@ class DisplayFormInWPContentTest extends \MailPoetUnitTest { /** @var DisplayFormInWPContent */ private $hook; + /** @var SubscribersRepository & MockObject */ + private $subscribersRepository; + + /** @var SubscriberSubscribeController & MockObject */ + private $subscriberSubscribeController; + public function _before() { parent::_before(); if (!defined('ARRAY_A')) define('ARRAY_A', 'ARRAY_A'); @@ -42,7 +51,17 @@ class DisplayFormInWPContentTest extends \MailPoetUnitTest { $this->renderer = $this->createMock(Renderer::class); $this->renderer->expects($this->any())->method('renderStyles')->willReturn(''); $this->renderer->expects($this->any())->method('renderHTML')->willReturn('
'); - $this->hook = new DisplayFormInWPContent($this->wp, $this->repository, $this->renderer, $this->assetsController, $this->templateRenderer); + $this->subscribersRepository = $this->createMock( SubscribersRepository::class); + $this->subscriberSubscribeController = $this->createMock(SubscriberSubscribeController::class); + $this->hook = new DisplayFormInWPContent( + $this->wp, + $this->repository, + $this->renderer, + $this->assetsController, + $this->templateRenderer, + $this->subscriberSubscribeController, + $this->subscribersRepository + ); } public function testAppendsRenderedFormAfterPostContent() { @@ -364,6 +383,62 @@ class DisplayFormInWPContentTest extends \MailPoetUnitTest { expect($result)->endsWith($formHtml); } + public function testDoesNotAppendPopupFormIfLoggedInAndSubscribed() { + $formHtml = '
'; + $subscriber = new SubscriberEntity(); + $this->subscribersRepository->expects($this->once())->method('getCurrentWPUser')->willReturn($subscriber); + $this->subscriberSubscribeController->expects($this->once())->method('isSubscribedToAnyFormSegments')->willReturn(true); + $this->wp->expects($this->once())->method('isSingle')->willReturn(false); + $this->wp->expects($this->any())->method('isPage')->willReturn(true); + $this->assetsController->expects($this->never())->method('setupFrontEndDependencies'); + $this->templateRenderer->expects($this->never())->method('render')->willReturn($formHtml); + $this->wp + ->expects($this->never()) + ->method('setTransient'); + $form = new FormEntity('My Form'); + $form->setSettings([ + 'segments' => ['3'], + 'form_placement' => [ + 'below_posts' => ['enabled' => '', 'pages' => ['all' => ''], 'posts' => ['all' => '']], + 'popup' => ['enabled' => '1', 'pages' => ['all' => '1'], 'posts' => ['all' => '']], + ], + 'success_message' => 'Hello', + ]); + $form->setBody([['type' => 'submit', 'params' => ['label' => 'Subscribe!'], 'id' => 'submit', 'name' => 'Submit']]); + $this->repository->expects($this->once())->method('findBy')->willReturn([$form]); + + $result = $this->hook->display('content'); + expect($result)->equals('content'); + } + + public function testAppendsPopupFormIfLoggedInAndNotSubscribed() { + $formHtml = '
'; + $subscriber = new SubscriberEntity(); + $this->subscribersRepository->expects($this->any())->method('getCurrentWPUser')->willReturn($subscriber); + $this->subscriberSubscribeController->expects($this->any())->method('isSubscribedToAnyFormSegments')->willReturn(false); + $this->wp->expects($this->once())->method('isSingle')->willReturn(false); + $this->wp->expects($this->any())->method('isPage')->willReturn(true); + $this->assetsController->expects($this->once())->method('setupFrontEndDependencies'); + $this->templateRenderer->expects($this->once())->method('render')->willReturn($formHtml); + $this->wp + ->expects($this->never()) + ->method('setTransient'); + $form = new FormEntity('My Form'); + $form->setSettings([ + 'segments' => ['3'], + 'form_placement' => [ + 'below_posts' => ['enabled' => '', 'pages' => ['all' => ''], 'posts' => ['all' => '']], + 'popup' => ['enabled' => '1', 'pages' => ['all' => '1'], 'posts' => ['all' => '']], + ], + 'success_message' => 'Hello', + ]); + $form->setBody([['type' => 'submit', 'params' => ['label' => 'Subscribe!'], 'id' => 'submit', 'name' => 'Submit']]); + $this->repository->expects($this->once())->method('findBy')->willReturn([$form]); + + $result = $this->hook->display('content'); + expect($result)->endsWith($formHtml); + } + public function testAppendsRenderedFixedBarForm() { $formHtml = '
'; $this->wp->expects($this->once())->method('isSingle')->willReturn(false); diff --git a/mailpoet/tests/unit/Subscribers/SubscriberSubscribeControllerUnitTest.php b/mailpoet/tests/unit/Subscribers/SubscriberSubscribeControllerUnitTest.php index 64034d985b..19cde7f1d0 100644 --- a/mailpoet/tests/unit/Subscribers/SubscriberSubscribeControllerUnitTest.php +++ b/mailpoet/tests/unit/Subscribers/SubscriberSubscribeControllerUnitTest.php @@ -9,11 +9,13 @@ use MailPoet\Entities\FormEntity; use MailPoet\Entities\SubscriberEntity; use MailPoet\Form\FormsRepository; use MailPoet\Form\Util\FieldNameObfuscator; +use MailPoet\Segments\SubscribersFinder; use MailPoet\Settings\SettingsController; use MailPoet\Statistics\StatisticsFormsRepository; use MailPoet\Subscription\Captcha; use MailPoet\Subscription\CaptchaSession; use MailPoet\Subscription\SubscriptionUrlFactory; +use MailPoet\Subscription\Throttling; use MailPoet\Subscription\Throttling as SubscriptionThrottling; use MailPoet\UnexpectedValueException; use MailPoet\WP\Functions as WPFunctions; @@ -29,6 +31,7 @@ class SubscriberSubscribeControllerUnitTest extends \MailPoetUnitTest { ], $this ); + $subscribersFinder = Stub::makeEmpty(SubscribersFinder::class); $subscriptionUrlFactory = Stub::makeEmpty(SubscriptionUrlFactory::class); $throttling = Stub::makeEmpty(SubscriptionThrottling::class); $fieldNameObfuscator = Stub::makeEmpty(FieldNameObfuscator::class); @@ -63,6 +66,7 @@ class SubscriberSubscribeControllerUnitTest extends \MailPoetUnitTest { $subscriptionCaptcha, $captchaSession, $subscriberActions, + $subscribersFinder, $subscriptionUrlFactory, $throttling, $fieldNameObfuscator, @@ -92,6 +96,7 @@ class SubscriberSubscribeControllerUnitTest extends \MailPoetUnitTest { ], $this ); + $subscribersFinder = Stub::makeEmpty(SubscribersFinder::class); $subscriptionUrlFactory = Stub::makeEmpty(SubscriptionUrlFactory::class); $throttling = Stub::makeEmpty( SubscriptionThrottling::class, @@ -144,6 +149,7 @@ class SubscriberSubscribeControllerUnitTest extends \MailPoetUnitTest { $subscriptionCaptcha, $captchaSession, $subscriberActions, + $subscribersFinder, $subscriptionUrlFactory, $throttling, $fieldNameObfuscator, @@ -171,6 +177,7 @@ class SubscriberSubscribeControllerUnitTest extends \MailPoetUnitTest { ], $this ); + $subscribersFinder = Stub::makeEmpty(SubscribersFinder::class); $subscriptionUrlFactory = Stub::makeEmpty(SubscriptionUrlFactory::class); $throttling = Stub::makeEmpty(SubscriptionThrottling::class); $fieldNameObfuscator = Stub::makeEmpty(FieldNameObfuscator::class, @@ -221,6 +228,7 @@ class SubscriberSubscribeControllerUnitTest extends \MailPoetUnitTest { $subscriptionCaptcha, $captchaSession, $subscriberActions, + $subscribersFinder, $subscriptionUrlFactory, $throttling, $fieldNameObfuscator, @@ -254,6 +262,7 @@ class SubscriberSubscribeControllerUnitTest extends \MailPoetUnitTest { ], $this ); + $subscribersFinder = Stub::makeEmpty(SubscribersFinder::class); $expectedRedirectLink = 'redirect'; $subscriptionUrlFactory = Stub::makeEmpty( SubscriptionUrlFactory::class, @@ -324,6 +333,7 @@ class SubscriberSubscribeControllerUnitTest extends \MailPoetUnitTest { $subscriptionCaptcha, $captchaSession, $subscriberActions, + $subscribersFinder, $subscriptionUrlFactory, $throttling, $fieldNameObfuscator, @@ -362,6 +372,7 @@ class SubscriberSubscribeControllerUnitTest extends \MailPoetUnitTest { ], $this ); + $subscribersFinder = Stub::makeEmpty(SubscribersFinder::class); $expectedRedirectLink = 'redirect'; $subscriptionUrlFactory = Stub::makeEmpty( SubscriptionUrlFactory::class, @@ -436,6 +447,7 @@ class SubscriberSubscribeControllerUnitTest extends \MailPoetUnitTest { $subscriptionCaptcha, $captchaSession, $subscriberActions, + $subscribersFinder, $subscriptionUrlFactory, $throttling, $fieldNameObfuscator, @@ -453,6 +465,108 @@ class SubscriberSubscribeControllerUnitTest extends \MailPoetUnitTest { ]); } + public function testItShouldReturnTrueIfSubscribedToAnySegmentsInForm() { + $blockSegmentIds = [15,16]; + $segmentIds = [17]; + $formSegments = [15,16,17]; + $subscriberId = 1; + + $form = Stub::makeEmpty( + FormEntity::class, + [ + 'getSettingsSegmentIds' => function() use ($segmentIds): array { + return $segmentIds; + }, + 'getSegmentBlocksSegmentIds' => function() use ($blockSegmentIds) { + return $blockSegmentIds; + }, + ] + ); + + $subscriber = Stub::makeEmpty( + SubscriberEntity::class, + [ + 'getId' => function() use($subscriberId): int { + return $subscriberId; + }, + ] + ); + + $subscribersFinder = $this->createMock(SubscribersFinder::class); + $subscribersFinder->expects($this->once())->method('findSubscribersInSegments') + ->with([$subscriberId], $formSegments) + ->willReturn([15]); + + $testee = new SubscriberSubscribeController( + Stub::makeEmpty(Captcha::class), + Stub::makeEmpty(CaptchaSession::class), + Stub::makeEmpty(SubscriberActions::class), + $subscribersFinder, + Stub::makeEmpty(SubscriptionUrlFactory::class), + Stub::makeEmpty(Throttling::class), + Stub::makeEmpty(FieldNameObfuscator::class), + Stub::makeEmpty(RequiredCustomFieldValidator::class), + Stub::makeEmpty(SettingsController::class), + Stub::makeEmpty(FormsRepository::class), + Stub::makeEmpty(StatisticsFormsRepository::class), + Stub::makeEmpty(WPFunctions::class) + ); + + $result = $testee->isSubscribedToAnyFormSegments($form, $subscriber); + expect($result)->equals(true); + } + + public function testItShouldReturnFalseIfNotSubscribedToAnySegmentsInForm() { + $blockSegmentIds = []; + $segmentIds = [17]; + $formSegments = [17]; + $subscriberId = 1; + + $form = Stub::makeEmpty( + FormEntity::class, + [ + 'getSettingsSegmentIds' => function() use ($segmentIds): array { + return $segmentIds; + }, + 'getSegmentBlocksSegmentIds' => function() use ($blockSegmentIds) { + return $blockSegmentIds; + }, + ] + ); + + $subscriber = Stub::makeEmpty( + SubscriberEntity::class, + [ + 'getId' => function() use($subscriberId): int { + return $subscriberId; + }, + ] + ); + + $subscribersFinder = $this->createMock(SubscribersFinder::class); + $subscribersFinder->expects($this->once())->method('findSubscribersInSegments') + ->with([$subscriberId], $formSegments) + ->willReturn([]); + + $testee = new SubscriberSubscribeController( + Stub::makeEmpty(Captcha::class), + Stub::makeEmpty(CaptchaSession::class), + Stub::makeEmpty(SubscriberActions::class), + $subscribersFinder, + Stub::makeEmpty(SubscriptionUrlFactory::class), + Stub::makeEmpty(SubscriptionThrottling::class), + Stub::makeEmpty(FieldNameObfuscator::class), + Stub::makeEmpty(RequiredCustomFieldValidator::class), + Stub::makeEmpty(SettingsController::class), + Stub::makeEmpty(FormsRepository::class), + Stub::makeEmpty(StatisticsFormsRepository::class), + Stub::makeEmpty(WPFunctions::class) + ); + + $result = $testee->isSubscribedToAnyFormSegments($form, $subscriber); + expect($result)->equals(false); + } + public function testSubscribeSuccess() { $captchaSessionId = 'captcha_session_id'; @@ -509,6 +623,7 @@ class SubscriberSubscribeControllerUnitTest extends \MailPoetUnitTest { ], $this ); + $subscribersFinder = Stub::makeEmpty(SubscribersFinder::class); $subscriptionUrlFactory = Stub::makeEmpty(SubscriptionUrlFactory::class, [ 'subscribe' => function($receivedSubscriber, $receivedForm) use ($subscriber, $form) { @@ -560,6 +675,7 @@ class SubscriberSubscribeControllerUnitTest extends \MailPoetUnitTest { $subscriptionCaptcha, $captchaSession, $subscriberActions, + $subscribersFinder, $subscriptionUrlFactory, $throttling, $fieldNameObfuscator,