Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions config/services/providers.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,6 @@ services:
autowire: true
arguments:
$cache: '@Psr\SimpleCache\CacheInterface'

PhpList\Core\Domain\Subscription\Service\Provider\SubscriberAttributeChangeSetProvider:
autowire: true
42 changes: 42 additions & 0 deletions resources/translations/messages.en.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,48 @@ Thank you.</target>
<source>Footer of public pages</source>
<target>__Footer of public pages</target>
</trans-unit>
<trans-unit id="vmzIZ25" resname="Please confirm your subscription">
<source>Please confirm your subscription</source>
<target>__Please confirm your subscription</target>
</trans-unit>
<trans-unit id="rT_b2Tm" resname="No user details changed">
<source>No user details changed</source>
<target>__No user details changed</target>
</trans-unit>
<trans-unit id="qdTR5bi" resname="%field% = %new% *changed* from %old%">
<source>%field% = %new% *changed* from %old%</source>
<target>__%field% = %new% *changed* from %old%</target>
</trans-unit>
<trans-unit id="rns1KOG" resname="Subscribed to %list%">
<source>Subscribed to %list%</source>
<target>__Subscribed to %list%</target>
</trans-unit>
<trans-unit id="eXllcQa" resname="Subscriber marked unconfirmed for invalid email address">
<source>Subscriber marked unconfirmed for invalid email address</source>
<target>__Subscriber marked unconfirmed for invalid email address</target>
</trans-unit>
<trans-unit id="oY0Gck3" resname="Marked unconfirmed while sending campaign %message_id%">
<source>Marked unconfirmed while sending campaign %message_id%</source>
<target>__Marked unconfirmed while sending campaign %message_id%</target>
</trans-unit>
<trans-unit id="duYukTn" resname="Update by %admin%">
<source>Update by %admin%</source>
<target>__Update by %admin%</target>
</trans-unit>
<trans-unit id="7oMcCd6" resname="(no data)">
<source>(no data)</source>
<target>__(no data)</target>
</trans-unit>
<trans-unit id="rSZSC.W" resname="%attribute% = %new_value% &#10; changed from %old_value%">
<source>%attribute% = %new_value%
changed from %old_value%</source>
<target>__%attribute% = %new_value%
changed from %old_value%</target>
</trans-unit>
<trans-unit id="jkLAivD" resname="No data changed">
<source>No data changed</source>
<target>__No data changed</target>
</trans-unit>
</body>
</file>
</xliff>
14 changes: 14 additions & 0 deletions src/Domain/Messaging/Service/Processor/CampaignProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use PhpList\Core\Domain\Messaging\Service\RateLimitedCampaignMailer;
use PhpList\Core\Domain\Messaging\Service\MaxProcessTimeLimiter;
use PhpList\Core\Domain\Messaging\Service\MessageProcessingPreparator;
use PhpList\Core\Domain\Subscription\Service\Manager\SubscriberHistoryManager;
use PhpList\Core\Domain\Subscription\Service\Provider\SubscriberProvider;
use PhpList\Core\Domain\Subscription\Model\Subscriber;
use Psr\Log\LoggerInterface;
Expand All @@ -23,6 +24,8 @@

/**
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
* @SuppressWarnings(PHPMD.StaticAccess)
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
class CampaignProcessor
{
Expand All @@ -35,6 +38,7 @@ class CampaignProcessor
private MaxProcessTimeLimiter $timeLimiter;
private RequeueHandler $requeueHandler;
private TranslatorInterface $translator;
private SubscriberHistoryManager $subscriberHistoryManager;

public function __construct(
RateLimitedCampaignMailer $mailer,
Expand All @@ -46,6 +50,7 @@ public function __construct(
MaxProcessTimeLimiter $timeLimiter,
RequeueHandler $requeueHandler,
TranslatorInterface $translator,
SubscriberHistoryManager $subscriberHistoryManager,
) {
$this->mailer = $mailer;
$this->entityManager = $entityManager;
Expand All @@ -56,6 +61,7 @@ public function __construct(
$this->timeLimiter = $timeLimiter;
$this->requeueHandler = $requeueHandler;
$this->translator = $translator;
$this->subscriberHistoryManager = $subscriberHistoryManager;
}

public function process(Message $campaign, ?OutputInterface $output = null): void
Expand Down Expand Up @@ -89,6 +95,14 @@ public function process(Message $campaign, ?OutputInterface $output = null): voi
$output?->writeln($this->translator->trans('Invalid email, marking unconfirmed: %email%', [
'%email%' => $subscriber->getEmail(),
]));
$this->subscriberHistoryManager->addHistory(
subscriber: $subscriber,
message: $this->translator->trans('Subscriber marked unconfirmed for invalid email address'),
details: $this->translator->trans(
'Marked unconfirmed while sending campaign %message_id%',
['%message_id%' => $campaign->getId()]
)
);
continue;
}

Expand Down
81 changes: 81 additions & 0 deletions src/Domain/Subscription/Model/Dto/ChangeSetDto.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

declare(strict_types=1);

namespace PhpList\Core\Domain\Subscription\Model\Dto;

class ChangeSetDto
{
public const IGNORED_ATTRIBUTES = ['password', 'modified'];

/**
* @var array<string, array{0: mixed, 1: mixed}>
*
* Example:
* [
* 'email' => [null, '[email protected]'],
* 'isActive' => [true, false]
* ]
*/
private array $changes = [];

/**
* @param array<string, array{0: mixed, 1: mixed}> $changes
*/
public function __construct(array $changes = [])
{
$this->changes = $changes;
}

/**
* @return array<string, array{0: mixed, 1: mixed}>
*/
public function getChanges(): array
{
return $this->changes;
}

public function hasChanges(): bool
{
return !empty($this->changes);
}

public function hasField(string $field): bool
{
return array_key_exists($field, $this->changes);
}

/**
* @return array{0: mixed, 1: mixed}|null
*/
public function getFieldChange(string $field): ?array
{
return $this->changes[$field] ?? null;
}

/**
* @return mixed|null
*/
public function getOldValue(string $field): mixed
{
return $this->changes[$field][0] ?? null;
}

/**
* @return mixed|null
*/
public function getNewValue(string $field): mixed
{
return $this->changes[$field][1] ?? null;
}

public function toArray(): array
{
return $this->changes;
}

public static function fromDoctrineChangeSet(array $changeSet): self
{
return new self($changeSet);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,29 @@

use Doctrine\ORM\EntityManagerInterface;
use PhpList\Core\Domain\Subscription\Exception\SubscriberAttributeCreationException;
use PhpList\Core\Domain\Subscription\Model\Dto\ChangeSetDto;
use PhpList\Core\Domain\Subscription\Model\Subscriber;
use PhpList\Core\Domain\Subscription\Model\SubscriberAttributeDefinition;
use PhpList\Core\Domain\Subscription\Model\SubscriberAttributeValue;
use PhpList\Core\Domain\Subscription\Repository\SubscriberAttributeDefinitionRepository;
use PhpList\Core\Domain\Subscription\Repository\SubscriberAttributeValueRepository;
use Symfony\Contracts\Translation\TranslatorInterface;

class SubscriberAttributeManager
{
private SubscriberAttributeValueRepository $attributeRepository;
private SubscriberAttributeDefinitionRepository $attrDefinitionRepository;
private EntityManagerInterface $entityManager;
private TranslatorInterface $translator;

public function __construct(
SubscriberAttributeValueRepository $attributeRepository,
SubscriberAttributeDefinitionRepository $attrDefinitionRepository,
EntityManagerInterface $entityManager,
TranslatorInterface $translator,
) {
$this->attributeRepository = $attributeRepository;
$this->attrDefinitionRepository = $attrDefinitionRepository;
$this->entityManager = $entityManager;
$this->translator = $translator;
}
Expand All @@ -33,6 +38,8 @@ public function createOrUpdate(
SubscriberAttributeDefinition $definition,
?string $value = null
): SubscriberAttributeValue {
// phpcs:ignore Generic.Commenting.Todo
// todo: clarify which attributes can be created/updated
$subscriberAttribute = $this->attributeRepository
->findOneBySubscriberAndAttribute($subscriber, $definition);

Expand Down Expand Up @@ -60,4 +67,23 @@ public function delete(SubscriberAttributeValue $attribute): void
{
$this->attributeRepository->remove($attribute);
}

public function processAttributes(Subscriber $subscriber, array $attributeData): void
{
foreach ($attributeData as $key => $value) {
$lowerKey = strtolower((string)$key);
if (in_array($lowerKey, ChangeSetDto::IGNORED_ATTRIBUTES, true)) {
continue;
}

$attributeDefinition = $this->attrDefinitionRepository->findOneByName($key);
if ($attributeDefinition !== null) {
$this->createOrUpdate(
subscriber: $subscriber,
definition: $attributeDefinition,
value: $value
);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,37 @@

namespace PhpList\Core\Domain\Subscription\Service\Manager;

use Doctrine\ORM\EntityManagerInterface;
use PhpList\Core\Domain\Common\ClientIpResolver;
use PhpList\Core\Domain\Common\SystemInfoCollector;
use PhpList\Core\Domain\Identity\Model\Administrator;
use PhpList\Core\Domain\Subscription\Model\Dto\ChangeSetDto;
use PhpList\Core\Domain\Subscription\Model\Filter\SubscriberHistoryFilter;
use PhpList\Core\Domain\Subscription\Model\Subscriber;
use PhpList\Core\Domain\Subscription\Model\SubscriberHistory;
use PhpList\Core\Domain\Subscription\Repository\SubscriberHistoryRepository;
use Symfony\Contracts\Translation\TranslatorInterface;

class SubscriberHistoryManager
{
private SubscriberHistoryRepository $repository;
private ClientIpResolver $clientIpResolver;
private SystemInfoCollector $systemInfoCollector;
private TranslatorInterface $translator;
private EntityManagerInterface $entityManager;

public function __construct(
SubscriberHistoryRepository $repository,
ClientIpResolver $clientIpResolver,
SystemInfoCollector $systemInfoCollector,
TranslatorInterface $translator,
EntityManagerInterface $entityManager,
) {
$this->repository = $repository;
$this->clientIpResolver = $clientIpResolver;
$this->systemInfoCollector = $systemInfoCollector;
$this->translator = $translator;
$this->entityManager = $entityManager;
}

public function getHistory(int $lastId, int $limit, SubscriberHistoryFilter $filter): array
Expand All @@ -40,8 +50,94 @@ public function addHistory(Subscriber $subscriber, string $message, ?string $det
$subscriberHistory->setSystemInfo($this->systemInfoCollector->collectAsString());
$subscriberHistory->setIp($this->clientIpResolver->resolve());

$this->repository->save($subscriberHistory);
$this->entityManager->persist($subscriberHistory);

return $subscriberHistory;
}

public function addHistoryFromChangeSet(
Subscriber $subscriber,
string $message,
ChangeSetDto $changeSet,
): SubscriberHistory {
$details = '';
foreach ($changeSet->getChanges() as $attribute => [$old, $new]) {
if (in_array($attribute, ChangeSetDto::IGNORED_ATTRIBUTES, true) || $new === null) {
continue;
}
$details .= $this->translator->trans(
"%attribute% = %new_value% \n changed from %old_value%",
[
'%attribute%' => $attribute,
'%new_value%' => $new,
'%old_value%' => $old ?? $this->translator->trans('(no data)'),
]
) . PHP_EOL;
}

if ($details === '') {
$details .= $this->translator->trans('No data changed') . PHP_EOL;
}

return $this->addHistory($subscriber, $message, $details);
}

public function addHistoryFromImport(
Subscriber $subscriber,
array $listLines,
ChangeSetDto $changeSetDto,
?Administrator $admin = null,
): void {
$headerLine = sprintf('API-v2-import - %s: %s%s', $admin ? 'Admin' : 'CLI', $admin?->getId(), "\n\n");

$lines = $this->getHistoryLines($changeSetDto, $listLines);

$this->addHistory(
subscriber: $subscriber,
message: 'Import by ' . $admin?->getLoginName(),
details: $headerLine . implode(PHP_EOL, $lines) . PHP_EOL
);
}

public function addHistoryFromApi(
Subscriber $subscriber,
array $listLines,
ChangeSetDto $updatedData,
?Administrator $admin = null,
): void {
$lines = $this->getHistoryLines($updatedData, $listLines);

$this->addHistory(
subscriber: $subscriber,
message: $this->translator->trans('Update by %admin%', ['%admin%' => $admin->getLoginName()]),
details: implode(PHP_EOL, $lines) . PHP_EOL
);
}

private function getHistoryLines(ChangeSetDto $updatedData, array $listLines): array
{
$lines = [];
if (!$updatedData->hasChanges() && empty($listLines)) {
$lines[] = $this->translator->trans('No user details changed');
} else {
foreach ($updatedData->getChanges() as $field => [$old, $new]) {
if (in_array($field, ChangeSetDto::IGNORED_ATTRIBUTES, true)) {
continue;
}
$lines[] = $this->translator->trans(
'%field% = %new% *changed* from %old%',
[
'%field' => $field,
'%new%' => json_encode($new),
'%old%' => json_encode($old)
],
);
}
foreach ($listLines as $line) {
$lines[] = $line;
}
}

return $lines;
}
}
Loading
Loading