Skip to content
Open

Dev #171

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
f9ee62d
Refactor: env (#170)
TatevikGr Jul 29, 2026
eb52ad4
Fix the order of route methods
tatevikg1 Jul 31, 2026
7129cec
Remove 'requeued' status from MessageMetadataRequest
tatevikg1 Aug 4, 2026
ab507da
Fix response schema references and add conflict response in TemplateC…
tatevikg1 Aug 12, 2026
d40679d
Refactor AnalyticsController: update dashboard routes and responses f…
tatevikg1 Aug 17, 2026
316b3a9
Add status and sort query parameters to CampaignController and implem…
tatevikg1 Aug 20, 2026
a36aaa4
Add getSupportedTypes method to normalizers for type support
tatevikg1 Aug 25, 2026
3279971
update phpstan
tatevikg1 Aug 26, 2026
72e848a
Elasticsearch (#172)
TatevikGr Sep 7, 2026
d7e3fcb
Add endpoints to resume and retrieve stuck campaigns in CampaignContr…
tatevikg1 Sep 9, 2026
47d8c24
Rename CampaignProcessorMessageHandler and TestCampaignProcessorMessa…
tatevikg1 Sep 18, 2026
d878e83
Update status property in BounceRegexRequest to include 'inactive' op…
tatevikg1 Sep 23, 2026
8e2df65
Update SubscribePagePublicNormalizer options serialization
tatevikg1 Sep 24, 2026
01847da
Update SubscriptionController to change schema for subscriber deletio…
tatevikg1 Sep 28, 2026
f708fe4
Add DomainConfirmationNormalizer and update AnalyticsController to us…
tatevikg1 Oct 1, 2026
020eb1b
fix tests
tatevikg1 Oct 1, 2026
11c1b54
Update SubscribePageController routes to remove leading slashes
tatevikg1 Oct 8, 2026
aae5f51
Fix updatingId reference in UniqueEmailValidator
tatevikg1 Oct 9, 2026
41675ad
Add route name prefix api_
tatevikg1 Oct 9, 2026
8ca32d3
Refactor UniqueEmailValidator and UniqueLoginNameValidator to use vio…
tatevikg1 Oct 9, 2026
f5eb970
Add name converter to UniqueEmailValidator configuration
tatevikg1 Oct 9, 2026
df69d24
fix
tatevikg1 Oct 9, 2026
0cc7d7d
ref: remove unnecessary privilege checks from SubscribePageController
tatevikg1 Oct 9, 2026
86c9ac2
ref: use permissionChecker
tatevikg1 Oct 9, 2026
4ef5dc0
Replace AccessDeniedHttpException with AccessDeniedException in Campa…
tatevikg1 Oct 12, 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
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ jobs:
export PHPLIST_DATABASE_PASSWORD=${{ env.DB_PASSWORD }}
export PHPLIST_DATABASE_PORT=${{ job.services.mysql.ports['3306'] }}
export PHPLIST_DATABASE_HOST=127.0.0.1
export PHPLIST_DATABASE_PATH=
vendor/bin/phpunit tests/Integration/
continue-on-error: ${{matrix.php-versions == '8.0' }} # [temp-php8]
- name: Running static analysis
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/client-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ on:

jobs:
generate-openapi:
if: (github.event_name == 'push' && (github.ref_name == 'main' || github.ref_name == 'dev')) || (github.event_name == 'pull_request' && github.base_ref == 'main')
runs-on: ubuntu-22.04
outputs:
source_branch: ${{ steps.branch.outputs.source_branch }}
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/front-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ on:
- main
jobs:
generate-openapi:
if: (github.event_name == 'push' && (github.ref_name == 'main' || github.ref_name == 'dev')) || (github.event_name == 'pull_request' && github.base_ref == 'main')
runs-on: ubuntu-22.04
outputs:
source_branch: ${{ steps.branch.outputs.source_branch }}
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,5 @@
/var/
/vendor/
.phpunit.result.cache
.env
.env.dist
21 changes: 14 additions & 7 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@
},
"require": {
"php": "^8.1",
"phplist/core": "dev-main",
"phplist/core": "dev-dev",
Comment thread
TatevikGr marked this conversation as resolved.
"elasticsearch/elasticsearch": "^8.9",
"friendsofsymfony/rest-bundle": "*",
"symfony/test-pack": "^1.0",
"symfony/process": "^6.4",
Expand All @@ -57,7 +58,7 @@
"phpunit/phpunit": "^10.0",
"guzzlehttp/guzzle": "^7.2.0",
"squizlabs/php_codesniffer": "^3.2.0",
"phpstan/phpstan": "^1.10",
"phpstan/phpstan": "^2.2",
"nette/caching": "^3.0.0",
"nikic/php-parser": "^4.19.1",
"phpmd/phpmd": "^2.6.0",
Expand Down Expand Up @@ -85,6 +86,7 @@
"PhpList\\Core\\Composer\\ScriptHandler::createGeneralConfiguration",
"PhpList\\Core\\Composer\\ScriptHandler::createBundleConfiguration",
"PhpList\\Core\\Composer\\ScriptHandler::createRoutesConfiguration",
"PhpList\\Core\\Composer\\ScriptHandler::createDotenvConfiguration",
"PhpList\\Core\\Composer\\ScriptHandler::createParametersConfiguration",
"PhpList\\Core\\Composer\\ScriptHandler::clearAllCaches"
],
Expand Down Expand Up @@ -114,27 +116,32 @@
"rest-api-identity": {
"resource": "@PhpListRestBundle/Identity/Controller/",
"type": "attribute",
"prefix": "/api/v2"
"prefix": "/api/v2",
"name_prefix": "api_"
},
"rest-api-subscription": {
"resource": "@PhpListRestBundle/Subscription/Controller/",
"type": "attribute",
"prefix": "/api/v2"
"prefix": "/api/v2",
"name_prefix": "api_"
},
"rest-api-messaging": {
"resource": "@PhpListRestBundle/Messaging/Controller/",
"type": "attribute",
"prefix": "/api/v2"
"prefix": "/api/v2",
"name_prefix": "api_"
},
"rest-api-configuration": {
"resource": "@PhpListRestBundle/Configuration/Controller/",
"type": "attribute",
"prefix": "/api/v2"
"prefix": "/api/v2",
"name_prefix": "api_"
},
"rest-api-analitics": {
"resource": "@PhpListRestBundle/Statistics/Controller/",
"type": "attribute",
"prefix": "/api/v2"
"prefix": "/api/v2",
"name_prefix": "api_"
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion config/services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ services:
calls:
- ['registerHandler', [ 'json', ['@my.secure_handler', 'createResponse'] ] ]

PhpList\Core\Security\Authentication:
PhpList\Core\Domain\Identity\Service\Authentication:
autowire: true
autoconfigure: true

Expand Down
4 changes: 2 additions & 2 deletions config/services/messenger_handlers.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
services:
PhpList\Core\Domain\Messaging\MessageHandler\CampaignProcessor\CampaignProcessorMessageHandler:
PhpList\Core\Domain\Messaging\MessageHandler\CampaignProcessorMessageHandler:
autowire: true
autoconfigure: true
public: false

PhpList\Core\Domain\Messaging\MessageHandler\CampaignProcessor\TestCampaignProcessorMessageHandler:
PhpList\Core\Domain\Messaging\MessageHandler\CampaignProcessorTestMessageHandler:
autowire: true
autoconfigure: true
public: false
1 change: 1 addition & 0 deletions config/services/validators.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ services:
arguments:
$serializer: '@phplist.request_serializer'
$validator: '@validator'
$nameConverter: '@Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter'

PhpList\RestBundle\Identity\Validator\Constraint\UniqueEmailValidator:
autowire: true
Expand Down
3 changes: 2 additions & 1 deletion phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/6.2/phpunit.xsd"
backupGlobals="false"
colors="true"
bootstrap="vendor/autoload.php"
bootstrap="tests/bootstrap.php"
>
<php>
<ini name="error_reporting" value="-1"/>
<server name="KERNEL_CLASS" value="PhpList\Core\Core\ApplicationKernel"/>
<server name="APP_ENV" value="test" force="true"/>
</php>
</phpunit>
4 changes: 2 additions & 2 deletions src/Common/Controller/BaseController.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
namespace PhpList\RestBundle\Common\Controller;

use PhpList\Core\Domain\Identity\Model\Administrator;
use PhpList\Core\Security\Authentication;
use PhpList\Core\Domain\Identity\Service\Authentication;
use PhpList\RestBundle\Common\Validator\RequestValidator;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;

/** @SuppressWarnings(PHPMD.NumberOfChildren) */
/** @SuppressWarnings("PHPMD.NumberOfChildren") */
abstract class BaseController extends AbstractController
{
protected Authentication $authentication;
Expand Down
4 changes: 2 additions & 2 deletions src/Common/EventListener/ExceptionListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
use PhpList\Core\Domain\Messaging\Exception\AttachmentFileNotFoundException;
use PhpList\Core\Domain\Messaging\Exception\MessageNotReceivedException;
use PhpList\Core\Domain\Messaging\Exception\SubscriberNotFoundException;
use PhpList\Core\Domain\Subscription\Exception\AttributeDefinitionCreationException;
use PhpList\Core\Domain\Common\Exception\AttributeDefinitionCreationException;
use PhpList\Core\Domain\Subscription\Exception\SubscriptionCreationException;
use PhpList\Core\Domain\Common\Exception\InvalidUploadException;
use PhpList\Core\Domain\Common\Exception\MissingUploadException;
Expand Down Expand Up @@ -57,7 +57,7 @@ public function onKernelException(ExceptionEvent $event): void
new JsonResponse([
'message' => 'Validation failed',
'errors' => $this->parseFlatValidationMessage($exception->getMessage()),
], 422)
], 422)
);

return;
Expand Down
14 changes: 12 additions & 2 deletions src/Common/Serializer/CursorPaginationNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class CursorPaginationNormalizer implements NormalizerInterface
{
/**
* @param CursorPaginationResult $object
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
* @SuppressWarnings("PHPMD.UnusedFormalParameter")
*/
public function normalize($object, string $format = null, array $context = []): array
{
Expand All @@ -32,10 +32,20 @@ public function normalize($object, string $format = null, array $context = []):
}

/**
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
* @SuppressWarnings("PHPMD.UnusedFormalParameter")
*/
public function supportsNormalization($data, string $format = null): bool
{
return $data instanceof CursorPaginationResult;
}

/**
* @SuppressWarnings("PHPMD.UnusedFormalParameter")
*/
public function getSupportedTypes(?string $format): array
{
return [
CursorPaginationResult::class => true,
];
}
}
6 changes: 4 additions & 2 deletions src/Common/Validator/RequestValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Throwable;
Expand All @@ -16,7 +17,8 @@ class RequestValidator
{
public function __construct(
private readonly DenormalizerInterface $serializer,
private readonly ValidatorInterface $validator
private readonly ValidatorInterface $validator,
private readonly NameConverterInterface $nameConverter
) {
}

Expand Down Expand Up @@ -66,7 +68,7 @@ public function validateDto(RequestInterface $request): RequestInterface
foreach ($errors as $violation) {
$lines[] = sprintf(
'%s: %s',
$violation->getPropertyPath(),
$this->nameConverter->normalize($violation->getPropertyPath()),
$violation->getMessage()
);
}
Expand Down
38 changes: 23 additions & 15 deletions src/Configuration/Controller/ConfigController.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
use PhpList\Core\Domain\Configuration\Exception\ConfigNotEditableException;
use PhpList\Core\Domain\Configuration\Model\Config;
use PhpList\Core\Domain\Configuration\Service\Manager\ConfigManager;
use PhpList\Core\Domain\Identity\Model\PrivilegeFlag;
use PhpList\Core\Security\Authentication;
use PhpList\Core\Domain\Identity\Service\Authentication;
use PhpList\Core\Domain\Identity\Service\PermissionChecker;
use PhpList\RestBundle\Common\Controller\BaseController;
use PhpList\RestBundle\Common\Validator\RequestValidator;
use PhpList\RestBundle\Configuration\Request\CreateConfigRequest;
Expand All @@ -31,6 +31,7 @@ public function __construct(
private readonly ConfigManager $manager,
private readonly ConfigNormalizer $normalizer,
private readonly EntityManagerInterface $entityManager,
private readonly PermissionChecker $permissionChecker,
) {
parent::__construct($authentication, $validator);
}
Expand Down Expand Up @@ -76,7 +77,10 @@ public function __construct(
)]
public function list(Request $request): JsonResponse
{
$this->denyUnlessSettingsAdmin($request, 'You are not allowed to view configuration.');
$admin = $this->requireAuthentication($request);
if (!$this->permissionChecker->canList($admin, Config::class)) {
throw $this->createAccessDeniedException('You are not allowed to view configuration.');
}
$items = $this->manager->getAllEditable();

usort(
Expand Down Expand Up @@ -148,10 +152,13 @@ public function getOne(
Request $request,
#[MapEntity(mapping: ['key' => 'key'])] ?Config $config,
): JsonResponse {
$this->denyUnlessSettingsAdmin($request, 'You are not allowed to view configuration.');
$admin = $this->requireAuthentication($request);
if ($config === null) {
throw $this->createNotFoundException('Configuration item not found.');
}
if (!$this->permissionChecker->canView($admin, $config)) {
throw $this->createAccessDeniedException('You are not allowed to view configuration.');
}

return $this->json($this->normalizer->normalize($config), Response::HTTP_OK);
}
Expand Down Expand Up @@ -202,7 +209,10 @@ public function getOne(
)]
public function create(Request $request): JsonResponse
{
$this->denyUnlessSettingsAdmin($request, 'You are not allowed to create configuration.');
$admin = $this->requireAuthentication($request);
if (!$this->permissionChecker->canCreate($admin, Config::class)) {
throw $this->createAccessDeniedException('You are not allowed to create configuration.');
}
/* @var CreateConfigRequest $configRequest */
$configRequest = $this->validator->validate($request, CreateConfigRequest::class);

Expand Down Expand Up @@ -267,10 +277,13 @@ public function update(
Request $request,
#[MapEntity(mapping: ['key' => 'key'])] ?Config $config = null
): JsonResponse {
$this->denyUnlessSettingsAdmin($request, 'You are not allowed to update configuration.');
$admin = $this->requireAuthentication($request);
if ($config === null) {
throw $this->createNotFoundException('Configuration item not found.');
}
if (!$this->permissionChecker->canEdit($admin, $config)) {
throw $this->createAccessDeniedException('You are not allowed to update configuration.');
}
/* @var UpdateConfigRequest $dto */
$dto = $this->validator->validate($request, UpdateConfigRequest::class);

Expand Down Expand Up @@ -325,22 +338,17 @@ public function delete(
Request $request,
#[MapEntity(mapping: ['key' => 'key'])] ?Config $config = null
): JsonResponse {
$this->denyUnlessSettingsAdmin($request, 'You are not allowed to delete configuration.');
$admin = $this->requireAuthentication($request);
if ($config === null) {
throw $this->createNotFoundException('Configuration item not found.');
}
if (!$this->permissionChecker->canDelete($admin, $config)) {
throw $this->createAccessDeniedException('You are not allowed to delete configuration.');
}

$this->manager->delete($config);
$this->entityManager->flush();

return $this->json(null, Response::HTTP_NO_CONTENT);
}

private function denyUnlessSettingsAdmin(Request $request, string $message): void
{
$admin = $this->requireAuthentication($request);
if (!$admin->getPrivileges()->has(PrivilegeFlag::Settings)) {
throw $this->createAccessDeniedException($message);
}
}
}
14 changes: 12 additions & 2 deletions src/Configuration/Serializer/ConfigNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class ConfigNormalizer implements NormalizerInterface
/**
* Normalizes a configuration item.
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
* @SuppressWarnings("PHPMD.UnusedFormalParameter")
*/
public function normalize($object, string $format = null, array $context = []): array
{
Expand All @@ -42,10 +42,20 @@ public function normalize($object, string $format = null, array $context = []):
/**
* Checks whether the value can be normalized.
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
* @SuppressWarnings("PHPMD.UnusedFormalParameter")
*/
public function supportsNormalization($data, string $format = null): bool
{
return $data instanceof Config;
}

/**
* @SuppressWarnings("PHPMD.UnusedFormalParameter")
*/
public function getSupportedTypes(?string $format): array
{
return [
Config::class => true,
];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
use PhpList\Core\Domain\Common\Model\Filter\PaginatedFilter;
use PhpList\Core\Domain\Identity\Model\AdminAttributeDefinition;
use PhpList\Core\Domain\Identity\Service\Manager\AdminAttributeDefinitionManager;
use PhpList\Core\Security\Authentication;
use PhpList\Core\Domain\Identity\Service\Authentication;
use PhpList\RestBundle\Common\Controller\BaseController;
use PhpList\RestBundle\Common\Service\Provider\PaginatedDataProvider;
use PhpList\RestBundle\Common\Validator\RequestValidator;
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 @@ -11,7 +11,7 @@
use PhpList\Core\Domain\Identity\Model\AdminAttributeDefinition;
use PhpList\Core\Domain\Identity\Model\AdminAttributeValue;
use PhpList\Core\Domain\Identity\Service\Manager\AdminAttributeManager;
use PhpList\Core\Security\Authentication;
use PhpList\Core\Domain\Identity\Service\Authentication;
use PhpList\RestBundle\Common\Controller\BaseController;
use PhpList\RestBundle\Common\Service\Provider\PaginatedDataProvider;
use PhpList\RestBundle\Common\Validator\RequestValidator;
Expand Down
4 changes: 2 additions & 2 deletions src/Identity/Controller/AdministratorController.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
use PhpList\Core\Domain\Common\Model\Filter\PaginatedFilter;
use PhpList\Core\Domain\Identity\Model\Administrator;
use PhpList\Core\Domain\Identity\Service\Manager\AdministratorManager;
use PhpList\Core\Security\Authentication;
use PhpList\Core\Domain\Identity\Service\Authentication;
use PhpList\RestBundle\Common\Controller\BaseController;
use PhpList\RestBundle\Common\Service\Provider\PaginatedDataProvider;
use PhpList\RestBundle\Common\Validator\RequestValidator;
Expand Down Expand Up @@ -257,7 +257,7 @@ public function updateAdministrator(
/** @var UpdateAdministratorRequest $updateRequest */
$updateRequest = $this->validator->validate(
request: $request,
dtoClass:UpdateAdministratorRequest::class,
dtoClass: UpdateAdministratorRequest::class,
beforeValidation: static function (UpdateAdministratorRequest $dto) use ($administrator): void {
$dto->setUpdatingId($administrator->getId());
}
Expand Down
Loading
Loading