Skip to content

Commit d7fa96a

Browse files
committed
Add: copy as draft message functionality
1 parent a6502a4 commit d7fa96a

8 files changed

Lines changed: 89 additions & 41 deletions

File tree

src/Domain/Analytics/Service/LinkTrackService.php

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
namespace PhpList\Core\Domain\Analytics\Service;
66

7-
use Doctrine\ORM\EntityManagerInterface;
87
use PhpList\Core\Core\ParameterProvider;
98
use PhpList\Core\Domain\Analytics\Exception\MissingMessageIdException;
109
use PhpList\Core\Domain\Analytics\Model\LinkTrack;
@@ -16,7 +15,6 @@ class LinkTrackService
1615
public function __construct(
1716
private readonly LinkTrackRepository $linkTrackRepository,
1817
private readonly ParameterProvider $paramProvider,
19-
private readonly EntityManagerInterface $entityManager
2018
) {
2119
}
2220

@@ -57,8 +55,6 @@ public function extractAndSaveLinks(MessagePrecacheDto $content, int $userId, ?i
5755
$links = array_unique($links);
5856

5957
$savedLinks = [];
60-
$newLinksPersisted = false;
61-
6258
foreach ($links as $url) {
6359
$existingLinkTrack = $this->linkTrackRepository->findByUrlUserIdAndMessageId($url, $userId, $messageId);
6460
if ($existingLinkTrack !== null) {
@@ -71,14 +67,9 @@ public function extractAndSaveLinks(MessagePrecacheDto $content, int $userId, ?i
7167
$linkTrack->setUrl($url);
7268

7369
$this->linkTrackRepository->persist($linkTrack);
74-
$newLinksPersisted = true;
7570
$savedLinks[] = $linkTrack;
7671
}
7772

78-
if ($newLinksPersisted) {
79-
$this->entityManager->flush();
80-
}
81-
8273
return $savedLinks;
8374
}
8475

src/Domain/Identity/Service/PermissionChecker.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use PhpList\Core\Domain\Common\Model\Interfaces\OwnableInterface;
99
use PhpList\Core\Domain\Identity\Model\Administrator;
1010
use PhpList\Core\Domain\Identity\Model\PrivilegeFlag;
11+
use PhpList\Core\Domain\Messaging\Model\ListMessage;
1112
use PhpList\Core\Domain\Messaging\Model\Message;
1213
use PhpList\Core\Domain\Subscription\Model\Subscriber;
1314
use PhpList\Core\Domain\Subscription\Model\SubscriberList;
@@ -70,9 +71,7 @@ private function resolveRelatedEntity(DomainModel $resource, string $relatedClas
7071
}
7172

7273
if ($resource instanceof Message && $relatedClass === SubscriberList::class) {
73-
// todo: check which one is correct
74-
// return $resource->getListMessages()->map(fn(ListMessage $lm) => $lm->getList())->toArray();
75-
return $resource->getListMessages()->map(fn($lm) => $lm->getSubscriberList())->toArray();
74+
return $resource->getListMessages()->map(fn(ListMessage $lm) => $lm->getList())->toArray();
7675
}
7776

7877
return [];

src/Domain/Messaging/MessageHandler/CampaignProcessorMessageHandler.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,12 +199,13 @@ private function handleEmailSending(
199199
UserMessage $userMessage,
200200
MessagePrecacheDto $precachedContent,
201201
): void {
202-
// todo: check at which point link tracking should be applied (maybe after constructing ful text?)
202+
// todo: check at which point link tracking should be applied (maybe after constructing full text?)
203203
$processed = $this->messagePreparator->processMessageLinks(
204204
campaignId: $campaign->getId(),
205205
cachedMessageDto: $precachedContent,
206206
subscriber: $subscriber
207207
);
208+
$this->entityManager->flush();
208209

209210
try {
210211
$result = $this->campaignEmailBuilder->buildCampaignEmail(

src/Domain/Messaging/Model/Message.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ class Message implements DomainModel, Identity, ModificationDate, OwnableInterfa
6161
#[ORM\JoinColumn(name: 'template', referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
6262
private ?Template $template = null;
6363

64+
/**
65+
* @var Collection<int, ListMessage>
66+
*/
6467
#[ORM\OneToMany(targetEntity: ListMessage::class, mappedBy: 'message')]
6568
private Collection $listMessages;
6669

@@ -190,6 +193,9 @@ public function setOptions(MessageOptions $options): self
190193
return $this;
191194
}
192195

196+
/**
197+
* @return Collection<int, ListMessage>
198+
*/
193199
public function getListMessages(): Collection
194200
{
195201
return $this->listMessages;

src/Domain/Messaging/Service/Manager/MessageManager.php

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,16 @@
88
use PhpList\Core\Domain\Messaging\Model\Dto\MessageContext;
99
use PhpList\Core\Domain\Messaging\Model\Dto\MessageDtoInterface;
1010
use PhpList\Core\Domain\Messaging\Model\Message;
11+
use PhpList\Core\Domain\Messaging\Model\Message\MessageMetadata;
1112
use PhpList\Core\Domain\Messaging\Repository\MessageRepository;
1213
use PhpList\Core\Domain\Messaging\Service\Builder\MessageBuilder;
1314

1415
class MessageManager
1516
{
16-
private MessageRepository $messageRepository;
17-
private MessageBuilder $messageBuilder;
18-
19-
public function __construct(MessageRepository $messageRepository, MessageBuilder $messageBuilder)
20-
{
21-
$this->messageRepository = $messageRepository;
22-
$this->messageBuilder = $messageBuilder;
17+
public function __construct(
18+
private readonly MessageRepository $messageRepository,
19+
private readonly MessageBuilder $messageBuilder,
20+
) {
2321
}
2422

2523
public function createMessage(MessageDtoInterface $createMessageDto, Administrator $authUser): Message
@@ -31,6 +29,27 @@ public function createMessage(MessageDtoInterface $createMessageDto, Administrat
3129
return $message;
3230
}
3331

32+
public function copyAsDraftMessage(Message $message, Administrator $authUser): Message
33+
{
34+
$newMessage = new Message(
35+
format: new Message\MessageFormat(
36+
htmlFormatted: $message->getFormat()->isHtmlFormatted(),
37+
sendFormat: $message->getFormat()->getSendFormat()
38+
),
39+
schedule: clone $message->getSchedule(),
40+
metadata: new MessageMetadata(status: Message\MessageStatus::Draft),
41+
content: clone $message->getContent(),
42+
options: clone $message->getOptions(),
43+
owner: $authUser,
44+
template: $message->getTemplate(),
45+
);
46+
$newMessage->setUuid(bin2hex(random_bytes(18)));
47+
48+
$this->messageRepository->persist($newMessage);
49+
50+
return $newMessage;
51+
}
52+
3453
public function updateMessage(
3554
MessageDtoInterface $updateMessageDto,
3655
Message $message,

tests/Unit/Domain/Analytics/Service/LinkTrackServiceTest.php

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
namespace PhpList\Core\Tests\Unit\Domain\Analytics\Service;
66

7-
use Doctrine\ORM\EntityManagerInterface;
87
use PhpList\Core\Core\ParameterProvider;
98
use PhpList\Core\Domain\Analytics\Exception\MissingMessageIdException;
109
use PhpList\Core\Domain\Analytics\Model\LinkTrack;
@@ -30,11 +29,7 @@ protected function setUp(): void
3029
->with('click_track', false)
3130
->willReturn(true);
3231

33-
$this->subject = new LinkTrackService(
34-
$this->linkTrackRepository,
35-
$paramProvider,
36-
$this->createMock(EntityManagerInterface::class)
37-
);
32+
$this->subject = new LinkTrackService($this->linkTrackRepository, $paramProvider);
3833
}
3934

4035
public function testExtractAndSaveLinksWithNoLinks(): void
@@ -214,11 +209,7 @@ public function testIsExtractAndSaveLinksApplicableWhenClickTrackIsFalse(): void
214209
->with('click_track', false)
215210
->willReturn(false);
216211

217-
$subject = new LinkTrackService(
218-
$this->linkTrackRepository,
219-
$paramProvider,
220-
$this->createMock(EntityManagerInterface::class)
221-
);
212+
$subject = new LinkTrackService($this->linkTrackRepository, $paramProvider);
222213

223214
self::assertFalse($subject->isExtractAndSaveLinksApplicable());
224215
}

tests/Unit/Domain/Messaging/Service/Manager/MessageManagerTest.php

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace PhpList\Core\Tests\Unit\Domain\Messaging\Service\Manager;
66

7+
use DateTime;
78
use PhpList\Core\Domain\Identity\Model\Administrator;
89
use PhpList\Core\Domain\Messaging\Model\Dto\CreateMessageDto;
910
use PhpList\Core\Domain\Messaging\Model\Dto\Message\MessageContentDto;
@@ -13,6 +14,10 @@
1314
use PhpList\Core\Domain\Messaging\Model\Dto\Message\MessageScheduleDto;
1415
use PhpList\Core\Domain\Messaging\Model\Dto\UpdateMessageDto;
1516
use PhpList\Core\Domain\Messaging\Model\Message;
17+
use PhpList\Core\Domain\Messaging\Model\Message\MessageFormat;
18+
use PhpList\Core\Domain\Messaging\Model\Message\MessageMetadata;
19+
use PhpList\Core\Domain\Messaging\Model\Message\MessageOptions;
20+
use PhpList\Core\Domain\Messaging\Model\Message\MessageSchedule;
1621
use PhpList\Core\Domain\Messaging\Model\Message\MessageContent;
1722
use PhpList\Core\Domain\Messaging\Repository\MessageRepository;
1823
use PhpList\Core\Domain\Messaging\Service\Builder\MessageBuilder;
@@ -21,6 +26,44 @@
2126

2227
class MessageManagerTest extends TestCase
2328
{
29+
public function testCopyAsDraftMessagePersistsClonedDraftMessage(): void
30+
{
31+
$messageRepository = $this->createMock(MessageRepository::class);
32+
$messageBuilder = $this->createMock(MessageBuilder::class);
33+
$manager = new MessageManager($messageRepository, $messageBuilder);
34+
35+
$message = new Message(
36+
format: new MessageFormat(true, 'html'),
37+
schedule: new MessageSchedule(
38+
repeatInterval: 0,
39+
repeatUntil: null,
40+
requeueInterval: 0,
41+
requeueUntil: null,
42+
embargo: new DateTime('2025-04-17T09:00:00+00:00')
43+
),
44+
metadata: new MessageMetadata(Message\MessageStatus::Submitted),
45+
content: new MessageContent('Subject', 'Full text', 'Short text', 'Footer'),
46+
options: new MessageOptions('from@example.com', 'to@example.com', 'reply@example.com', 'all-users'),
47+
owner: null
48+
);
49+
50+
$messageRepository->expects($this->once())
51+
->method('persist')
52+
->with($this->callback(function (Message $persistedMessage) use ($message): bool {
53+
$this->assertNotSame($message, $persistedMessage);
54+
$this->assertSame(Message\MessageStatus::Draft, $persistedMessage->getMetadata()->getStatus());
55+
$this->assertTrue($persistedMessage->getFormat()->isHtmlFormatted());
56+
$this->assertSame('html', $persistedMessage->getFormat()->getSendFormat());
57+
58+
return true;
59+
}));
60+
61+
$result = $manager->copyAsDraftMessage($message, $this->createMock(Administrator::class));
62+
63+
$this->assertSame(Message\MessageStatus::Draft, $result->getMetadata()->getStatus());
64+
$this->assertNotSame($message, $result);
65+
}
66+
2467
public function testCreateMessageReturnsPersistedMessage(): void
2568
{
2669
$messageRepository = $this->createMock(MessageRepository::class);

tests/Unit/Domain/Messaging/Service/MessageDataLoaderTest.php

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@
77
use Doctrine\Common\Collections\ArrayCollection;
88
use PhpList\Core\Domain\Configuration\Model\ConfigOption;
99
use PhpList\Core\Domain\Configuration\Service\Provider\ConfigProvider;
10+
use PhpList\Core\Domain\Messaging\Model\ListMessage;
1011
use PhpList\Core\Domain\Messaging\Model\Message;
1112
use PhpList\Core\Domain\Messaging\Model\MessageData;
1213
use PhpList\Core\Domain\Messaging\Repository\MessageDataRepository;
1314
use PhpList\Core\Domain\Messaging\Repository\MessageRepository;
1415
use PhpList\Core\Domain\Messaging\Service\MessageDataLoader;
16+
use PhpList\Core\Domain\Subscription\Model\SubscriberList;
1517
use PHPUnit\Framework\MockObject\MockObject;
1618
use PHPUnit\Framework\TestCase;
1719
use Psr\Log\LoggerInterface;
@@ -61,6 +63,11 @@ public function testLoadsMessageDataMergesAndParses(): void
6163
$md2 = (new MessageData())->setId($messageId)->setName('criteria_match')->setData('any');
6264
$md3 = (new MessageData())->setId($messageId)->setName('embargo')->setData('string');
6365

66+
$listMock = $this->createMock(SubscriberList::class);
67+
$listMock->method('getId')->willReturn(42);
68+
$listMessageMock = $this->createMock(ListMessage::class);
69+
$listMessageMock->method('getList')->willReturn($listMock);
70+
6471
$this->messageDataRepository
6572
->method('getForMessage')
6673
->with($messageId)
@@ -69,16 +76,7 @@ public function testLoadsMessageDataMergesAndParses(): void
6976
// Use a Message mock instead of an anonymous stub
7077
$message = $this->createMock(Message::class);
7178
$message->method('getId')->willReturn($messageId);
72-
$message->method('getListMessages')->willReturn(
73-
new ArrayCollection([
74-
new class {
75-
public function getListId(): int
76-
{
77-
return 42;
78-
}
79-
},
80-
])
81-
);
79+
$message->method('getListMessages')->willReturn(new ArrayCollection([$listMessageMock]));
8280

8381
$loader = new MessageDataLoader(
8482
configProvider: $this->config,

0 commit comments

Comments
 (0)