Skip to content
Open
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: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@
"ext-gd": "*",
"ext-curl": "*",
"ext-fileinfo": "*",
"setasign/fpdf": "^1.8"
"setasign/fpdf": "^1.8",
"phpdocumentor/reflection-docblock": "^5.2"
},
"require-dev": {
"phpunit/phpunit": "^9.5",
Expand Down
19 changes: 18 additions & 1 deletion config/parameters.yml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ parameters:
app.preference_page_show_private_lists: '%%env(PREFERENCEPAGE_SHOW_PRIVATE_LISTS)%%'
env(PREFERENCEPAGE_SHOW_PRIVATE_LISTS): '0'
app.rest_api_domain: '%%env(REST_API_DOMAIN)%%'
env(REST_API_DOMAIN): 'https://example.com/api/v2'
env(REST_API_DOMAIN): 'example.com'

# Email configuration
app.mailer_from: '%%env(MAILER_FROM)%%'
Expand Down Expand Up @@ -81,6 +81,8 @@ parameters:
# A secret key that's used to generate certain security-related tokens
secret: '%%env(PHPLIST_SECRET)%%'
env(PHPLIST_SECRET): %1$s
phplist.verify_ssl: '%%env(VERIFY_SSL)%%'
env(VERIFY_SSL): '1'

graylog_host: 'graylog.example.com'
graylog_port: 12201
Expand Down Expand Up @@ -127,6 +129,19 @@ parameters:
env(EMAILTEXTCREDITS): '0'
messaging.always_add_user_track: '%%env(ALWAYS_ADD_USERTRACK)%%'
env(ALWAYS_ADD_USERTRACK): '1'
messaging.send_list_admin_copy: '%%env(SEND_LISTADMIN_COPY)%%'
env(SEND_LISTADMIN_COPY): '0'

phplist.forward_email_period: '%%env(FORWARD_EMAIL_PERIOD)%%'
env(FORWARD_EMAIL_PERIOD): '1 minute'
phplist.forward_email_count: '%%env(FORWARD_EMAIL_COUNT)%%'
env(FORWARD_EMAIL_COUNT): '1'
phplist.forward_personal_note_size: '%%env(FORWARD_PERSONAL_NOTE_SIZE)%%'
env(FORWARD_PERSONAL_NOTE_SIZE): '0'
phplist.forward_friend_count_attribute: '%%env(FORWARD_FRIEND_COUNT_ATTRIBUTE)%%'
env(FORWARD_FRIEND_COUNT_ATTRIBUTE): ''
phplist.keep_forwarded_attributes: '%%env(KEEPFORWARDERATTRIBUTES)%%'
env(KEEPFORWARDERATTRIBUTES): '0'

phplist.upload_images_dir: '%%env(PHPLIST_UPLOADIMAGES_DIR)%%'
env(PHPLIST_UPLOADIMAGES_DIR): 'images'
Expand All @@ -138,3 +153,5 @@ parameters:
env(PHPLIST_ATTACHMENT_DOWNLOAD_URL): 'https://example.com/download/'
phplist.attachment_repository_path: '%%env(PHPLIST_ATTACHMENT_REPOSITORY_PATH)%%'
env(PHPLIST_ATTACHMENT_REPOSITORY_PATH): '/tmp'
phplist.max_avatar_size: '%%env(MAX_AVATAR_SIZE)%%'
env(MAX_AVATAR_SIZE): '100000'
8 changes: 2 additions & 6 deletions config/services/builders.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,16 @@ services:
PhpList\Core\Domain\Messaging\Service\Constructor\CampaignMailContentBuilder: ~

# Two EmailBuilder services with different constructors injected
Core.EmailBuilder.system:
class: PhpList\Core\Domain\Messaging\Service\Builder\SystemEmailBuilder
PhpList\Core\Domain\Messaging\Service\Builder\SystemEmailBuilder:
arguments:
$mailConstructor: '@PhpList\Core\Domain\Messaging\Service\Constructor\SystemMailContentBuilder'
$googleSenderId: '%messaging.google_sender_id%'
$useAmazonSes: '%messaging.use_amazon_ses%'
$usePrecedenceHeader: '%messaging.use_precedence_header%'
$devVersion: '%app.dev_version%'
$devEmail: '%app.dev_email%'

Core.EmailBuilder.campaign:
class: PhpList\Core\Domain\Messaging\Service\Builder\EmailBuilder
PhpList\Core\Domain\Messaging\Service\Builder\EmailBuilder:
arguments:
$mailConstructor: '@PhpList\Core\Domain\Messaging\Service\Constructor\CampaignMailContentBuilder'
$googleSenderId: '%messaging.google_sender_id%'
$useAmazonSes: '%messaging.use_amazon_ses%'
$usePrecedenceHeader: '%messaging.use_precedence_header%'
Expand Down
2 changes: 0 additions & 2 deletions config/services/messenger.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,3 @@ services:
autowire: true
autoconfigure: true
arguments:
$campaignEmailBuilder: '@Core.EmailBuilder.campaign'
$systemEmailBuilder: '@Core.EmailBuilder.system'
26 changes: 21 additions & 5 deletions resources/translations/messages.en.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -774,7 +774,7 @@ Thank you.</target>
<source>This message contains attachments that can be viewed with a webbrowser</source>
<target>__This message contains attachments that can be viewed with a webbrowser</target>
</trans-unit>
<trans-unit id="QLlIQQh" resname="Insufficient memory to add attachment to campaign %campaignId% %totalSize% - %memLimit%">
<trans-unit id="28lDkjt" resname="Insufficient memory to add attachment to campaign %campaignId% %totalSize% - %memLimit%">
<source>Insufficient memory to add attachment to campaign %campaignId% %totalSize% - %memLimit%</source>
<target>__Insufficient memory to add attachment to campaign %campaignId% %totalSize% - %memLimit%</target>
</trans-unit>
Expand All @@ -794,10 +794,6 @@ Thank you.</target>
<source>failed to open attachment (%remoteFile%) to add to campaign %campaignId%</source>
<target>__failed to open attachment (%remoteFile%) to add to campaign %campaignId%</target>
</trans-unit>
<trans-unit id="28lDkjt" resname="Insufficient memory to add attachment to campaign %campaignId% %totalSize% - %memLimit%">
<source>Insufficient memory to add attachment to campaign %campaignId% %totalSize% - %memLimit%</source>
<target>__Insufficient memory to add attachment to campaign %campaignId% %totalSize% - %memLimit%</target>
</trans-unit>
<trans-unit id="DmqU7IE" resname="Attachment %remoteFile% does not exist">
<source>Attachment %remoteFile% does not exist</source>
<target>__Attachment %remoteFile% does not exist</target>
Expand All @@ -810,6 +806,26 @@ Thank you.</target>
<source>Location</source>
<target>__Location</target>
</trans-unit>
<trans-unit id="1eHD7_P" resname="Fwd">
<source>Fwd</source>
<target>__Fwd</target>
</trans-unit>
<trans-unit id="xBvzfdD" resname="(test)">
<source>(test)</source>
<target>__(test)</target>
</trans-unit>
<trans-unit id="gLiV1kM" resname="Message Forwarded">
<source>Message Forwarded</source>
<target>__Message Forwarded</target>
</trans-unit>
<trans-unit id="XbRAoq3" resname="%subscriber% tried forwarding message %campaignId% to %email% but failed">
<source>%subscriber% tried forwarding message %campaignId% to %email% but failed</source>
<target>__%subscriber% tried forwarding message %campaignId% to %email% but failed</target>
</trans-unit>
<trans-unit id="u7htSNe" resname="%subscriber% has forwarded message %campaignId% to %email%">
<source>%subscriber% has forwarded message %campaignId% to %email%</source>
<target>__%subscriber% has forwarded message %campaignId% to %email%</target>
</trans-unit>
</body>
</file>
</xliff>
10 changes: 10 additions & 0 deletions src/Core/Version.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

namespace PhpList\Core\Core;

class Version
{
public const VERSION = '1.0.0';
}
1 change: 1 addition & 0 deletions src/Domain/Analytics/Service/LinkTrackService.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public function isExtractAndSaveLinksApplicable(): bool
*/
public function extractAndSaveLinks(MessagePrecacheDto $content, int $userId, ?int $messageId = null): array
{
// todo: in case of forwarded message, we need to use 'forwarded' instead of user id
if (!$this->isExtractAndSaveLinksApplicable()) {
return [];
}
Expand Down
5 changes: 3 additions & 2 deletions src/Domain/Common/ExternalImageService.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use PhpList\Core\Domain\Configuration\Model\ConfigOption;
use PhpList\Core\Domain\Configuration\Service\Provider\ConfigProvider;
use Psr\Log\LoggerInterface;
use Throwable;
use Symfony\Component\DependencyInjection\Attribute\Autowire;

class ExternalImageService
{
Expand All @@ -19,6 +19,7 @@ public function __construct(
private readonly string $tempDir,
private readonly int $externalImageMaxAge,
private readonly int $externalImageMaxSize,
#[Autowire('%phplist.verify_ssl%')] private readonly bool $verifySsl = true,
private readonly ?int $externalImageTimeout = 30,
) {
$this->externalCacheDir = $this->tempDir . '/external_cache';
Expand Down Expand Up @@ -125,7 +126,7 @@ private function downloadUsingCurl(string $filename): ?string
curl_setopt($cURLHandle, CURLOPT_TIMEOUT, $this->externalImageTimeout);
curl_setopt($cURLHandle, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($cURLHandle, CURLOPT_MAXREDIRS, 10);
curl_setopt($cURLHandle, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($cURLHandle, CURLOPT_SSL_VERIFYPEER, $this->verifySsl);
curl_setopt($cURLHandle, CURLOPT_FAILONERROR, true);

$cacheFileContent = curl_exec($cURLHandle);
Expand Down
4 changes: 4 additions & 0 deletions src/Domain/Configuration/Model/ConfigOption.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ enum ConfigOption: string
case SystemMessageTemplate = 'systemmessagetemplate';
case AlwaysAddGoogleTracking = 'always_add_googletracking';
case AdminAddress = 'admin_address';
case AdminAddresses = 'admin_addresses';
case DefaultMessageTemplate = 'defaultmessagetemplate';
case MessageFooter = 'messagefooter';
case ForwardFooter = 'forwardfooter';
Expand All @@ -41,4 +42,7 @@ enum ConfigOption: string
case HtmlEmailStyle = 'html_email_style';
case AlwaysSendTextDomains = 'alwayssendtextto';
case ReportAddress = 'report_address';
case SendAdminCopies = 'send_admin_copies';
case DontSaveUserPassword = 'dontsave_userpassword';
case AutoCreateAttributes = 'autocreate_attributes';
}
4 changes: 2 additions & 2 deletions src/Domain/Configuration/Model/Dto/PlaceholderContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public function __construct(
public readonly OutputFormat $format,
public readonly ?MessagePrecacheDto $messagePrecacheDto = null,
public readonly string $locale = 'en',
private readonly ?string $forwardedBy = null,
private readonly ?Subscriber $forwardedBy = null,
private readonly ?int $messageId = null,
) {
}
Expand All @@ -30,7 +30,7 @@ public function isText(): bool
return $this->format === OutputFormat::Text;
}

public function forwardedBy(): ?string
public function forwardedBy(): ?Subscriber
{
return $this->forwardedBy;
}
Expand Down
37 changes: 25 additions & 12 deletions src/Domain/Configuration/Service/MessagePlaceholderProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,25 +30,29 @@ public function __construct(
/** @var iterable<SupportingPlaceholderResolverInterface> */
private readonly iterable $supportingResolvers,
#[Autowire('%messaging.always_add_user_track%')] private readonly bool $alwaysAddUserTrack,
#[Autowire('%phplist.keep_forwarded_attributes%')] private readonly bool $keepForwardedAttributes,
) {
}

public function process(
string $value,
Subscriber $user,
Subscriber $receiver,
OutputFormat $format,
MessagePrecacheDto $messagePrecacheDto,
?int $campaignId = null,
?string $forwardedBy = null,
?Subscriber $forwardedBy = null,
): string {
$value = $this->ensureStandardPlaceholders($value, $format);

$resolver = new PlaceholderResolver();
$resolver->register('EMAIL', fn(PlaceholderContext $ctx) => $ctx->user->getEmail());
$resolver->register('FORWARDEDBY', fn(PlaceholderContext $ctx) => $ctx->forwardedBy());
$resolver->register('FORWARDEDBY', fn(PlaceholderContext $ctx) => $ctx->forwardedBy()?->getEmail() ?? '');
$resolver->register('MESSAGEID', fn(PlaceholderContext $ctx) => $ctx->messageId());
$resolver->register('FORWARDFORM', fn(PlaceholderContext $ctx) => '');
$resolver->register('USERID', fn(PlaceholderContext $ctx) => $ctx->user->getUniqueId());
$resolver->register(
name: 'USERID',
resolver: fn(PlaceholderContext $ctx) => $ctx->forwardedBy() ? 'forwarded' : $ctx->user->getUniqueId()
);
$resolver->register(
name: 'WEBSITE',
resolver: fn(PlaceholderContext $ctx) => $this->config->getValue(ConfigOption::Website) ?? ''
Expand All @@ -74,18 +78,12 @@ public function process(
$resolver->registerSupporting($supportingResolver);
}

$userAttributes = $this->attributesRepository->getForSubscriber($user);
foreach ($userAttributes as $userAttribute) {
$resolver->register(
name: strtoupper($userAttribute->getAttributeDefinition()->getName()),
resolver: fn(PlaceholderContext $ctx) => $this->attributeValueResolver->resolve($userAttribute)
);
}
$this->registerAttributeResolvers($resolver, $receiver, $forwardedBy);

return $resolver->resolve(
value: $value,
context: new PlaceholderContext(
user: $user,
user: $receiver,
format: $format,
messagePrecacheDto: $messagePrecacheDto,
forwardedBy: $forwardedBy,
Expand Down Expand Up @@ -123,4 +121,19 @@ private function appendContent(string $message, string $append): string

return $message;
}

private function registerAttributeResolvers(
PlaceholderResolver $resolver,
Subscriber $receiver,
?Subscriber $forwardedBy
): void {
$userForAttributes = ($forwardedBy && $this->keepForwardedAttributes) ? $forwardedBy : $receiver;
$userAttributes = $this->attributesRepository->getForSubscriber($userForAttributes);
foreach ($userAttributes as $userAttribute) {
$resolver->register(
name: strtoupper($userAttribute->getAttributeDefinition()->getName()),
resolver: fn(PlaceholderContext $ctx) => $this->attributeValueResolver->resolve($userAttribute)
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,12 @@ public function __invoke(PlaceholderContext $ctx): string
return '';
}
$sep = !str_contains($url, '?') ? '?' : '&';
$uid = $ctx->forwardedBy() ? 'forwarded' : $ctx->getUser()->getUniqueId();

if ($ctx->isHtml()) {
return sprintf('%s%suid=%s', $url, htmlspecialchars($sep), $ctx->getUser()->getUniqueId());
return sprintf('%s%suid=%s', $url, htmlspecialchars($sep), $uid);
}

return sprintf('%s%suid=%s', $url, $sep, $ctx->getUser()->getUniqueId());
return sprintf('%s%suid=%s', $url, $sep, $uid);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ public function name(): string

public function __invoke(PlaceholderContext $ctx): string
{
if ($ctx->forwardedBy() === null && $ctx->messagePrecacheDto) {
return $ctx->isText() ? $ctx->messagePrecacheDto->textFooter : $ctx->messagePrecacheDto->htmlFooter;
}

//0013076: different content when forwarding 'to a friend'
if ($this->forwardAlternativeContent && $ctx->messagePrecacheDto) {
return stripslashes($ctx->messagePrecacheDto->footer);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,18 @@ public function __invoke(PlaceholderContext $ctx): string
return '';
}
$sep = !str_contains($url, '?') ? '?' : '&';
$uid = $ctx->forwardedBy() ? 'forwarded' : $ctx->getUser()->getUniqueId();

if ($ctx->isHtml()) {
return sprintf(
'%s%suid=%s&amp;mid=%d',
htmlspecialchars($url, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'),
htmlspecialchars($sep),
$ctx->getUser()->getUniqueId(),
$uid,
$ctx->messageId(),
);
}

return sprintf('%s%suid=%s&mid=%d', $url, $sep, $ctx->getUser()->getUniqueId(), $ctx->messageId());
return sprintf('%s%suid=%s&mid=%d', $url, $sep, $uid, $ctx->messageId());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public function __invoke(PlaceholderContext $ctx): string
return '';
}
$sep = !str_contains($url, '?') ? '?' : '&';
$uid = $ctx->forwardedBy() ? 'forwarded' : $ctx->getUser()->getUniqueId();

if ($ctx->isHtml()) {
$label = $this->translator->trans('This link');
Expand All @@ -37,14 +38,14 @@ public function __invoke(PlaceholderContext $ctx): string
. htmlspecialchars($url, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')
. htmlspecialchars($sep)
. 'uid='
. $ctx->getUser()->getUniqueId()
. $uid
. '&amp;mid='
. $ctx->messageId()
. '">'
. htmlspecialchars($label, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')
. '</a> ';
}

return sprintf('%s%suid=%s&mid=%d ', $url, $sep, $ctx->getUser()->getUniqueId(), $ctx->messageId());
return sprintf('%s%suid=%s&mid=%d ', $url, $sep, $uid, $ctx->messageId());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ public function __invoke(PlaceholderContext $ctx): string
if (empty($base)) {
return '';
}
$url = $this->urlBuilder->withUid($base, $ctx->getUser()->getUniqueId());
$uid = $ctx->forwardedBy() ? 'forwarded' : $ctx->getUser()->getUniqueId();
$url = $this->urlBuilder->withUid($base, $uid);

if ($ctx->isHtml()) {
return '';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,17 @@ public function __invoke(PlaceholderContext $ctx): string
return '';
}
$sep = !str_contains($url, '?') ? '?' : '&';
$uid = $ctx->forwardedBy() ? 'forwarded' : $ctx->getUser()->getUniqueId();

if ($ctx->isHtml()) {
return sprintf(
'%s%suid=%s',
htmlspecialchars($url, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'),
htmlspecialchars($sep),
$ctx->getUser()->getUniqueId(),
$uid,
);
}

return sprintf('%s%suid=%s', $url, $sep, $ctx->getUser()->getUniqueId());
return sprintf('%s%suid=%s', $url, $sep, $uid);
}
}
Loading
Loading