diff --git a/config/services/managers.yml b/config/services/managers.yml index 4f57fc11..0e1b1d8a 100644 --- a/config/services/managers.yml +++ b/config/services/managers.yml @@ -67,3 +67,7 @@ services: PhpList\Core\Domain\Messaging\Service\Manager\ListMessageManager: autowire: true autoconfigure: true + + PhpList\Core\Domain\Subscription\Service\Manager\SubscriberBlacklistManager: + autowire: true + autoconfigure: true diff --git a/config/services/repositories.yml b/config/services/repositories.yml index eca3a31c..db3831dd 100644 --- a/config/services/repositories.yml +++ b/config/services/repositories.yml @@ -110,3 +110,13 @@ services: parent: PhpList\Core\Domain\Common\Repository\AbstractRepository arguments: - PhpList\Core\Domain\Messaging\Model\ListMessage + + PhpList\Core\Domain\Subscription\Repository\UserBlacklistRepository: + parent: PhpList\Core\Domain\Common\Repository\AbstractRepository + arguments: + - PhpList\Core\Domain\Subscription\Model\UserBlacklist + + PhpList\Core\Domain\Subscription\Repository\UserBlacklistDataRepository: + parent: PhpList\Core\Domain\Common\Repository\AbstractRepository + arguments: + - PhpList\Core\Domain\Subscription\Model\UserBlacklistData diff --git a/src/Domain/Identity/Repository/UserBlacklistDataRepository.php b/src/Domain/Identity/Repository/UserBlacklistDataRepository.php deleted file mode 100644 index 0f06722b..00000000 --- a/src/Domain/Identity/Repository/UserBlacklistDataRepository.php +++ /dev/null @@ -1,11 +0,0 @@ -email; @@ -42,4 +45,9 @@ public function setAdded(?DateTime $added): self $this->added = $added; return $this; } + + public function getBlacklistData(): ?UserBlacklistData + { + return $this->blacklistData; + } } diff --git a/src/Domain/Identity/Model/UserBlacklistData.php b/src/Domain/Subscription/Model/UserBlacklistData.php similarity index 90% rename from src/Domain/Identity/Model/UserBlacklistData.php rename to src/Domain/Subscription/Model/UserBlacklistData.php index 09697616..f8d78c59 100644 --- a/src/Domain/Identity/Model/UserBlacklistData.php +++ b/src/Domain/Subscription/Model/UserBlacklistData.php @@ -2,11 +2,11 @@ declare(strict_types=1); -namespace PhpList\Core\Domain\Identity\Model; +namespace PhpList\Core\Domain\Subscription\Model; use Doctrine\ORM\Mapping as ORM; use PhpList\Core\Domain\Common\Model\Interfaces\DomainModel; -use PhpList\Core\Domain\Identity\Repository\UserBlacklistDataRepository; +use PhpList\Core\Domain\Subscription\Repository\UserBlacklistDataRepository; #[ORM\Entity(repositoryClass: UserBlacklistDataRepository::class)] #[ORM\Table(name: 'phplist_user_blacklist_data')] diff --git a/src/Domain/Subscription/Repository/SubscriberRepository.php b/src/Domain/Subscription/Repository/SubscriberRepository.php index 762096a0..6ebaee70 100644 --- a/src/Domain/Subscription/Repository/SubscriberRepository.php +++ b/src/Domain/Subscription/Repository/SubscriberRepository.php @@ -127,4 +127,18 @@ public function findSubscriberWithSubscriptions(int $id): ?Subscriber ->getQuery() ->getOneOrNullResult(); } + + public function isEmailBlacklisted(string $email): bool + { + $queryBuilder = $this->getEntityManager()->createQueryBuilder(); + + $queryBuilder->select('u.email') + ->from(Subscriber::class, 'u') + ->where('u.email = :email') + ->andWhere('u.blacklisted = 1') + ->setParameter('email', $email) + ->setMaxResults(1); + + return !($queryBuilder->getQuery()->getOneOrNullResult() === null); + } } diff --git a/src/Domain/Subscription/Repository/UserBlacklistDataRepository.php b/src/Domain/Subscription/Repository/UserBlacklistDataRepository.php new file mode 100644 index 00000000..a64525b9 --- /dev/null +++ b/src/Domain/Subscription/Repository/UserBlacklistDataRepository.php @@ -0,0 +1,16 @@ +findOneBy(['email' => $email]); + } +} diff --git a/src/Domain/Subscription/Repository/UserBlacklistRepository.php b/src/Domain/Subscription/Repository/UserBlacklistRepository.php new file mode 100644 index 00000000..665deb64 --- /dev/null +++ b/src/Domain/Subscription/Repository/UserBlacklistRepository.php @@ -0,0 +1,33 @@ +getEntityManager()->createQueryBuilder(); + + $queryBuilder->select('ub.email, ub.added, ubd.data AS reason') + ->from(UserBlacklist::class, 'ub') + ->innerJoin(UserBlacklistData::class, 'ubd', 'WITH', 'ub.email = ubd.email') + ->where('ub.email = :email') + ->setParameter('email', $email) + ->setMaxResults(1); + + return $queryBuilder->getQuery()->getOneOrNullResult(); + } + + public function findOneByEmail(string $email): ?UserBlacklist + { + return $this->findOneBy([ + 'email' => $email, + ]); + } +} diff --git a/src/Domain/Subscription/Service/Manager/SubscriberBlacklistManager.php b/src/Domain/Subscription/Service/Manager/SubscriberBlacklistManager.php new file mode 100644 index 00000000..d30bae2d --- /dev/null +++ b/src/Domain/Subscription/Service/Manager/SubscriberBlacklistManager.php @@ -0,0 +1,86 @@ +subscriberRepository->isEmailBlacklisted($email); + } + + public function getBlacklistInfo(string $email): ?UserBlacklist + { + return $this->userBlacklistRepository->findBlacklistInfoByEmail($email); + } + + public function addEmailToBlacklist(string $email, ?string $reasonData = null): UserBlacklist + { + $existing = $this->subscriberRepository->isEmailBlacklisted($email); + if ($existing) { + return $this->getBlacklistInfo($email); + } + + $blacklistEntry = new UserBlacklist(); + $blacklistEntry->setEmail($email); + $blacklistEntry->setAdded(new DateTime()); + + $this->entityManager->persist($blacklistEntry); + + if ($reasonData !== null) { + $blacklistData = new UserBlacklistData(); + $blacklistData->setEmail($email); + $blacklistData->setName('reason'); + $blacklistData->setData($reasonData); + $this->entityManager->persist($blacklistData); + } + + $this->entityManager->flush(); + + return $blacklistEntry; + } + + public function removeEmailFromBlacklist(string $email): void + { + $blacklistEntry = $this->userBlacklistRepository->findOneByEmail($email); + if ($blacklistEntry) { + $this->entityManager->remove($blacklistEntry); + } + + $blacklistData = $this->blacklistDataRepository->findOneByEmail($email); + if ($blacklistData) { + $this->entityManager->remove($blacklistData); + } + + $subscriber = $this->subscriberRepository->findOneByEmail($email); + if ($subscriber) { + $subscriber->setBlacklisted(false); + } + + $this->entityManager->flush(); + } + + public function getBlacklistReason(string $email): ?string + { + $data = $this->blacklistDataRepository->findOneByEmail($email); + return $data ? $data->getData() : null; + } +} diff --git a/tests/Unit/Domain/Subscription/Service/Manager/SubscriberBlacklistManagerTest.php b/tests/Unit/Domain/Subscription/Service/Manager/SubscriberBlacklistManagerTest.php new file mode 100644 index 00000000..25fdf5ca --- /dev/null +++ b/tests/Unit/Domain/Subscription/Service/Manager/SubscriberBlacklistManagerTest.php @@ -0,0 +1,202 @@ +subscriberRepository = $this->createMock(SubscriberRepository::class); + $this->userBlacklistRepository = $this->createMock(UserBlacklistRepository::class); + $this->userBlacklistDataRepository = $this->createMock(UserBlacklistDataRepository::class); + $this->entityManager = $this->createMock(EntityManagerInterface::class); + + $this->manager = new SubscriberBlacklistManager( + subscriberRepository: $this->subscriberRepository, + userBlacklistRepository: $this->userBlacklistRepository, + blacklistDataRepository: $this->userBlacklistDataRepository, + entityManager: $this->entityManager, + ); + } + + public function testIsEmailBlacklistedReturnsValueFromRepository(): void + { + $this->subscriberRepository + ->expects($this->once()) + ->method('isEmailBlacklisted') + ->with('test@example.com') + ->willReturn(true); + + $result = $this->manager->isEmailBlacklisted('test@example.com'); + + $this->assertTrue($result); + } + + public function testGetBlacklistInfoReturnsResultFromRepository(): void + { + $userBlacklist = $this->createMock(UserBlacklist::class); + + $this->userBlacklistRepository + ->expects($this->once()) + ->method('findBlacklistInfoByEmail') + ->with('foo@bar.com') + ->willReturn($userBlacklist); + + $result = $this->manager->getBlacklistInfo('foo@bar.com'); + + $this->assertSame($userBlacklist, $result); + } + + public function testAddEmailToBlacklistDoesNotAddIfAlreadyBlacklisted(): void + { + $this->subscriberRepository + ->expects($this->once()) + ->method('isEmailBlacklisted') + ->with('already@blacklisted.com') + ->willReturn(true); + + $this->userBlacklistRepository + ->expects($this->once()) + ->method('findBlacklistInfoByEmail') + ->willReturn($this->createMock(UserBlacklist::class)); + + $this->entityManager + ->expects($this->never()) + ->method('persist'); + + $this->entityManager + ->expects($this->never()) + ->method('flush'); + + $this->manager->addEmailToBlacklist('already@blacklisted.com', 'reason'); + } + + public function testAddEmailToBlacklistAddsEntryAndReason(): void + { + $this->subscriberRepository + ->expects($this->once()) + ->method('isEmailBlacklisted') + ->with('new@blacklist.com') + ->willReturn(false); + + $this->entityManager + ->expects($this->exactly(2)) + ->method('persist') + ->withConsecutive( + [$this->isInstanceOf(UserBlacklist::class)], + [$this->isInstanceOf(UserBlacklistData::class)] + ); + + $this->entityManager + ->expects($this->once()) + ->method('flush'); + + $this->manager->addEmailToBlacklist('new@blacklist.com', 'test reason'); + } + + public function testAddEmailToBlacklistAddsEntryWithoutReason(): void + { + $this->subscriberRepository + ->expects($this->once()) + ->method('isEmailBlacklisted') + ->with('noreason@blacklist.com') + ->willReturn(false); + + $this->entityManager + ->expects($this->once()) + ->method('persist') + ->with($this->isInstanceOf(UserBlacklist::class)); + + $this->entityManager + ->expects($this->once()) + ->method('flush'); + + $this->manager->addEmailToBlacklist('noreason@blacklist.com'); + } + + public function testRemoveEmailFromBlacklistRemovesAllRelatedData(): void + { + $blacklist = $this->createMock(UserBlacklist::class); + $blacklistData = $this->createMock(UserBlacklistData::class); + $subscriber = $this->getMockBuilder(Subscriber::class) + ->onlyMethods(['setBlacklisted']) + ->getMock(); + + $this->userBlacklistRepository + ->expects($this->once()) + ->method('findOneByEmail') + ->with('remove@me.com') + ->willReturn($blacklist); + + $this->userBlacklistDataRepository + ->expects($this->once()) + ->method('findOneByEmail') + ->with('remove@me.com') + ->willReturn($blacklistData); + + $this->subscriberRepository + ->expects($this->once()) + ->method('findOneByEmail') + ->with('remove@me.com') + ->willReturn($subscriber); + + $this->entityManager + ->expects($this->exactly(2)) + ->method('remove') + ->withConsecutive([$blacklist], [$blacklistData]); + + $subscriber->expects($this->once())->method('setBlacklisted')->with(false); + + $this->entityManager + ->expects($this->once()) + ->method('flush'); + + $this->manager->removeEmailFromBlacklist('remove@me.com'); + } + + public function testGetBlacklistReasonReturnsReasonOrNull(): void + { + $blacklistData = $this->createMock(UserBlacklistData::class); + $blacklistData->expects($this->once())->method('getData')->willReturn('my reason'); + + $this->userBlacklistDataRepository + ->expects($this->once()) + ->method('findOneByEmail') + ->with('why@blacklist.com') + ->willReturn($blacklistData); + + $result = $this->manager->getBlacklistReason('why@blacklist.com'); + $this->assertSame('my reason', $result); + } + + public function testGetBlacklistReasonReturnsNullIfNoData(): void + { + $this->userBlacklistDataRepository + ->expects($this->once()) + ->method('findOneByEmail') + ->with('none@blacklist.com') + ->willReturn(null); + + $result = $this->manager->getBlacklistReason('none@blacklist.com'); + $this->assertNull($result); + } +}