Skip to content
Open

Dev #378

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
d0ba222
Feat: message sending-forwarding (#374)
TatevikGr Feb 9, 2026
3bd50ae
AttachmentDownloadService (#379)
TatevikGr Feb 11, 2026
1770948
Feat: user message open tracking (#380)
TatevikGr Feb 13, 2026
3a1ab4e
Remove 'to' from MessagePrecacheDto (#381)
TatevikGr Feb 16, 2026
83ad337
Upgrade guzzle
tatevikg1 Feb 27, 2026
beab554
Fix: manager configuration
tatevikg1 Feb 27, 2026
ca374e4
Subscriber getFilteredAfterId innerJoin => lefftJoin
tatevikg1 Mar 3, 2026
f87a38e
Add: SubscriberFilter options
tatevikg1 Mar 4, 2026
97ad3ba
Fix: subscriber prop name in query builder condition
tatevikg1 Mar 9, 2026
589d85f
PaginatedResult
tatevikg1 Mar 10, 2026
32a7dc9
remove sorting
tatevikg1 Mar 10, 2026
fa0af38
Add getSubscriberDetails method
tatevikg1 Mar 13, 2026
981c20b
Add: front-end url
tatevikg1 Mar 17, 2026
41e443b
Fix: ExtraData
tatevikg1 Mar 17, 2026
aaa05e8
Add:getSummaryStatistics
tatevikg1 Mar 19, 2026
9e77ca5
Add: getCampaignPerformance, getRecentCampaigns
tatevikg1 Mar 19, 2026
fa22d99
Ref: FilterRequestInterface
tatevikg1 Mar 23, 2026
d6f81ce
Ref: PaginatedFilter base class
tatevikg1 Mar 23, 2026
1576422
Fix: types
tatevikg1 Mar 23, 2026
5973219
Add: updateSubscriberList
tatevikg1 Mar 23, 2026
a92362e
Add: fields to export
tatevikg1 Mar 25, 2026
9af573e
Fix: subscriber attribute resolution
tatevikg1 Mar 25, 2026
d99ae7e
Fix: subscribers getFilteredAfterId
tatevikg1 Mar 26, 2026
6e983d9
createSubscriptions with autoConfirm
tatevikg1 Mar 30, 2026
29e567c
uniqueByMessageId
tatevikg1 Mar 30, 2026
68aed9b
todo
tatevikg1 Mar 30, 2026
fc991f7
Fix: metadata processed
tatevikg1 Mar 30, 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
13 changes: 8 additions & 5 deletions .coderabbit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,21 @@ reviews:
suggested_labels: false
high_level_summary_in_walkthrough: false
poem: false
finishing_touches:
docstrings:
enabled: false
path_instructions:
- path: "src/Domain/**"
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
18 changes: 12 additions & 6 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,11 +80,17 @@
"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",
"guzzlehttp/guzzle": "^7.4.5"
},
"require-dev": {
"phpunit/phpunit": "^9.5",
"guzzlehttp/guzzle": "^6.3.0",
"squizlabs/php_codesniffer": "^3.2.0",
"phpstan/phpstan": "^1.10",
"nette/caching": "^3.0.0",
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",
"FOS\\RestBundle\\FOSRestBundle"
],
"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>
76 changes: 72 additions & 4 deletions config/parameters.yml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,28 @@ 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_base_url: '%%env(REST_API_BASE_URL)%%'
env(REST_API_BASE_URL): 'http://api.phplist.local/api/v2'
app.frontend_base_url: '%%env(FRONT_END_BASE_URL)%%'
env(FRONT_END_BASE_URL): 'http://frontend.phplist.local'

# 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/'
env(CONFIRMATION_URL): 'http://api.phplist.local/api/v2/subscriber/confirm/'
app.subscription_confirmation_url: '%%env(SUBSCRIPTION_CONFIRMATION_URL)%%'
env(SUBSCRIPTION_CONFIRMATION_URL): 'https://example.com/subscription/confirm/'
env(SUBSCRIPTION_CONFIRMATION_URL): 'http://api.phplist.local/api/v2/subscription/confirm/'
app.password_reset_url: '%%env(PASSWORD_RESET_URL)%%'
env(PASSWORD_RESET_URL): 'https://example.com/reset/'

Expand Down Expand Up @@ -71,8 +83,10 @@ 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_host: 'graylog.phplist.local'
graylog_port: 12201

app.phplist_isp_conf_path: '%%env(APP_PHPLIST_ISP_CONF_PATH)%%'
Expand All @@ -89,3 +103,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'
7 changes: 3 additions & 4 deletions config/services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,9 @@ services:
calls:
- [ set, [ 'Cache.SerializerPath', '%kernel.cache_dir%/htmlpurifier' ] ]
- [ set, [ 'HTML.ForbiddenElements', [ 'script', 'style' ] ] ]
- [ set, [ 'CSS.Disable', true ] ]
- [ set, [ 'URI.DisableJavaScript', true ] ]
- [ set, [ 'URI.DisableDataURI', true ] ]
- [ set, [ 'HTML.Doctype', 'HTML5' ] ]
- [ set, [ 'CSS.AllowedProperties', [] ] ]
- [ set, [ 'URI.AllowedSchemes', { http: true, https: true, mailto: true } ] ]
- [ set, [ 'HTML.Doctype', 'XHTML 1.0 Transitional' ] ]
- [ set, [ 'HTML.Allowed', 'p,br,b,strong,i,em,u,a[href|title],ul,ol,li,blockquote,img[src|alt|title],span,div'] ]

HTMLPurifier:
Expand Down
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%'
Loading
Loading