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
5 changes: 3 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,14 @@
},
"require": {
"php": "^8.1",
"phplist/core": "dev-main",
"phplist/core": "dev-ref/campaign-processing",
"friendsofsymfony/rest-bundle": "*",
"symfony/test-pack": "^1.0",
"symfony/process": "^6.4",
"zircote/swagger-php": "^4.11",
"ext-dom": "*",
"tatevikgr/rss-feed": "dev-main as 0.1.0"
"tatevikgr/rss-feed": "dev-main as 0.1.0",
"psr/simple-cache": "^3.0"
},
"require-dev": {
"phpunit/phpunit": "^10.0",
Expand Down
100 changes: 6 additions & 94 deletions config/services/normalizers.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,104 +4,16 @@ services:
autoconfigure: true
public: false

_instanceof:
Symfony\Component\Serializer\Normalizer\NormalizerInterface:
tags: [ 'serializer.normalizer' ]

Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter: ~

Symfony\Component\Serializer\Normalizer\ObjectNormalizer:
arguments:
$classMetadataFactory: '@?serializer.mapping.class_metadata_factory'
$nameConverter: '@Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter'

PhpList\RestBundle\Subscription\Serializer\SubscriberNormalizer:
tags: [ 'serializer.normalizer' ]
autowire: true

PhpList\RestBundle\Subscription\Serializer\SubscriberOnlyNormalizer:
tags: [ 'serializer.normalizer' ]
autowire: true

PhpList\RestBundle\Identity\Serializer\AdministratorTokenNormalizer:
tags: [ 'serializer.normalizer' ]
autowire: true

PhpList\RestBundle\Subscription\Serializer\SubscriberListNormalizer:
tags: [ 'serializer.normalizer' ]
autowire: true

PhpList\RestBundle\Subscription\Serializer\SubscriberHistoryNormalizer:
tags: [ 'serializer.normalizer' ]
autowire: true

PhpList\RestBundle\Subscription\Serializer\SubscriptionNormalizer:
tags: [ 'serializer.normalizer' ]
autowire: true

PhpList\RestBundle\Messaging\Serializer\MessageNormalizer:
tags: [ 'serializer.normalizer' ]
autowire: true

PhpList\RestBundle\Messaging\Serializer\TemplateImageNormalizer:
tags: [ 'serializer.normalizer' ]
autowire: true

PhpList\RestBundle\Messaging\Serializer\TemplateNormalizer:
tags: [ 'serializer.normalizer' ]
autowire: true

PhpList\RestBundle\Messaging\Serializer\ListMessageNormalizer:
tags: [ 'serializer.normalizer' ]
autowire: true

PhpList\RestBundle\Identity\Serializer\AdministratorNormalizer:
tags: [ 'serializer.normalizer' ]
autowire: true

PhpList\RestBundle\Identity\Serializer\AdminAttributeDefinitionNormalizer:
tags: [ 'serializer.normalizer' ]
autowire: true

PhpList\RestBundle\Identity\Serializer\AdminAttributeValueNormalizer:
tags: [ 'serializer.normalizer' ]
autowire: true

PhpList\RestBundle\Subscription\Serializer\AttributeDefinitionNormalizer:
tags: [ 'serializer.normalizer' ]
autowire: true

PhpList\RestBundle\Subscription\Serializer\SubscriberAttributeValueNormalizer:
tags: [ 'serializer.normalizer' ]
autowire: true

PhpList\RestBundle\Common\Serializer\CursorPaginationNormalizer:
autowire: true

PhpList\RestBundle\Subscription\Serializer\SubscribersExportRequestNormalizer:
tags: [ 'serializer.normalizer' ]
autowire: true

PhpList\RestBundle\Statistics\Serializer\CampaignStatisticsNormalizer:
tags: [ 'serializer.normalizer' ]
autowire: true

PhpList\RestBundle\Statistics\Serializer\ViewOpensStatisticsNormalizer:
tags: [ 'serializer.normalizer' ]
autowire: true

PhpList\RestBundle\Statistics\Serializer\TopDomainsNormalizer:
tags: [ 'serializer.normalizer' ]
autowire: true

PhpList\RestBundle\Statistics\Serializer\TopLocalPartsNormalizer:
tags: [ 'serializer.normalizer' ]
autowire: true

PhpList\RestBundle\Subscription\Serializer\UserBlacklistNormalizer:
tags: [ 'serializer.normalizer' ]
autowire: true

PhpList\RestBundle\Subscription\Serializer\SubscribePageNormalizer:
tags: [ 'serializer.normalizer' ]
autowire: true

PhpList\RestBundle\Messaging\Serializer\BounceRegexNormalizer:
tags: [ 'serializer.normalizer' ]
autowire: true
PhpList\RestBundle\:
resource: '../../src/*/Serializer/*'
15 changes: 13 additions & 2 deletions config/services/services.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
services:
PhpList\RestBundle\Subscription\Service\SubscriberService:
PhpList\RestBundle\Subscription\Service\SubscriberHistoryService:
autowire: true
autoconfigure: true

PhpList\RestBundle\Subscription\Service\SubscriberHistoryService:
PhpList\Core\Domain\Messaging\Service\ForwardingGuard:
autowire: true
autoconfigure: true
public: false

PhpList\Core\Domain\Messaging\Service\ForwardDeliveryService:
autowire: true
autoconfigure: true
public: false

PhpList\Core\Domain\Messaging\Service\ForwardContentService:
autowire: true
autoconfigure: true
public: false
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use Doctrine\ORM\EntityManagerInterface;
use OpenApi\Attributes as OA;
use PhpList\Core\Domain\Identity\Model\AdminAttributeDefinition;
use PhpList\Core\Domain\Identity\Service\AdminAttributeDefinitionManager;
use PhpList\Core\Domain\Identity\Service\Manager\AdminAttributeDefinitionManager;
use PhpList\Core\Security\Authentication;
use PhpList\RestBundle\Common\Controller\BaseController;
use PhpList\RestBundle\Common\Service\Provider\PaginatedDataProvider;
Expand Down
2 changes: 1 addition & 1 deletion src/Identity/Controller/AdminAttributeValueController.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
use PhpList\Core\Domain\Identity\Model\Administrator;
use PhpList\Core\Domain\Identity\Model\AdminAttributeDefinition;
use PhpList\Core\Domain\Identity\Model\AdminAttributeValue;
use PhpList\Core\Domain\Identity\Service\AdminAttributeManager;
use PhpList\Core\Domain\Identity\Service\Manager\AdminAttributeManager;
use PhpList\Core\Security\Authentication;
use PhpList\RestBundle\Common\Controller\BaseController;
use PhpList\RestBundle\Common\Service\Provider\PaginatedDataProvider;
Expand Down
2 changes: 1 addition & 1 deletion src/Identity/Controller/AdministratorController.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use Doctrine\ORM\EntityManagerInterface;
use OpenApi\Attributes as OA;
use PhpList\Core\Domain\Identity\Model\Administrator;
use PhpList\Core\Domain\Identity\Service\AdministratorManager;
use PhpList\Core\Domain\Identity\Service\Manager\AdministratorManager;
use PhpList\Core\Security\Authentication;
use PhpList\RestBundle\Common\Controller\BaseController;
use PhpList\RestBundle\Common\Service\Provider\PaginatedDataProvider;
Expand Down
2 changes: 1 addition & 1 deletion src/Identity/Controller/PasswordResetController.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

use Doctrine\ORM\EntityManagerInterface;
use OpenApi\Attributes as OA;
use PhpList\Core\Domain\Identity\Service\PasswordManager;
use PhpList\Core\Domain\Identity\Service\Manager\PasswordManager;
use PhpList\Core\Security\Authentication;
use PhpList\RestBundle\Common\Controller\BaseController;
use PhpList\RestBundle\Common\Validator\RequestValidator;
Expand Down
2 changes: 1 addition & 1 deletion src/Identity/Controller/SessionController.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use Doctrine\ORM\EntityManagerInterface;
use OpenApi\Attributes as OA;
use PhpList\Core\Domain\Identity\Model\AdministratorToken;
use PhpList\Core\Domain\Identity\Service\SessionManager;
use PhpList\Core\Domain\Identity\Service\Manager\SessionManager;
use PhpList\Core\Security\Authentication;
use PhpList\RestBundle\Common\Controller\BaseController;
use PhpList\RestBundle\Common\Validator\RequestValidator;
Expand Down
9 changes: 2 additions & 7 deletions src/Messaging/Controller/CampaignController.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,14 @@
#[Route('/campaigns', name: 'campaign_')]
class CampaignController extends BaseController
{
private CampaignService $campaignService;
private MessageBusInterface $messageBus;

public function __construct(
Authentication $authentication,
RequestValidator $validator,
CampaignService $campaignService,
MessageBusInterface $messageBus,
private readonly CampaignService $campaignService,
private readonly MessageBusInterface $messageBus,
private readonly EntityManagerInterface $entityManager,
) {
parent::__construct($authentication, $validator);
$this->campaignService = $campaignService;
$this->messageBus = $messageBus;
}

#[Route('', name: 'get_list', methods: ['GET'])]
Expand Down
121 changes: 121 additions & 0 deletions src/Messaging/Controller/EmailForwardController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<?php

declare(strict_types=1);

namespace PhpList\RestBundle\Messaging\Controller;

use Doctrine\ORM\EntityManagerInterface;
use OpenApi\Attributes as OA;
use PhpList\Core\Domain\Messaging\Model\Dto\MessageForwardDto;
use PhpList\Core\Domain\Messaging\Model\Message;
use PhpList\Core\Domain\Messaging\Service\MessageForwardService;
use PhpList\Core\Security\Authentication;
use PhpList\RestBundle\Common\Controller\BaseController;
use PhpList\RestBundle\Common\Validator\RequestValidator;
use PhpList\RestBundle\Messaging\Request\ForwardMessageRequest;
use PhpList\RestBundle\Messaging\Serializer\ForwardingResultNormalizer;
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

/**
* This controller provides REST API for email forwarding
*
* @author Tatevik Grigoryan <tatevik@phplist.com>
*/
#[Route('/email-forward', name: 'email_forward_')]
class EmailForwardController extends BaseController
{
public function __construct(
Authentication $authentication,
RequestValidator $validator,
private readonly EntityManagerInterface $entityManager,
private readonly MessageForwardService $messageForwardService,
private readonly ForwardingResultNormalizer $forwardingResultNormalizer,
) {
parent::__construct($authentication, $validator);
}

#[Route('/{messageId}', name: 'forward', requirements: ['messageId' => '\\d+'], methods: ['POST'])]
#[OA\Post(
path: '/api/v2/campaigns/{messageId}/forward',
description: '🚧 **Status: Beta** – This method is under development. Avoid using in production. ' .
'Queues forwarding of a campaign/message to provided recipient emails.',
summary: 'Forward a message to recipients.',
requestBody: new OA\RequestBody(
description: 'Forwarding payload',
required: true,
content: new OA\JsonContent(ref: '#/components/schemas/ForwardMessageRequest')
),
tags: ['campaigns'],
parameters: [
new OA\Parameter(
name: 'php-auth-pw',
description: 'Session key obtained from login',
in: 'header',
required: true,
schema: new OA\Schema(type: 'string')
),
new OA\Parameter(
name: 'messageId',
description: 'message ID',
in: 'path',
required: true,
schema: new OA\Schema(type: 'string')
)
],
responses: [
new OA\Response(
response: 202,
description: 'Accepted',
content: new OA\JsonContent(ref: '#/components/schemas/ForwardResult')
),
new OA\Response(
response: 403,
description: 'Failure',
content: new OA\JsonContent(ref: '#/components/schemas/UnauthorizedResponse')
),
new OA\Response(
response: 404,
description: 'Failure',
content: new OA\JsonContent(ref: '#/components/schemas/NotFoundErrorResponse')
),
new OA\Response(
response: 422,
description: 'Failure',
content: new OA\JsonContent(ref: '#/components/schemas/ValidationErrorResponse')
)
]
)]
public function forwardMessage(
Request $request,
#[MapEntity(mapping: ['messageId' => 'id'])] ?Message $message = null
): JsonResponse {
if ($message === null) {
throw $this->createNotFoundException('Campaign not found.');
}

/** @var ForwardMessageRequest $forwardRequest */
$forwardRequest = $this->validator->validate($request, ForwardMessageRequest::class);

$result = $this->messageForwardService->forward(
messageForwardDto: new MessageForwardDto(
emails: $forwardRequest->recipients,
uid: $forwardRequest->uid,
fromName: $forwardRequest->fromName,
fromEmail: $forwardRequest->fromEmail,
note: $forwardRequest->note,
),
campaign: $message,
);

$this->entityManager->flush();

return $this->json(
$this->forwardingResultNormalizer->normalize($result),
Response::HTTP_ACCEPTED
Comment on lines +70 to +118
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:

# First, let's see the full controller file context
cat -n src/Messaging/Controller/EmailForwardController.php | head -130

Repository: phpList/rest-api

Length of output: 5504


🏁 Script executed:

# Find the ForwardingResultNormalizer file
find . -name "*ForwardingResultNormalizer*" -type f

Repository: phpList/rest-api

Length of output: 117


🏁 Script executed:

# Search for schema definitions or OpenAPI schemas related to ForwardResult
find . -name "*.php" -type f | head -50 | xargs grep -l "ForwardResult\|ForwardingResult" 2>/dev/null | head -20

Repository: phpList/rest-api

Length of output: 113


🏁 Script executed:

cat -n src/Messaging/Serializer/ForwardingResultNormalizer.php

Repository: phpList/rest-api

Length of output: 2993


🏁 Script executed:

# Search for OpenAPI schema definitions (might be in attributes or configuration)
find . -type f -name "*.php" | xargs grep -l "#/components/schemas/ForwardResult" 2>/dev/null

Repository: phpList/rest-api

Length of output: 113


🏁 Script executed:

# Look for schema attribute definitions or any other ForwardResult/ForwardingResult references
rg "ForwardResult|ForwardingResult" --type php -C 3

Repository: phpList/rest-api

Length of output: 6174


🏁 Script executed:

cat -n src/Messaging/Serializer/ForwardResultNormalizer.php

Repository: phpList/rest-api

Length of output: 2500


Resolve the schema naming collision: ForwardingResultNormalizer should define 'ForwardingResult', not 'ForwardResult'.

Both ForwardingResultNormalizer and ForwardResultNormalizer define a schema named 'ForwardResult', but with completely different structures. This will break OpenAPI spec generation.

Rename the schema in ForwardingResultNormalizer (src/Messaging/Serializer/ForwardingResultNormalizer.php, line 22) from 'ForwardResult' to 'ForwardingResult' to match the class name and avoid the collision. Then update the controller's schema reference accordingly:

Fix
-            new OA\Response(
-                response: 202,
-                description: 'Accepted',
-                content: new OA\JsonContent(ref: '#/components/schemas/ForwardResult')
-            ),
+            new OA\Response(
+                response: 202,
+                description: 'Accepted',
+                content: new OA\JsonContent(ref: '#/components/schemas/ForwardingResult')
+            ),
🤖 Prompt for AI Agents
In `@src/Messaging/Controller/EmailForwardController.php` around lines 70 - 118,
The OpenAPI schema name collision is caused by ForwardingResultNormalizer
registering a schema as 'ForwardResult' — change the schema name in the
ForwardingResultNormalizer class from 'ForwardResult' to 'ForwardingResult'
(update the OA\Schema/@OA\Components reference inside that normalizer), then
update the EmailForwardController::forwardMessage OpenAPI response reference
from '#/components/schemas/ForwardResult' to
'#/components/schemas/ForwardingResult' so the controller's OA\JsonContent
refers to the new schema name (also search for any other references to
'ForwardResult' and update to 'ForwardingResult' if they point to the forwarding
normalizer).

);
}
}
Loading
Loading