From f066bc8fddbbaf91f757ff63c80734b4cc3c4d22 Mon Sep 17 00:00:00 2001 From: Rostislav Wolny Date: Fri, 19 Mar 2021 16:31:21 +0100 Subject: [PATCH] Disallow trashing single dynamic segment with active email [MAILPOET-3463] --- lib/API/JSON/v1/DynamicSegments.php | 19 +++++++++++++- .../Segment/NewsletterSegmentRepository.php | 26 +++++++++++++++++++ .../API/JSON/v1/DynamicSegmentsTest.php | 21 +++++++++++++++ 3 files changed, 65 insertions(+), 1 deletion(-) diff --git a/lib/API/JSON/v1/DynamicSegments.php b/lib/API/JSON/v1/DynamicSegments.php index 2e8220b478..26ddee5495 100644 --- a/lib/API/JSON/v1/DynamicSegments.php +++ b/lib/API/JSON/v1/DynamicSegments.php @@ -10,6 +10,7 @@ use MailPoet\API\JSON\ResponseBuilders\DynamicSegmentsResponseBuilder; use MailPoet\Config\AccessControl; use MailPoet\Entities\SegmentEntity; use MailPoet\Listing\Handler; +use MailPoet\Newsletter\Segment\NewsletterSegmentRepository; use MailPoet\Segments\DynamicSegments\DynamicSegmentsListingRepository; use MailPoet\Segments\DynamicSegments\Exceptions\InvalidFilterException; use MailPoet\Segments\DynamicSegments\FilterDataMapper; @@ -46,6 +47,9 @@ class DynamicSegments extends APIEndpoint { /** @var FilterDataMapper */ private $filterDataMapper; + /** @var NewsletterSegmentRepository */ + private $newsletterSegmentRepository; + public function __construct( Handler $handler, DynamicSegmentsListingRepository $dynamicSegmentsListingRepository, @@ -53,7 +57,8 @@ class DynamicSegments extends APIEndpoint { SegmentsRepository $segmentsRepository, SegmentSubscribersRepository $segmentSubscribersRepository, FilterDataMapper $filterDataMapper, - SegmentSaveController $saveController + SegmentSaveController $saveController, + NewsletterSegmentRepository $newsletterSegmentRepository ) { $this->listingHandler = $handler; $this->dynamicSegmentsListingRepository = $dynamicSegmentsListingRepository; @@ -62,6 +67,7 @@ class DynamicSegments extends APIEndpoint { $this->saveController = $saveController; $this->segmentSubscribersRepository = $segmentSubscribersRepository; $this->filterDataMapper = $filterDataMapper; + $this->newsletterSegmentRepository = $newsletterSegmentRepository; } public function get($data = []) { @@ -148,6 +154,17 @@ class DynamicSegments extends APIEndpoint { ]); } + $activelyUsedNewslettersSubjects = $this->newsletterSegmentRepository->getSubjectsOfActivelyUsedEmailsForSegments([$segment->getId()]); + if (isset($activelyUsedNewslettersSubjects[$segment->getId()])) { + return $this->badRequest([ + Error::BAD_REQUEST => str_replace( + '%$1s', + $activelyUsedNewslettersSubjects[$segment->getId()][0], + _x('List cannot be deleted because it’s used for %$1s email', 'Alert shown when trying to delete segment, which is assigned to any automatic emails.') + ), + ]); + } + $this->segmentsRepository->bulkTrash([$segment->getId()], SegmentEntity::TYPE_DYNAMIC); return $this->successResponse( $this->segmentsResponseBuilder->build($segment), diff --git a/lib/Newsletter/Segment/NewsletterSegmentRepository.php b/lib/Newsletter/Segment/NewsletterSegmentRepository.php index 49ac2b5abd..d4a07566a6 100644 --- a/lib/Newsletter/Segment/NewsletterSegmentRepository.php +++ b/lib/Newsletter/Segment/NewsletterSegmentRepository.php @@ -14,6 +14,32 @@ class NewsletterSegmentRepository extends Repository { return NewsletterSegmentEntity::class; } + public function getSubjectsOfActivelyUsedEmailsForSegments(array $segmentIds): array { + $results = $this->doctrineRepository->createQueryBuilder('ns') + ->join('ns.newsletter', 'n') + ->leftJoin('n.queues', 'q') + ->leftJoin('q.task', 't') + ->select('IDENTITY(ns.segment) AS segment_id, n.subject') + ->where('(n.type IN (:types) OR n.status = :scheduled OR (t.id IS NOT NULL AND t.status IS NULL))') + ->andWhere('ns.segment IN (:segmentIds)') + ->setParameter('types', [ + NewsletterEntity::TYPE_AUTOMATIC, + NewsletterEntity::TYPE_WELCOME, + NewsletterEntity::TYPE_NOTIFICATION, + ]) + ->setParameter('segmentIds', $segmentIds) + ->setParameter('scheduled', NewsletterEntity::STATUS_SCHEDULED) + ->addGroupBy('n.id, q.id, t.id') + ->getQuery() + ->getResult(); + + $nameMap = []; + foreach ($results as $result) { + $nameMap[(string)$result['segment_id']][] = $result['subject']; + } + return $nameMap; + } + public function getAutomatedEmailSubjectsBySegmentIds(array $segmentIds): array { $results = $this->doctrineRepository->createQueryBuilder('ns') ->join('ns.newsletter', 'n') diff --git a/tests/integration/API/JSON/v1/DynamicSegmentsTest.php b/tests/integration/API/JSON/v1/DynamicSegmentsTest.php index 03fa9a9533..c71e23e906 100644 --- a/tests/integration/API/JSON/v1/DynamicSegmentsTest.php +++ b/tests/integration/API/JSON/v1/DynamicSegmentsTest.php @@ -2,9 +2,12 @@ namespace MailPoet\API\JSON\v1; +use MailPoet\API\JSON\Response as APIResponse; use MailPoet\DI\ContainerWrapper; use MailPoet\Entities\DynamicSegmentFilterData; use MailPoet\Entities\DynamicSegmentFilterEntity; +use MailPoet\Entities\NewsletterEntity; +use MailPoet\Entities\NewsletterSegmentEntity; use MailPoet\Entities\SegmentEntity; class DynamicSegmentsTest extends \MailPoetTest { @@ -83,6 +86,22 @@ class DynamicSegmentsTest extends \MailPoetTest { expect($dynamicSegment->getDeletedAt())->notNull(); } + public function testItReturnsErrorWhenTrashingSegmentWithActiveNewsletter() { + $dynamicSegment = $this->createDynamicSegmentEntity('Trash test 2', 'description'); + $newsletter = new NewsletterEntity(); + $newsletter->setSubject('Subject'); + $newsletter->setType(NewsletterEntity::TYPE_WELCOME); + $newsletterSegment = new NewsletterSegmentEntity($newsletter, $dynamicSegment); + $this->entityManager->persist($newsletter); + $this->entityManager->persist($newsletterSegment); + $this->entityManager->flush(); + + $response = $this->endpoint->trash(['id' => $dynamicSegment->getId()]); + $this->entityManager->refresh($dynamicSegment); + expect($response->status)->equals(APIResponse::STATUS_BAD_REQUEST); + expect($response->errors[0]['message'])->equals("List cannot be deleted because it’s used for 'Subject' email"); + } + public function testItCanRestoreASegment() { $dynamicSegment = $this->createDynamicSegmentEntity('Trash test', 'description'); @@ -187,5 +206,7 @@ class DynamicSegmentsTest extends \MailPoetTest { parent::_after(); $this->truncateEntity(SegmentEntity::class); $this->truncateEntity(DynamicSegmentFilterEntity::class); + $this->truncateEntity(NewsletterSegmentEntity::class); + $this->truncateEntity(NewsletterEntity::class); } }