Skip to content

Commit 492e1d0

Browse files
committed
SystemMailConstructor
1 parent 077bc63 commit 492e1d0

File tree

8 files changed

+167
-21
lines changed

8 files changed

+167
-21
lines changed

config/parameters.yml.dist

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ parameters:
2525
env(DATABASE_PREFIX): 'phplist_'
2626
list_table_prefix: '%%env(LIST_TABLE_PREFIX)%%'
2727
env(LIST_TABLE_PREFIX): 'listattr_'
28+
app.dev_version: '%%env(APP_DEV_VERSION)%%'
29+
env(APP_DEV_VERSION): 0
30+
app.dev_email: '%%env(APP_DEV_EMAIL)%%'
31+
env(APP_DEV_EMAIL): '[email protected]'
32+
app.powered_by_phplist: '%%env(APP_POWERED_BY_PHPLIST)%%'
33+
env(APP_POWERED_BY_PHPLIST): 0
2834

2935
# Email configuration
3036
app.mailer_from: '%%env(MAILER_FROM)%%'
@@ -93,6 +99,8 @@ parameters:
9399
env(DEFAULT_MESSAGEAGE): '691200'
94100
messaging.use_manual_text_part: '%%env(USE_MANUAL_TEXT_PART)%%'
95101
env(USE_MANUAL_TEXT_PART): 0
102+
messaging.blacklist_grace_time: '%%env(MESSAGING_BLACKLIST_GRACE_TIME)%%'
103+
env(MESSAGING_BLACKLIST_GRACE_TIME):
96104

97105
phplist.upload_images_dir: '%%env(PHPLIST_UPLOADIMAGES_DIR)%%'
98106
env(PHPLIST_UPLOADIMAGES_DIR): 'images'
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
use PhpList\Core\Domain\Configuration\Model\ConfigOption;
88
use PhpList\Core\Domain\Configuration\Service\Provider\ConfigProvider;
99

10-
class HtmlToText
10+
class Html2Text
1111
{
1212
private const WORD_WRAP = 70;
1313

src/Domain/Configuration/Model/ConfigOption.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ enum ConfigOption: string
1616
case Domain = 'domain';
1717
case Website = 'website';
1818
case MessageFromAddress = 'message_from_address';
19+
case MessageFromName = 'message_from_name';
20+
case MessageReplyToAddress = 'message_replyto_address';
21+
case SystemMessageTemplate = 'systemmessagetemplate';
1922
case AlwaysAddGoogleTracking = 'always_add_googletracking';
2023
case AdminAddress = 'admin_address';
2124
case DefaultMessageTemplate = 'defaultmessagetemplate';
@@ -26,4 +29,6 @@ enum ConfigOption: string
2629
case WordWrap = 'wordwrap';
2730
case RemoteUrlAppend = 'remoteurl_append';
2831
case OrganisationLogo = 'organisation_logo';
32+
case PoweredByImage = 'PoweredByImage';
33+
case PoweredByText = 'PoweredByText';
2934
}

src/Domain/Configuration/Service/Provider/DefaultConfigProvider.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,20 @@ private function init(): void
557557
'allowempty' => 0,
558558
'category' => 'subscription-ui',
559559
],
560+
'PoweredByImage' => [
561+
'value' => '<p class="poweredby" style="text-align:center"><a href="https://www.phplist.com/poweredby?utm_source=pl&amp;utm_medium=poweredlocalimg&amp;utm_campaign=phpList" title="visit the phpList website"><img src="images/power-phplist.png" title="powered by phpList version , &copy; phpList ltd" alt="powered by phpList , &copy; phpList ltd" border="0"/></a></p>',
562+
'description' => 'logo/image ndicates that emails are sent by phpList',
563+
'type' => 'string',
564+
'allowempty' => false,
565+
'category' => 'general',
566+
],
567+
'PoweredByText' => [
568+
'value' => '<div style="clear: both; font-family: arial, verdana, sans-serif; font-size: 8px; font-variant: small-caps; font-weight: normal; padding: 2px; padding-left:10px;padding-top:20px;">powered by <a href="https://www.phplist.com/poweredby?utm_source=download&amp;utm_medium=poweredtxt&amp;utm_campaign=phpList" target="_blank" title="powered by phpList version, &copy; phpList ltd">phpList</a></div>',
569+
'description' => 'text indicates that emails are sent by phpList',
570+
'type' => 'string',
571+
'allowempty' => false,
572+
'category' => 'general',
573+
],
560574
];
561575
}
562576

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,20 @@ public function delete(TemplateImage $templateImage): void
9898
$this->templateImageRepository->remove($templateImage);
9999
}
100100

101+
public function parseLogoPlaceholders($content)
102+
{
103+
//# replace Logo placeholders
104+
preg_match_all('/\[LOGO\:?(\d+)?\]/', $content, $logoInstances);
105+
foreach ($logoInstances[0] as $index => $logoInstance) {
106+
$size = sprintf('%d', $logoInstances[1][$index]);
107+
$logoSize = !empty($size) ? $size : '500';
108+
$this->createCachedLogoImage((int)$logoSize);
109+
$content = str_replace($logoInstance, 'ORGANISATIONLOGO'.$logoSize.'.png', $content);
110+
}
111+
112+
return $content;
113+
}
114+
101115
public function createCachedLogoImage(int $size): void
102116
{
103117
$logoImageId = $this->configProvider->getValue(ConfigOption::OrganisationLogo);

src/Domain/Messaging/Service/MessagePrecacheService.php

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

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

7-
use PhpList\Core\Domain\Common\HtmlToText;
7+
use PhpList\Core\Domain\Common\Html2Text;
88
use PhpList\Core\Domain\Common\RemotePageFetcher;
99
use PhpList\Core\Domain\Common\TextParser;
1010
use PhpList\Core\Domain\Configuration\Model\ConfigOption;
@@ -23,7 +23,7 @@ class MessagePrecacheService
2323
public function __construct(
2424
private readonly CacheInterface $cache,
2525
private readonly ConfigProvider $configProvider,
26-
private readonly HtmlToText $htmlToText,
26+
private readonly Html2Text $html2Text,
2727
private readonly TextParser $textParser,
2828
private readonly TemplateRepository $templateRepository,
2929
private readonly RemotePageFetcher $remotePageFetcher,
@@ -89,7 +89,7 @@ public function precacheMessage(Message $campaign, $loadedMessageData, ?bool $fo
8989
$messagePrecacheDto->footer = $forwardContent ? stripslashes($loadedMessageData['forwardfooter']) : $loadedMessageData['footer'];
9090

9191
if (strip_tags($messagePrecacheDto->footer ) !== $messagePrecacheDto->footer) {
92-
$messagePrecacheDto->textFooter = ($this->htmlToText)($messagePrecacheDto->footer);
92+
$messagePrecacheDto->textFooter = ($this->html2Text)($messagePrecacheDto->footer);
9393
$messagePrecacheDto->htmlFooter = $messagePrecacheDto->footer;
9494
} else {
9595
$messagePrecacheDto->textFooter = $messagePrecacheDto->footer;
@@ -176,9 +176,9 @@ public function precacheMessage(Message $campaign, $loadedMessageData, ?bool $fo
176176
);
177177
}
178178

179-
$messagePrecacheDto->content = $this->parseLogoPlaceholders($messagePrecacheDto->content);
180-
$messagePrecacheDto->template = $this->parseLogoPlaceholders($messagePrecacheDto->template);
181-
$messagePrecacheDto->htmlFooter = $this->parseLogoPlaceholders($messagePrecacheDto->htmlFooter);
179+
$messagePrecacheDto->content = $this->templateImageManager->parseLogoPlaceholders($messagePrecacheDto->content);
180+
$messagePrecacheDto->template = $this->templateImageManager->parseLogoPlaceholders($messagePrecacheDto->template);
181+
$messagePrecacheDto->htmlFooter = $this->templateImageManager->parseLogoPlaceholders($messagePrecacheDto->htmlFooter);
182182

183183
// $replacements = $this->buildBasicReplacements($campaign, $subject);
184184
// $html = $this->applyReplacements($html, $replacements);
@@ -221,18 +221,4 @@ private function applyReplacements(?string $input, array $replacements): ?string
221221

222222
return str_ireplace(array_keys($replacements), array_values($replacements), $input);
223223
}
224-
225-
private function parseLogoPlaceholders($content)
226-
{
227-
//# replace Logo placeholders
228-
preg_match_all('/\[LOGO\:?(\d+)?\]/', $content, $logoInstances);
229-
foreach ($logoInstances[0] as $index => $logoInstance) {
230-
$size = sprintf('%d', $logoInstances[1][$index]);
231-
$logoSize = !empty($size) ? $size : '500';
232-
$this->templateImageManager->createCachedLogoImage((int)$logoSize);
233-
$content = str_replace($logoInstance, 'ORGANISATIONLOGO'.$logoSize.'.png', $content);
234-
}
235-
236-
return $content;
237-
}
238224
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpList\Core\Domain\Messaging\Service;
6+
7+
use PhpList\Core\Domain\Common\Html2Text;
8+
use PhpList\Core\Domain\Configuration\Model\ConfigOption;
9+
use PhpList\Core\Domain\Configuration\Service\Provider\ConfigProvider;
10+
use PhpList\Core\Domain\Messaging\Repository\TemplateRepository;
11+
use PhpList\Core\Domain\Messaging\Service\Manager\TemplateImageManager;
12+
13+
class SystemMailConstructor
14+
{
15+
public function __construct(
16+
private readonly Html2Text $html2Text,
17+
private readonly ConfigProvider $configProvider,
18+
private readonly TemplateRepository $templateRepository,
19+
private readonly TemplateImageManager $templateImageManager,
20+
private readonly bool $poweredByPhplist = false,
21+
) {
22+
}
23+
24+
public function __invoke($message, $subject = ''): array
25+
{
26+
$hasHTML = strip_tags($message) !== $message;
27+
28+
if ($hasHTML) {
29+
$message = stripslashes($message);
30+
$textMessage = ($this->html2Text)($message);
31+
$htmlMessage = $message;
32+
} else {
33+
$textMessage = $message;
34+
$htmlMessage = $message;
35+
// $htmlMessage = str_replace("\n\n","\n",$htmlMessage);
36+
$htmlMessage = nl2br($htmlMessage);
37+
//# make links clickable:
38+
$htmlMessage = preg_replace('~https?://[^\s<]+~i', '<a href="$0">$0</a>', $htmlMessage);
39+
}
40+
//# add li-s around the lists
41+
if (preg_match('/<ul>\s+(\*.*)<\/ul>/imsxU', $htmlMessage, $listsmatch)) {
42+
$lists = $listsmatch[1];
43+
$listsHTML = '';
44+
preg_match_all('/\*([^\*]+)/', $lists, $matches);
45+
for ($i = 0; $i < count($matches[0]); ++$i) {
46+
$listsHTML .= '<li>'.$matches[1][$i].'</li>';
47+
}
48+
$htmlMessage = str_replace($listsmatch[0], '<ul>'.$listsHTML.'</ul>', $htmlMessage);
49+
}
50+
51+
$htmlContent = $htmlMessage;
52+
$textContent = $textMessage;
53+
$templateId = $this->configProvider->getValue(ConfigOption::SystemMessageTemplate);
54+
if ($templateId) {
55+
$template = $this->templateRepository->findOneById((int)$templateId);
56+
if ($template) {
57+
$htmlTemplate = stripslashes($template->getContent());
58+
$textTemplate = stripslashes($template->getText());
59+
$htmlContent = str_replace('[CONTENT]', $htmlMessage, $htmlTemplate);
60+
$htmlContent = str_replace('[SUBJECT]', $subject, $htmlContent);
61+
$htmlContent = str_replace('[FOOTER]', '', $htmlContent);
62+
if (!$this->poweredByPhplist) {
63+
$phpListPowered = preg_replace(
64+
'/src=".*power-phplist.png"/',
65+
'src="powerphplist.png"',
66+
$this->configProvider->getValue(ConfigOption::PoweredByImage),
67+
);
68+
} else {
69+
$phpListPowered = $this->configProvider->getValue(ConfigOption::PoweredByText);
70+
}
71+
if (strpos($htmlContent, '[SIGNATURE]')) {
72+
$htmlContent = str_replace('[SIGNATURE]', $phpListPowered, $htmlContent);
73+
} elseif (strpos($htmlContent, '</body>')) {
74+
$htmlContent = str_replace('</body>', $phpListPowered.'</body>', $htmlContent);
75+
} else {
76+
$htmlContent .= $phpListPowered;
77+
}
78+
$htmlContent = $this->templateImageManager->parseLogoPlaceholders($htmlContent);
79+
$textContent = str_replace('[CONTENT]', $textMessage, $textTemplate);
80+
$textContent = str_replace('[SUBJECT]', $subject, $textContent);
81+
$textContent = str_replace('[FOOTER]', '', $textContent);
82+
$phpListPowered = trim(($this->html2Text)($this->configProvider->getValue(ConfigOption::PoweredByText)));
83+
if (strpos($textContent, '[SIGNATURE]')) {
84+
$textContent = str_replace('[SIGNATURE]', $phpListPowered, $textContent);
85+
} else {
86+
$textContent .= "\n\n" . $phpListPowered;
87+
}
88+
}
89+
}
90+
91+
return [$htmlContent, $textContent];
92+
}
93+
}

src/Domain/Subscription/Repository/UserBlacklistRepository.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,23 @@
44

55
namespace PhpList\Core\Domain\Subscription\Repository;
66

7+
use DateTimeImmutable;
8+
use Doctrine\ORM\EntityManagerInterface;
9+
use Doctrine\ORM\Mapping\ClassMetadata;
710
use PhpList\Core\Domain\Common\Repository\AbstractRepository;
811
use PhpList\Core\Domain\Subscription\Model\UserBlacklist;
912
use PhpList\Core\Domain\Subscription\Model\UserBlacklistData;
1013

1114
class UserBlacklistRepository extends AbstractRepository
1215
{
16+
public function __construct(
17+
EntityManagerInterface $entityManager,
18+
ClassMetadata $class,
19+
private readonly int $blacklistGraceTime,
20+
) {
21+
parent::__construct($entityManager, $class);
22+
}
23+
1324
public function findBlacklistInfoByEmail(string $email): ?UserBlacklist
1425
{
1526
$queryBuilder = $this->getEntityManager()->createQueryBuilder();
@@ -30,4 +41,19 @@ public function findOneByEmail(string $email): ?UserBlacklist
3041
'email' => $email,
3142
]);
3243
}
44+
45+
public function isEmailBlacklisted(string $email, ?bool $immediate = true): bool
46+
{
47+
// allow 5 minutes to send the last message acknowledging unsubscription
48+
$grace = $immediate ? 0 : ((($gt = $this->blacklistGraceTime) >= 1 && $gt <= 15) ? $gt : 5);
49+
$cutoff = (new DateTimeImmutable())->modify('-' . $grace .' minutes');
50+
51+
return $this->createQueryBuilder('ub')
52+
->where('ub.email = :email')
53+
->andWhere('ub.added < :cutoff')
54+
->setParameter('email', $email)
55+
->setParameter('cutoff', $cutoff)
56+
->getQuery()
57+
->getOneOrNullResult() !== null;
58+
}
3359
}

0 commit comments

Comments
 (0)