Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
a72d2e9
UserPersonalizer in CampaignProcessorMessageHandler
tatevikg1 Dec 11, 2025
d40dedd
HtmlToText
tatevikg1 Dec 11, 2025
613a196
MessageDataLoader
tatevikg1 Dec 11, 2025
759d8e0
TextParser
tatevikg1 Dec 11, 2025
d94f825
RemotePageFetcher
tatevikg1 Dec 13, 2025
3b9267f
Use repo methods
tatevikg1 Dec 14, 2025
5fc8637
Use MessagePrecacheDto
tatevikg1 Dec 14, 2025
69884a8
Refactor
tatevikg1 Dec 14, 2025
077bc63
Todo
tatevikg1 Dec 15, 2025
492e1d0
SystemMailConstructor
tatevikg1 Dec 15, 2025
109b07a
EmailBuilder
tatevikg1 Dec 16, 2025
65c0030
InjectedByHeaderSubscriber
tatevikg1 Dec 16, 2025
7e9bab2
TemplateImageManager
tatevikg1 Dec 17, 2025
3dcb90a
ExternalImageCacher
tatevikg1 Dec 17, 2025
0c5b4f4
TemplateImageEmbedder
tatevikg1 Dec 18, 2025
25ef84a
Mailer
tatevikg1 Dec 21, 2025
166a66c
RemotePageFetcherTest
tatevikg1 Dec 22, 2025
e7f9eca
TextParserTest
tatevikg1 Dec 22, 2025
cec0eb1
MessageDataLoaderTest
tatevikg1 Dec 22, 2025
2b0c256
MessageDataLoaderTest
tatevikg1 Dec 22, 2025
1643a29
Test fix
tatevikg1 Dec 22, 2025
04e84a1
Fix: phpmd
tatevikg1 Dec 23, 2025
4a9e895
Fix: phpcs
tatevikg1 Dec 24, 2025
b6e2ee2
After review 0
tatevikg1 Dec 25, 2025
ca6dc94
After review 1
tatevikg1 Dec 25, 2025
9fea466
Add tests
tatevikg1 Dec 27, 2025
2323119
EmailBuilderTest
tatevikg1 Dec 29, 2025
823e006
update coderabbit.yaml
tatevikg1 Dec 29, 2025
a140c2b
Add tests
tatevikg1 Dec 29, 2025
7f21c58
MailSizeChecker
tatevikg1 Dec 29, 2025
8342b4a
Feat/email building with attachments (#375)
TatevikGr Jan 22, 2026
39712ed
Feat: email forwarding (#377)
TatevikGr Feb 3, 2026
88e25f5
Cutoff from forward_email_period config
tatevikg1 Feb 3, 2026
6f81e7b
ForwardingResult
tatevikg1 Feb 3, 2026
2adf65c
Remove MessageFormat consts
tatevikg1 Feb 3, 2026
7adf1f6
Testing bundle
tatevikg1 Feb 3, 2026
0a9b9a4
After review 3
tatevikg1 Feb 4, 2026
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
10 changes: 5 additions & 5 deletions .coderabbit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ reviews:
instructions: |
You are reviewing PHP domain-layer code. Enforce domain purity, with a relaxed policy for DynamicListAttr:

- ❌ Do not allow persistence or transaction side effects here for *normal* domain models.
- Flag ANY usage of Doctrine persistence APIs on regular domain entities, especially:
- ❌ Do not allow, flag ANY DB write / finalization:
- `$entityManager->flush(...)`, `$this->entityManager->flush(...)`
- `$em->persist(...)`, `$em->remove(...)`
- `$em->beginTransaction()`, `$em->commit()`, `$em->rollback()`
- `$em->beginTransaction()`, `$em->commit()`, `$em->rollback()`, `$em->transactional(...)`
- `$em->getConnection()->executeStatement(...)` for DML/DDL (INSERT/UPDATE/DELETE/ALTER/...)
- ✅ Accessing Doctrine *metadata*, *schema manager*, or *read-only schema info* is acceptable
as long as it does not modify state or perform writes.
as long as it does not modify state or perform writes. Accessing Doctrine *persistence APIs*
persist, remove, etc.) is acceptable, allow scheduling changes in the UnitOfWork (no DB writes)

- ✅ **Relaxed rule for DynamicListAttr-related code**:
- DynamicListAttr is a special case dealing with dynamic tables/attrs.
Expand Down
16 changes: 11 additions & 5 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "phplist/core",
"description": "The core module of phpList, the world's most popular open source newsletter manager",
"type": "phplist-module",
"type": "symfony-bundle",
"keywords": [
"phplist",
"email",
Expand Down Expand Up @@ -46,6 +46,7 @@
},
"require": {
"php": "^8.1",
"symfony/framework-bundle": "^6.4",
"symfony/dependency-injection": "^6.4",
"symfony/config": "^6.4",
"symfony/yaml": "^6.4",
Expand Down Expand Up @@ -79,7 +80,13 @@
"ext-imap": "*",
"tatevikgr/rss-feed": "dev-main",
"ext-pdo": "*",
"ezyang/htmlpurifier": "^4.19"
"ezyang/htmlpurifier": "^4.19",
"ext-libxml": "*",
"ext-gd": "*",
"ext-curl": "*",
"ext-fileinfo": "*",
"setasign/fpdf": "^1.8",
"phpdocumentor/reflection-docblock": "^5.2"
},
"require-dev": {
"phpunit/phpunit": "^9.5",
Expand All @@ -92,7 +99,6 @@
"symfony/test-pack": "^1.1",
"symfony/process": "^6.4",
"composer/composer": "^2.7",
"symfony/framework-bundle": "^6.4",
"symfony/http-kernel": "^6.4",
"symfony/http-foundation": "^6.4",
"symfony/routing": "^6.4",
Expand Down Expand Up @@ -152,8 +158,8 @@
"Doctrine\\Bundle\\DoctrineBundle\\DoctrineBundle",
"Doctrine\\Bundle\\MigrationsBundle\\DoctrineMigrationsBundle",
"PhpList\\Core\\EmptyStartPageBundle\\EmptyStartPageBundle",
"FOS\\RestBundle\\FOSRestBundle",
"TatevikGr\\RssFeedBundle\\RssFeedBundle"
"PhpList\\Core\\EmptyStartPageBundle\\PhpListCoreBundle",
Comment on lines 160 to +161
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify bundle class definitions exist
rg -n --type=php 'class\s+EmptyStartPageBundle\b'
rg -n --type=php 'class\s+PhpListCoreBundle\b'

Repository: phpList/core

Length of output: 217


🏁 Script executed:

#!/bin/bash
# Check namespace declarations in both files
echo "=== EmptyStartPageBundle namespace ==="
head -20 src/EmptyStartPageBundle/EmptyStartPageBundle.php

echo -e "\n=== PhpListCoreBundle namespace ==="
head -20 src/PhpListCoreBundle.php

Repository: phpList/core

Length of output: 577


Fix incorrect bundle namespace in composer.json line 161.

The PhpListCoreBundle class is registered with the wrong fully-qualified name. It's actually in the PhpList\Core namespace, not PhpList\Core\EmptyStartPageBundle.

Change line 161 from:

"PhpList\\Core\\EmptyStartPageBundle\\PhpListCoreBundle",

To:

"PhpList\\Core\\PhpListCoreBundle",

Incorrect bundle registration will cause Symfony to fail loading the bundle during kernel boot.

🤖 Prompt for AI Agents
In `@composer.json` around lines 160 - 161, Update the incorrect bundle FQCN in
composer.json: replace the entry
"PhpList\\Core\\EmptyStartPageBundle\\PhpListCoreBundle" with the correct
fully-qualified class name "PhpList\\Core\\PhpListCoreBundle" so Symfony can
locate the PhpListCoreBundle class (fix the wrong namespace reference to
PhpList\Core\EmptyStartPageBundle and use PhpList\Core\PhpListCoreBundle
instead).

"FOS\\RestBundle\\FOSRestBundle"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if FOSRestBundle or friendsofsymfony/rest-bundle is present in composer.json
echo "=== Searching composer.json ===" 
cat composer.json | grep -i "friendsofsymfony\|fosrest"

echo ""
echo "=== Full composer.json (for context) ==="
cat composer.json

Repository: phpList/core

Length of output: 5875


Add FOSRestBundle package to composer.json require section.

The bundle is registered in the Symfony configuration but the Composer package is missing. Add friendsofsymfony/rest-bundle with a version compatible for Symfony 6.4 to the require section, otherwise Symfony will fail during bootstrap.

🤖 Prompt for AI Agents
In `@composer.json` at line 161, composer.json is missing the Composer package for
the registered bundle FOS\RestBundle\FOSRestBundle; add the package
"friendsofsymfony/rest-bundle" to the require section with a Symfony
6.4‑compatible constraint (e.g. a 3.x/4.x range compatible with Symfony 6.4) so
the bundle registration resolves at bootstrap; update composer.json's require
entry accordingly and run composer update to install the package.

],
"routes": {
"homepage": {
Expand Down
8 changes: 4 additions & 4 deletions config/PHPMD/rules.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<exclude-pattern>*/Migrations/*</exclude-pattern>

<!-- rules from the "clean code" rule set -->
<rule ref="rulesets/cleancode.xml/BooleanArgumentFlag"/>
<!-- <rule ref="rulesets/cleancode.xml/BooleanArgumentFlag"/>-->
<rule ref="rulesets/codesize.xml/CyclomaticComplexity"/>
<rule ref="rulesets/codesize.xml/NPathComplexity"/>
<rule ref="rulesets/codesize.xml/ExcessiveMethodLength"/>
Expand All @@ -33,20 +33,20 @@
<rule ref="rulesets/design.xml/DepthOfInheritance"/>
<rule ref="rulesets/design.xml/CouplingBetweenObjects">
<properties>
<property name="maximum" value="15"/>
<property name="maximum" value="17"/>
</properties>
</rule>
<rule ref="rulesets/design.xml/DevelopmentCodeFragment"/>

<!-- rules from the "naming" rule set -->
<rule ref="rulesets/naming.xml/ShortVariable">
<properties>
<property name="exceptions" value="id,ip,cc,io"/>
<property name="exceptions" value="id,ip,cc,io,to"/>
</properties>
</rule>
<rule ref="rulesets/naming.xml/LongVariable">
<properties>
<property name="maximum" value="25"/>
<property name="maximum" value="30"/>
</properties>
</rule>
<rule ref="rulesets/naming.xml/ShortMethodName"/>
Expand Down
6 changes: 5 additions & 1 deletion config/PhpCodeSniffer/ruleset.xml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@
<rule ref="PEAR.WhiteSpace.ScopeClosingBrace"/>
<rule ref="Squiz.WhiteSpace.CastSpacing"/>
<rule ref="Squiz.WhiteSpace.LogicalOperatorSpacing"/>
<rule ref="Squiz.WhiteSpace.OperatorSpacing"/>
<rule ref="Squiz.WhiteSpace.OperatorSpacing">
<properties>
<property name="ignoreNewlines" value="true"/>
</properties>
</rule>
<rule ref="Squiz.WhiteSpace.SemicolonSpacing"/>
</ruleset>
68 changes: 67 additions & 1 deletion config/parameters.yml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,22 @@ parameters:
env(DATABASE_PREFIX): 'phplist_'
list_table_prefix: '%%env(LIST_TABLE_PREFIX)%%'
env(LIST_TABLE_PREFIX): 'listattr_'
app.dev_version: '%%env(APP_DEV_VERSION)%%'
env(APP_DEV_VERSION): '0'
app.dev_email: '%%env(APP_DEV_EMAIL)%%'
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'
app.rest_api_domain: '%%env(REST_API_DOMAIN)%%'
env(REST_API_DOMAIN): 'example.com'

# Email configuration
app.mailer_from: '%%env(MAILER_FROM)%%'
env(MAILER_FROM): 'noreply@phplist.com'
app.mailer_dsn: '%%env(MAILER_DSN)%%'
env(MAILER_DSN): 'null://null'
env(MAILER_DSN): 'null://null' # set local_domain on transport
app.confirmation_url: '%%env(CONFIRMATION_URL)%%'
env(CONFIRMATION_URL): 'https://example.com/subscriber/confirm/'
app.subscription_confirmation_url: '%%env(SUBSCRIPTION_CONFIRMATION_URL)%%'
Expand Down Expand Up @@ -71,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 All @@ -89,3 +101,57 @@ parameters:
env(MESSAGING_MAX_PROCESS_TIME): '600'
messaging.max_mail_size: '%%env(MAX_MAILSIZE)%%'
env(MAX_MAILSIZE): '209715200'
messaging.default_message_age: '%%env(DEFAULT_MESSAGEAGE)%%'
env(DEFAULT_MESSAGEAGE): '691200'
messaging.use_manual_text_part: '%%env(USE_MANUAL_TEXT_PART)%%'
env(USE_MANUAL_TEXT_PART): '0'
messaging.blacklist_grace_time: '%%env(MESSAGING_BLACKLIST_GRACE_TIME)%%'
env(MESSAGING_BLACKLIST_GRACE_TIME): '600'
messaging.google_sender_id: '%%env(GOOGLE_SENDERID)%%'
env(GOOGLE_SENDERID): ''
messaging.use_amazon_ses: '%%env(USE_AMAZONSES)%%'
env(USE_AMAZONSES): '0'
messaging.use_precedence_header: '%%env(USE_PRECEDENCE_HEADER)%%'
env(USE_PRECEDENCE_HEADER): '0'
messaging.embed_external_images: '%%env(EMBEDEXTERNALIMAGES)%%'
env(EMBEDEXTERNALIMAGES): '0'
messaging.embed_uploaded_images: '%%env(EMBEDUPLOADIMAGES)%%'
env(EMBEDUPLOADIMAGES): '0'
messaging.external_image_max_age: '%%env(EXTERNALIMAGE_MAXAGE)%%'
env(EXTERNALIMAGE_MAXAGE): '0'
messaging.external_image_timeout: '%%env(EXTERNALIMAGE_TIMEOUT)%%'
env(EXTERNALIMAGE_TIMEOUT): '30'
messaging.external_image_max_size: '%%env(EXTERNALIMAGE_MAXSIZE)%%'
env(EXTERNALIMAGE_MAXSIZE): '204800'
messaging.forward_alternative_content: '%%env(FORWARD_ALTERNATIVE_CONTENT)%%'
env(FORWARD_ALTERNATIVE_CONTENT): '0'
messaging.email_text_credits: '%%env(EMAILTEXTCREDITS)%%'
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'
phplist.editor_images_dir: '%%env(FCKIMAGES_DIR)%%'
env(FCKIMAGES_DIR): 'uploadimages'
phplist.public_schema: '%%env(PUBLIC_SCHEMA)%%'
env(PUBLIC_SCHEMA): 'https'
phplist.attachment_download_url: '%%env(PHPLIST_ATTACHMENT_DOWNLOAD_URL)%%'
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'
42 changes: 27 additions & 15 deletions config/services/builders.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,34 @@ services:
autoconfigure: true
public: false

PhpList\Core\Domain\Messaging\Service\Builder\MessageBuilder:
autowire: true
autoconfigure: true
PhpList\Core\Domain\:
resource: '../../src/Domain/*/Service/Builder/*'

PhpList\Core\Domain\Messaging\Service\Builder\MessageFormatBuilder:
autowire: true
autoconfigure: true
# Concrete mail constructors
PhpList\Core\Domain\Messaging\Service\Constructor\SystemMailContentBuilder: ~
PhpList\Core\Domain\Messaging\Service\Constructor\CampaignMailContentBuilder: ~

PhpList\Core\Domain\Messaging\Service\Builder\MessageScheduleBuilder:
autowire: true
autoconfigure: true
# Two EmailBuilder services with different constructors injected
PhpList\Core\Domain\Messaging\Service\Builder\SystemEmailBuilder:
arguments:
$googleSenderId: '%messaging.google_sender_id%'
$useAmazonSes: '%messaging.use_amazon_ses%'
$usePrecedenceHeader: '%messaging.use_precedence_header%'
$devVersion: '%app.dev_version%'
$devEmail: '%app.dev_email%'

PhpList\Core\Domain\Messaging\Service\Builder\MessageContentBuilder:
autowire: true
autoconfigure: true
PhpList\Core\Domain\Messaging\Service\Builder\EmailBuilder:
arguments:
$googleSenderId: '%messaging.google_sender_id%'
$useAmazonSes: '%messaging.use_amazon_ses%'
$usePrecedenceHeader: '%messaging.use_precedence_header%'
$devVersion: '%app.dev_version%'
$devEmail: '%app.dev_email%'

PhpList\Core\Domain\Messaging\Service\Builder\MessageOptionsBuilder:
autowire: true
autoconfigure: true
PhpList\Core\Domain\Messaging\Service\Builder\ForwardEmailBuilder:
arguments:
$googleSenderId: '%messaging.google_sender_id%'
$useAmazonSes: '%messaging.use_amazon_ses%'
$usePrecedenceHeader: '%messaging.use_precedence_header%'
$devVersion: '%app.dev_version%'
$devEmail: '%app.dev_email%'
102 changes: 4 additions & 98 deletions config/services/managers.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,53 +4,11 @@ services:
autoconfigure: true
public: false

PhpList\Core\Domain\Configuration\Service\Manager\ConfigManager:
autowire: true
autoconfigure: true

PhpList\Core\Domain\Configuration\Service\Manager\EventLogManager:
autowire: true
autoconfigure: true
PhpList\Core\Domain\:
resource: '../../src/Domain/*/Service/Manager/*'
exclude: '../../src/Domain/*/Service/Manager/Builder/*'

PhpList\Core\Domain\Identity\Service\SessionManager:
autowire: true
autoconfigure: true

PhpList\Core\Domain\Identity\Service\AdministratorManager:
autowire: true
autoconfigure: true

PhpList\Core\Domain\Identity\Service\AdminAttributeDefinitionManager:
autowire: true
autoconfigure: true

PhpList\Core\Domain\Identity\Service\AdminAttributeManager:
autowire: true
autoconfigure: true

PhpList\Core\Domain\Identity\Service\PasswordManager:
autowire: true
autoconfigure: true

PhpList\Core\Domain\Subscription\Service\Manager\SubscriberManager:
autowire: true
autoconfigure: true

PhpList\Core\Domain\Subscription\Service\Manager\SubscriberListManager:
autowire: true
autoconfigure: true

PhpList\Core\Domain\Subscription\Service\Manager\SubscriptionManager:
autowire: true
autoconfigure: true

PhpList\Core\Domain\Subscription\Service\Manager\AttributeDefinitionManager:
autowire: true
autoconfigure: true

PhpList\Core\Domain\Subscription\Service\Manager\DynamicListAttrManager:
autowire: true
autoconfigure: true
PhpList\Core\Bounce\Service\Manager\BounceManager: ~

Doctrine\DBAL\Schema\AbstractSchemaManager:
factory: ['@doctrine.dbal.default_connection', 'createSchemaManager']
Expand All @@ -62,55 +20,3 @@ services:
arguments:
$dbPrefix: '%database_prefix%'
$dynamicListTablePrefix: '%list_table_prefix%'

PhpList\Core\Domain\Subscription\Service\Manager\SubscriberHistoryManager:
autowire: true
autoconfigure: true

PhpList\Core\Domain\Subscription\Service\Manager\SubscriberAttributeManager:
autowire: true
autoconfigure: true

PhpList\Core\Domain\Subscription\Service\Manager\SubscriberBlacklistManager:
autowire: true
autoconfigure: true

PhpList\Core\Domain\Subscription\Service\Manager\SubscribePageManager:
autowire: true
autoconfigure: true

PhpList\Core\Domain\Messaging\Service\Manager\MessageManager:
autowire: true
autoconfigure: true

PhpList\Core\Domain\Messaging\Service\Manager\TemplateManager:
autowire: true
autoconfigure: true

PhpList\Core\Domain\Messaging\Service\Manager\TemplateImageManager:
autowire: true
autoconfigure: true

PhpList\Core\Domain\Messaging\Service\Manager\BounceRegexManager:
autowire: true
autoconfigure: true

PhpList\Core\Bounce\Service\Manager\BounceManager:
autowire: true
autoconfigure: true

PhpList\Core\Domain\Messaging\Service\Manager\ListMessageManager:
autowire: true
autoconfigure: true

PhpList\Core\Domain\Messaging\Service\Manager\BounceRuleManager:
autowire: true
autoconfigure: true

PhpList\Core\Domain\Messaging\Service\Manager\SendProcessManager:
autowire: true
autoconfigure: true

PhpList\Core\Domain\Messaging\Service\Manager\MessageDataManager:
autowire: true
autoconfigure: true
Loading
Loading