diff --git a/config/parameters.yml.dist b/config/parameters.yml.dist
index 92b33bed..efbdc7e5 100644
--- a/config/parameters.yml.dist
+++ b/config/parameters.yml.dist
@@ -31,6 +31,8 @@ parameters:
env(APP_DEV_EMAIL): 'dev@dev.com'
app.powered_by_phplist: '%%env(APP_POWERED_BY_PHPLIST)%%'
env(APP_POWERED_BY_PHPLIST): '0'
+ app.preference_page_show_private_lists: '%%env(PREFERENCEPAGE_SHOW_PRIVATE_LISTS)%%'
+ env(PREFERENCEPAGE_SHOW_PRIVATE_LISTS): '0'
# Email configuration
app.mailer_from: '%%env(MAILER_FROM)%%'
diff --git a/config/services/services.yml b/config/services/services.yml
index cf298621..1517737b 100644
--- a/config/services/services.yml
+++ b/config/services/services.yml
@@ -123,6 +123,8 @@ services:
PhpList\Core\Domain\Configuration\Service\UserPersonalizer:
autowire: true
autoconfigure: true
+ arguments:
+ $preferencePageShowPrivateLists: '%app.preference_page_show_private_lists%'
PhpList\Core\Domain\Configuration\Service\LegacyUrlBuilder:
autowire: true
diff --git a/src/Domain/Configuration/Model/OutputFormat.php b/src/Domain/Configuration/Model/OutputFormat.php
new file mode 100644
index 00000000..c413d3ed
--- /dev/null
+++ b/src/Domain/Configuration/Model/OutputFormat.php
@@ -0,0 +1,11 @@
+subscriberRepository->findOneByEmail($email);
if (!$user) {
@@ -33,18 +39,49 @@ public function personalize(string $value, string $email): string
$resolver = new PlaceholderResolver();
$resolver->register('EMAIL', fn() => $user->getEmail());
- $resolver->register('UNSUBSCRIBEURL', function () use ($user) {
+ $resolver->register('UNSUBSCRIBEURL', function () use ($user, $format) {
$base = $this->config->getValue(ConfigOption::UnsubscribeUrl) ?? '';
- return $this->urlBuilder->withUid($base, $user->getUniqueId()) . self::PHP_SPACE;
+ $url = $this->urlBuilder->withUid($base, $user->getUniqueId());
+
+ if ($format === OutputFormat::Html) {
+ $label = $this->translator->trans('Unsubscribe');
+ $safeUrl = htmlspecialchars($url, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
+ $safeLabel = htmlspecialchars($label, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
+
+ return '' . $safeLabel . '' . self::PHP_SPACE;
+ }
+
+ return $url . self::PHP_SPACE;
});
- $resolver->register('CONFIRMATIONURL', function () use ($user) {
+ $resolver->register('CONFIRMATIONURL', function () use ($user, $format) {
$base = $this->config->getValue(ConfigOption::ConfirmationUrl) ?? '';
- return $this->urlBuilder->withUid($base, $user->getUniqueId()) . self::PHP_SPACE;
+ $url = $this->urlBuilder->withUid($base, $user->getUniqueId());
+
+ if ($format === OutputFormat::Html) {
+ $label = $this->translator->trans('Confirm');
+ $safeUrl = htmlspecialchars($url, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
+ $safeLabel = htmlspecialchars($label, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
+
+ return '' . $safeLabel . '' . self::PHP_SPACE;
+ }
+
+ return $url . self::PHP_SPACE;
});
- $resolver->register('PREFERENCESURL', function () use ($user) {
+
+ $resolver->register('PREFERENCESURL', function () use ($user, $format) {
$base = $this->config->getValue(ConfigOption::PreferencesUrl) ?? '';
- return $this->urlBuilder->withUid($base, $user->getUniqueId()) . self::PHP_SPACE;
+ $url = $this->urlBuilder->withUid($base, $user->getUniqueId());
+
+ if ($format === OutputFormat::Html) {
+ $label = $this->translator->trans('Update preferences');
+ $safeUrl = htmlspecialchars($url, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
+ $safeLabel = htmlspecialchars($label, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
+
+ return '' . $safeLabel . '' . self::PHP_SPACE;
+ }
+
+ return $url . self::PHP_SPACE;
});
$resolver->register(
@@ -54,6 +91,29 @@ public function personalize(string $value, string $email): string
$resolver->register('DOMAIN', fn() => $this->config->getValue(ConfigOption::Domain) ?? '');
$resolver->register('WEBSITE', fn() => $this->config->getValue(ConfigOption::Website) ?? '');
+ $resolver->register('LISTS', function () use ($user, $format) {
+ $names = $this->subscriberListRepository->getActiveListNamesForSubscriber(
+ subscriber: $user,
+ showPrivate: $this->preferencePageShowPrivateLists
+ );
+
+ if ($names === []) {
+ return $this->translator
+ ->trans('Sorry, you are not subscribed to any of our newsletters with this email address.');
+ }
+
+ $separator = $format === OutputFormat::Html ? '
' : "\n";
+
+ if ($format === OutputFormat::Html) {
+ $names = array_map(
+ static fn(string $name) => htmlspecialchars($name, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'),
+ $names
+ );
+ }
+
+ return implode($separator, $names);
+ });
+
$userAttributes = $this->attributesRepository->getForSubscriber($user);
foreach ($userAttributes as $userAttribute) {
$resolver->register(
diff --git a/src/Domain/Messaging/MessageHandler/AsyncEmailMessageHandler.php b/src/Domain/Messaging/MessageHandler/AsyncEmailMessageHandler.php
index 42d91417..bfda027a 100644
--- a/src/Domain/Messaging/MessageHandler/AsyncEmailMessageHandler.php
+++ b/src/Domain/Messaging/MessageHandler/AsyncEmailMessageHandler.php
@@ -14,11 +14,8 @@
#[AsMessageHandler]
class AsyncEmailMessageHandler
{
- private EmailService $emailService;
-
- public function __construct(EmailService $emailService)
+ public function __construct(private readonly EmailService $emailService)
{
- $this->emailService = $emailService;
}
/**
diff --git a/src/Domain/Messaging/MessageHandler/CampaignProcessorMessageHandler.php b/src/Domain/Messaging/MessageHandler/CampaignProcessorMessageHandler.php
index 35b4c841..ed6ca612 100644
--- a/src/Domain/Messaging/MessageHandler/CampaignProcessorMessageHandler.php
+++ b/src/Domain/Messaging/MessageHandler/CampaignProcessorMessageHandler.php
@@ -8,6 +8,7 @@
use DateTimeImmutable;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use Doctrine\ORM\EntityManagerInterface;
+use PhpList\Core\Domain\Configuration\Model\OutputFormat;
use PhpList\Core\Domain\Configuration\Service\UserPersonalizer;
use PhpList\Core\Domain\Messaging\Exception\MessageCacheMissingException;
use PhpList\Core\Domain\Messaging\Exception\MessageSizeLimitExceededException;
@@ -195,15 +196,20 @@ private function handleEmailSending(
MessagePrecacheDto $precachedContent,
): void {
$processed = $this->messagePreparator->processMessageLinks(
- $campaign->getId(),
- $precachedContent,
- $subscriber
+ campaignId: $campaign->getId(),
+ cachedMessageDto: $precachedContent,
+ subscriber: $subscriber
);
$processed->textContent = $this->userPersonalizer->personalize(
- $processed->textContent,
- $subscriber->getEmail(),
+ value: $processed->textContent,
+ email: $subscriber->getEmail(),
+ format:OutputFormat::Text,
+ );
+ $processed->footer = $this->userPersonalizer->personalize(
+ value: $processed->footer,
+ email: $subscriber->getEmail(),
+ format: OutputFormat::Text,
);
- $processed->footer = $this->userPersonalizer->personalize($processed->footer, $subscriber->getEmail());
try {
$email = $this->rateLimitedCampaignMailer->composeEmail($campaign, $subscriber, $processed);
diff --git a/src/Domain/Messaging/MessageHandler/PasswordResetMessageHandler.php b/src/Domain/Messaging/MessageHandler/PasswordResetMessageHandler.php
index 7d2a3096..ceedf35a 100644
--- a/src/Domain/Messaging/MessageHandler/PasswordResetMessageHandler.php
+++ b/src/Domain/Messaging/MessageHandler/PasswordResetMessageHandler.php
@@ -13,15 +13,11 @@
#[AsMessageHandler]
class PasswordResetMessageHandler
{
- private EmailService $emailService;
- private TranslatorInterface $translator;
- private string $passwordResetUrl;
-
- public function __construct(EmailService $emailService, TranslatorInterface $translator, string $passwordResetUrl)
- {
- $this->emailService = $emailService;
- $this->translator = $translator;
- $this->passwordResetUrl = $passwordResetUrl;
+ public function __construct(
+ private readonly EmailService $emailService,
+ private readonly TranslatorInterface $translator,
+ private readonly string $passwordResetUrl
+ ) {
}
/**
diff --git a/src/Domain/Messaging/MessageHandler/SubscriberConfirmationMessageHandler.php b/src/Domain/Messaging/MessageHandler/SubscriberConfirmationMessageHandler.php
index 69ec42cb..4a9e3c07 100644
--- a/src/Domain/Messaging/MessageHandler/SubscriberConfirmationMessageHandler.php
+++ b/src/Domain/Messaging/MessageHandler/SubscriberConfirmationMessageHandler.php
@@ -16,15 +16,11 @@
#[AsMessageHandler]
class SubscriberConfirmationMessageHandler
{
- private EmailService $emailService;
- private TranslatorInterface $translator;
- private string $confirmationUrl;
-
- public function __construct(EmailService $emailService, TranslatorInterface $translator, string $confirmationUrl)
- {
- $this->emailService = $emailService;
- $this->translator = $translator;
- $this->confirmationUrl = $confirmationUrl;
+ public function __construct(
+ private readonly EmailService $emailService,
+ private readonly TranslatorInterface $translator,
+ private readonly string $confirmationUrl
+ ) {
}
/**
diff --git a/src/Domain/Messaging/MessageHandler/SubscriptionConfirmationMessageHandler.php b/src/Domain/Messaging/MessageHandler/SubscriptionConfirmationMessageHandler.php
index 6ecb965b..73f2fda0 100644
--- a/src/Domain/Messaging/MessageHandler/SubscriptionConfirmationMessageHandler.php
+++ b/src/Domain/Messaging/MessageHandler/SubscriptionConfirmationMessageHandler.php
@@ -5,6 +5,7 @@
namespace PhpList\Core\Domain\Messaging\MessageHandler;
use PhpList\Core\Domain\Configuration\Model\ConfigOption;
+use PhpList\Core\Domain\Configuration\Model\OutputFormat;
use PhpList\Core\Domain\Configuration\Service\Provider\ConfigProvider;
use PhpList\Core\Domain\Configuration\Service\UserPersonalizer;
use PhpList\Core\Domain\Messaging\Message\SubscriptionConfirmationMessage;
@@ -20,24 +21,13 @@
#[AsMessageHandler]
class SubscriptionConfirmationMessageHandler
{
- private EmailService $emailService;
- private ConfigProvider $configProvider;
- private LoggerInterface $logger;
- private UserPersonalizer $userPersonalizer;
- private SubscriberListRepository $subscriberListRepository;
-
public function __construct(
- EmailService $emailService,
- ConfigProvider $configProvider,
- LoggerInterface $logger,
- UserPersonalizer $userPersonalizer,
- SubscriberListRepository $subscriberListRepository,
+ private readonly EmailService $emailService,
+ private readonly ConfigProvider $configProvider,
+ private readonly LoggerInterface $logger,
+ private readonly UserPersonalizer $userPersonalizer,
+ private readonly SubscriberListRepository $subscriberListRepository,
) {
- $this->emailService = $emailService;
- $this->configProvider = $configProvider;
- $this->logger = $logger;
- $this->userPersonalizer = $userPersonalizer;
- $this->subscriberListRepository = $subscriberListRepository;
}
/**
@@ -47,14 +37,19 @@ public function __invoke(SubscriptionConfirmationMessage $message): void
{
$subject = $this->configProvider->getValue(ConfigOption::SubscribeEmailSubject);
$textContent = $this->configProvider->getValue(ConfigOption::SubscribeMessage);
- $personalizedTextContent = $this->userPersonalizer->personalize($textContent, $message->getUniqueId());
$listOfLists = $this->getListNames($message->getListIds());
- $replacedTextContent = str_replace('[LISTS]', $listOfLists, $personalizedTextContent);
+ $replacedTextContent = str_replace('[LISTS]', $listOfLists, $textContent);
+
+ $personalizedTextContent = $this->userPersonalizer->personalize(
+ value: $replacedTextContent,
+ email: $message->getUniqueId(),
+ format: OutputFormat::Text,
+ );
$email = (new Email())
->to($message->getEmail())
->subject($subject)
- ->text($replacedTextContent);
+ ->text($personalizedTextContent);
$this->emailService->sendEmail($email);
@@ -63,14 +58,6 @@ public function __invoke(SubscriptionConfirmationMessage $message): void
private function getListNames(array $listIds): string
{
- $listNames = [];
- foreach ($listIds as $id) {
- $list = $this->subscriberListRepository->find($id);
- if ($list) {
- $listNames[] = $list->getName();
- }
- }
-
- return implode(', ', $listNames);
+ return implode(', ', $this->subscriberListRepository->getListNames($listIds));
}
}
diff --git a/src/Domain/Messaging/Service/MessagePrecacheService.php b/src/Domain/Messaging/Service/MessagePrecacheService.php
index 2a0f6b28..2628257e 100644
--- a/src/Domain/Messaging/Service/MessagePrecacheService.php
+++ b/src/Domain/Messaging/Service/MessagePrecacheService.php
@@ -195,7 +195,7 @@ private function applyRemoteContentIfPresent(MessagePrecacheDto $messagePrecache
} else {
$this->eventLogManager->log(
page: 'unknown page',
- entry: 'Error fetching URL: '.$loadedMessageData['sendurl'].' cannot proceed',
+ entry: 'Error fetching URL: ' . $loadedMessageData['sendurl'] . ' cannot proceed',
);
return false;
diff --git a/src/Domain/Subscription/Repository/SubscriberListRepository.php b/src/Domain/Subscription/Repository/SubscriberListRepository.php
index d8910751..988c23e3 100644
--- a/src/Domain/Subscription/Repository/SubscriberListRepository.php
+++ b/src/Domain/Subscription/Repository/SubscriberListRepository.php
@@ -9,6 +9,7 @@
use PhpList\Core\Domain\Common\Repository\Interfaces\PaginatableRepositoryInterface;
use PhpList\Core\Domain\Identity\Model\Administrator;
use PhpList\Core\Domain\Messaging\Model\Message;
+use PhpList\Core\Domain\Subscription\Model\Subscriber;
use PhpList\Core\Domain\Subscription\Model\SubscriberList;
/**
@@ -53,4 +54,41 @@ public function getAllActive(): array
->getQuery()
->getResult();
}
+
+ public function getListNames(array $listIds): array
+ {
+ if ($listIds === []) {
+ return [];
+ }
+
+ $lists = $this->createQueryBuilder('l')
+ ->select('l.name')
+ ->where('l.id IN (:ids)')
+ ->setParameter('ids', $listIds)
+ ->getQuery()
+ ->getScalarResult();
+
+ return array_column($lists, 'name');
+ }
+
+ /**
+ * Returns the names of lists the given subscriber is subscribed to.
+ * If $showPrivate is false, only active/public lists are included.
+ */
+ public function getActiveListNamesForSubscriber(Subscriber $subscriber, bool $showPrivate): array
+ {
+ $qb = $this->createQueryBuilder('l')
+ ->select('l.name')
+ ->innerJoin('l.subscriptions', 's')
+ ->where('IDENTITY(s.subscriber) = :subscriberId')
+ ->setParameter('subscriberId', $subscriber->getId());
+
+ if (!$showPrivate) {
+ $qb->andWhere('l.active = true');
+ }
+
+ $rows = $qb->getQuery()->getScalarResult();
+
+ return array_column($rows, 'name');
+ }
}
diff --git a/src/Domain/Subscription/Service/SubscriberCsvImporter.php b/src/Domain/Subscription/Service/SubscriberCsvImporter.php
index f962c9ab..f9db822a 100644
--- a/src/Domain/Subscription/Service/SubscriberCsvImporter.php
+++ b/src/Domain/Subscription/Service/SubscriberCsvImporter.php
@@ -34,36 +34,17 @@
*/
class SubscriberCsvImporter
{
- private SubscriberManager $subscriberManager;
- private SubscriberAttributeManager $attributeManager;
- private SubscriptionManager $subscriptionManager;
- private SubscriberRepository $subscriberRepository;
- private CsvToDtoImporter $csvToDtoImporter;
- private EntityManagerInterface $entityManager;
- private TranslatorInterface $translator;
- private MessageBusInterface $messageBus;
- private SubscriberHistoryManager $subscriberHistoryManager;
-
public function __construct(
- SubscriberManager $subscriberManager,
- SubscriberAttributeManager $attributeManager,
- SubscriptionManager $subscriptionManager,
- SubscriberRepository $subscriberRepository,
- CsvToDtoImporter $csvToDtoImporter,
- EntityManagerInterface $entityManager,
- TranslatorInterface $translator,
- MessageBusInterface $messageBus,
- SubscriberHistoryManager $subscriberHistoryManager,
+ private readonly SubscriberManager $subscriberManager,
+ private readonly SubscriberAttributeManager $attributeManager,
+ private readonly SubscriptionManager $subscriptionManager,
+ private readonly SubscriberRepository $subscriberRepository,
+ private readonly CsvToDtoImporter $csvToDtoImporter,
+ private readonly EntityManagerInterface $entityManager,
+ private readonly TranslatorInterface $translator,
+ private readonly MessageBusInterface $messageBus,
+ private readonly SubscriberHistoryManager $subscriberHistoryManager,
) {
- $this->subscriberManager = $subscriberManager;
- $this->attributeManager = $attributeManager;
- $this->subscriptionManager = $subscriptionManager;
- $this->subscriberRepository = $subscriberRepository;
- $this->csvToDtoImporter = $csvToDtoImporter;
- $this->entityManager = $entityManager;
- $this->translator = $translator;
- $this->messageBus = $messageBus;
- $this->subscriberHistoryManager = $subscriberHistoryManager;
}
/**