Skip to content

Conversation

@TatevikGr
Copy link
Contributor

@TatevikGr TatevikGr commented Jan 30, 2026

Summary by CodeRabbit

  • New Features

    • Message forwarding: send campaigns to friends with optional personal note, per-user limits, admin notifications, and forwarding stats; forwarded messages labeled "Fwd".
    • Admin-copy emails: configurable toggle to send admin copies and choose recipients.
  • Configuration

    • Toggleable SSL verification for external downloads and configurable max avatar upload size.
  • Internationalization

    • Added forwarding-related translation strings.

Thanks for contributing to phpList!

@coderabbitai
Copy link

coderabbitai bot commented Jan 30, 2026

Warning

Rate limit exceeded

@TatevikGr has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 23 minutes and 12 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📝 Walkthrough

Walkthrough

Adds message-forwarding: new forwarding services, guards, stats, builders, DTOs, repositories, exceptions, admin notifications, placeholder/URL adjustments for forwarded context, configuration flags, DI/service wiring changes, and corresponding tests and translations.

Changes

Cohort / File(s) Summary
Configuration
config/parameters.yml.dist
Added phplist.* forwarding, admin copy, SSL verify, avatar size params; changed REST_API_DOMAIN default.
Service wiring
config/services/builders.yml, config/services/messenger.yml
Renamed builder service IDs to FQCNs; removed $mailConstructor injections; removed builder args from messenger handler.
Core email builders
src/Domain/Messaging/Service/Builder/BaseEmailBuilder.php, src/Domain/Messaging/Service/Builder/EmailBuilder.php, src/Domain/Messaging/Service/Builder/SystemEmailBuilder.php
Refactored base/header handling, made BaseEmailBuilder abstract, renamed buildPhplistEmail→buildCampaignEmail, swapped to concrete content builders, propagated $forwarded flag, added header helpers.
Forward builders & stamps
src/Domain/Messaging/Service/Builder/ForwardEmailBuilder.php, src/Domain/Messaging/Service/Builder/HttpReceivedStampBuilder.php
Added ForwardEmailBuilder for building forwarded emails and HttpReceivedStampBuilder to generate Received stamps from HTTP request metadata.
Content builders & interface
src/Domain/Messaging/Service/Constructor/CampaignMailContentBuilder.php, src/Domain/Messaging/Service/Constructor/SystemMailContentBuilder.php, src/Domain/Messaging/Service/Constructor/MailContentBuilderInterface.php
Removed MailContentBuilderInterface; CampaignMailContentBuilder now accepts forwarded context and personal note; SystemMailContentBuilder signature narrowed.
Forwarding orchestration
src/Domain/Messaging/Service/MessageForwardService.php, src/Domain/Messaging/Service/ForwardingGuard.php, src/Domain/Messaging/Service/ForwardingStatsService.php, src/Domain/Messaging/Service/ForwardContentService.php, src/Domain/Messaging/Service/ForwardDeliveryService.php
Added services for forward orchestration, permission/limit checks, per-request forwarding stats, content retrieval, and delivery/marking of forwards.
Persistence & DTOs
src/Domain/Messaging/Model/Dto/MessageForwardDto.php, src/Domain/Messaging/Service/Manager/UserMessageForwardManager.php, src/Domain/Messaging/Repository/UserMessageForwardRepository.php
Added MessageForwardDto, manager to persist forward records, and repository methods to count/find forwards.
Admin notifications
src/Domain/Identity/Service/AdminCopyEmailSender.php, src/Domain/Identity/Service/AdminNotifier.php
Added admin-copy sender and notifier to email admins on forward success/failure and to resolve recipient lists.
Placeholder/context changes
src/Domain/Configuration/Model/Dto/PlaceholderContext.php, src/Domain/Configuration/Service/MessagePlaceholderProcessor.php
PlaceholderContext.forwardedBy now holds Subscriber; MessagePlaceholderProcessor accepts Subscriber forwardedBy, adds keepForwardedAttributes flag and changes USERID/FORWARDEDBY resolution and attribute registration.
Placeholder resolvers
src/Domain/Configuration/Service/Placeholder/*ValueResolver.php (multiple)
Updated Confirmation/Forward/Jumpoff/Preferences/Unsubscribe/UserTrack/Footer resolvers to use 'forwarded' UID when context indicates forwarding and to handle forwarded footers.
Attachment & link handling
src/Domain/Messaging/Service/AttachmentAdder.php, src/Domain/Analytics/Service/LinkTrackService.php
AttachmentAdder signature gains $forwarded and uses 'forwarded' UID in view URLs when forwarded; LinkTrackService adds a comment about forwarded identifiers.
Subscriber attributes & manager
src/Domain/Subscription/Model/SubscriberAttributeDefinition.php, src/Domain/Subscription/Repository/SubscriberAttributeValueRepository.php, src/Domain/Subscription/Service/Manager/SubscriberAttributeManager.php, src/Domain/Subscription/Exception/AttributeNotAllowedException.php
Added toArray(), attribute lookup by name, createOrUpdateByName, constructor promotion refactor, and AttributeNotAllowedException.
Exceptions & config options
src/Domain/Messaging/Exception/*.php, src/Domain/Configuration/Model/ConfigOption.php, src/Domain/Configuration/Service/Provider/ConfigProvider.php
Added ForwardLimitExceededException and MessageNotReceivedException; added ConfigOption cases and boolean parsing changes in ConfigProvider (SendAdminCopies handling).
External/infra & misc
src/Domain/Common/ExternalImageService.php, src/Core/Version.php, composer.json
Made SSL verification configurable via DI; added Version class; added phpdocumentor/reflection-docblock dependency.
Translations
resources/translations/messages.en.xlf
Removed a memory-related unit; added forward-related translation units (Fwd, Message Forwarded, forward success/failure, test marker).
Message handler & tests
src/Domain/Messaging/MessageHandler/CampaignProcessorMessageHandler.php, tests/...
Updated handler to call buildCampaignEmail; updated many tests for renamed builders, forwardedBy type change, DI changes, and new forwarding behavior.

Sequence Diagram(s)

sequenceDiagram
    actor User as Forwarding Subscriber
    participant MessageForwardService
    participant ForwardingGuard
    participant MessagePrecacheService
    participant ForwardContentService
    participant ForwardEmailBuilder
    participant ForwardDeliveryService
    participant AdminNotifier
    participant DB as Database

    User->>MessageForwardService: forward(MessageForwardDto)
    MessageForwardService->>ForwardingGuard: assertCanForward(uid, campaign, cutoff)
    ForwardingGuard->>DB: lookup subscriber / received message / count forwards
    DB-->>ForwardingGuard: subscriber/message/counts
    ForwardingGuard-->>MessageForwardService: allowed subscriber

    loop per recipient
        MessageForwardService->>ForwardingGuard: hasAlreadyBeenSent(email, campaign)?
        ForwardingGuard->>DB: check forward records
        DB-->>ForwardingGuard: exists?
        alt not sent
            MessageForwardService->>MessagePrecacheService: precache message
            MessagePrecacheService-->>MessageForwardService: precached data
            MessageForwardService->>ForwardContentService: getContents(campaign, subscriber, email, DTO)
            ForwardContentService->>ForwardEmailBuilder: buildForwardEmail(...)
            ForwardEmailBuilder-->>ForwardContentService: Email + OutputFormat
            ForwardContentService-->>MessageForwardService: Email
            MessageForwardService->>ForwardDeliveryService: send(Email)
            ForwardDeliveryService->>DB: persist/send record
            ForwardDeliveryService-->>MessageForwardService: ack
            MessageForwardService->>AdminNotifier: notifyForwardSucceeded(...)
        else already sent
            MessageForwardService-->>MessageForwardService: skip
        end
    end

    MessageForwardService->>DB: update forwarding subscriber friend count
    DB-->>MessageForwardService: ack
    MessageForwardService-->>User: complete
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.47% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Feat/email forwarding' clearly summarizes the main feature being implemented—email forwarding functionality—aligning well with the extensive changeset across configuration, services, and domain models.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/email-forwarding

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

coderabbitai[bot]

This comment was marked as resolved.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@composer.json`:
- Around line 87-88: The composer.json lists "phpdocumentor/reflection-docblock"
at version ^5.2 but no runtime usage was found; either remove that dependency
from the "require" block or move it to "require-dev" if it is only used for
tooling (e.g., PHPStan or docs), and if you intend to keep it update the version
constraint to the current stable "6.0.1"; modify the composer.json entry
accordingly and run composer update to ensure lockfile consistency.
🧹 Nitpick comments (1)
src/Domain/Common/ExternalImageService.php (1)

150-166: Minor inconsistency: file_get_contents fallback doesn't respect $verifySsl.

The cURL path now respects the verifySsl config, but the file_get_contents fallback still uses PHP's default SSL behavior. If someone sets verify_ssl=false expecting to bypass verification, this fallback path would still verify (or vice versa depending on PHP config).

Not a blocker, but worth considering for consistency if both paths should behave the same way.

🔧 Optional: Add SSL context to file_get_contents
 private function downloadUsingFileGetContent(string $filename): string
 {
     $remoteURLContext = stream_context_create([
         'http' => [
             'method' => 'GET',
             'timeout' => $this->externalImageTimeout,
             'max_redirects' => '10',
-        ]
+        ],
+        'ssl' => [
+            'verify_peer' => $this->verifySsl,
+            'verify_peer_name' => $this->verifySsl,
+        ],
     ]);

     $cacheFileContent = file_get_contents($filename, false, $remoteURLContext);

coderabbitai[bot]

This comment was marked as resolved.

@TatevikGr TatevikGr force-pushed the feat/email-forwarding branch from 215071b to a736ce2 Compare February 2, 2026 07:39
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@src/Domain/Messaging/Service/Constructor/CampaignMailContentBuilder.php`:
- Around line 32-33: The injected config key name is misleading: change the
Autowire attribute on the CampaignMailContentBuilder constructor parameter
forwardPersonalNote from '%phplist.forward_personal_note_size%' to a
boolean-named key like '%phplist.forward_personal_note_enabled%'; update any
config definitions, fixtures or tests that set forward_personal_note_size to use
forward_personal_note_enabled, and keep the parameter type (?bool
$forwardPersonalNote = false) and its usage in the builder (where it gates
inclusion of the forwarded note) unchanged so the name reflects boolean
semantics.

In `@src/Domain/Messaging/Service/ForwardingStatsService.php`:
- Around line 40-60: Both incrementFriendsCount and updateFriendsCount must
guard against null subscriber IDs to avoid cache collisions for transient
entities: in incrementFriendsCount and updateFriendsCount, after calling
Subscriber::getId() store it in $subscriberId and immediately return if
$subscriberId === null; this prevents comparing null to
$this->friendCountSubscriberId and stops reusing $this->friendCount for
different transient subscribers, preserving correct behavior of the
$this->friendCount cache and related logic.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@src/Domain/Configuration/Service/Placeholder/FooterValueResolver.php`:
- Around line 26-28: The early return when $ctx->forwardedBy() === null accesses
$ctx->messagePrecacheDto->textFooter / ->htmlFooter without a null check; update
the condition to also ensure $ctx->messagePrecacheDto is truthy (e.g., change
the if to check both $ctx->forwardedBy() === null && $ctx->messagePrecacheDto)
so the ternary that uses $ctx->messagePrecacheDto->textFooter / ->htmlFooter is
only executed when that DTO exists (look for this check pattern used later in
the file and apply the same guard in FooterValueResolver around the existing
isText() ternary).

In `@src/Domain/Identity/Service/AdminCopyEmailSender.php`:
- Around line 57-62: The mapping over $lists in AdminCopyEmailSender (inside the
$this->sendListAdminCopy branch) calls $list->getOwner()->getEmail() without
guarding for a null owner; change the logic to first filter or skip
SubscriberList instances where $list->getOwner() is null and then map only those
with a non-null owner to their emails, ensuring the resulting $emails is an
array (possibly empty) of strings; update the mapping around $lists and
references to getOwner()/getEmail() accordingly so no direct method call occurs
on a null owner.
🧹 Nitpick comments (3)
src/Domain/Messaging/Service/ForwardDeliveryService.php (1)

39-57: Consider using constants or an enum for status values.

The magic strings 'sent' and 'failed' are used here and likely referenced elsewhere. Extracting them to constants (or a PHP 8.1+ enum) would improve type safety and make refactoring easier down the road.

💡 Example using class constants
 class ForwardDeliveryService
 {
+    public const STATUS_SENT = 'sent';
+    public const STATUS_FAILED = 'failed';
+
     public function __construct(
         // ...
     }

     public function markSent(Message $campaign, Subscriber $subscriber, string $friendEmail): void
     {
         $this->messageForwardManager->create(
             subscriber: $subscriber,
             campaign: $campaign,
             friendEmail: $friendEmail,
-            status: 'sent'
+            status: self::STATUS_SENT
         );
     }

     public function markFailed(Message $campaign, Subscriber $subscriber, string $friendEmail): void
     {
         $this->messageForwardManager->create(
             subscriber: $subscriber,
             campaign: $campaign,
             friendEmail: $friendEmail,
-            status: 'failed'
+            status: self::STATUS_FAILED
         );
     }
 }
src/Domain/Messaging/Service/ForwardingGuard.php (1)

47-52: Consider extracting status string to a constant.

The 'sent' status string appears here and likely in other places. A shared constant (or enum) would prevent typos and make the status values discoverable.

Optional improvement
+    private const STATUS_SENT = 'sent';
+
     public function hasAlreadyBeenSent(string $friendEmail, Message $campaign): bool
     {
         $existing = $this->forwardRepository->findByEmailAndMessage($friendEmail, $campaign->getId());

-        return $existing !== null && $existing->getStatus() === 'sent';
+        return $existing !== null && $existing->getStatus() === self::STATUS_SENT;
     }
src/Domain/Messaging/Service/ForwardContentService.php (1)

40-40: TODO noted - link handling for forwards.

Worth tracking this as an issue so it doesn't get lost. Forwarded messages might need special treatment for click tracking or unsubscribe links.

Would you like me to open an issue to track the link handling investigation for forwarded messages?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants