Skip to content
Open

Dev #173

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
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
strategy:
fail-fast: false
matrix:
php-versions: ['8.1']
php-versions: ['8.2']
dependencies: ['latest', 'oldest']
steps:
- name: Checkout
Expand Down Expand Up @@ -63,7 +63,7 @@ jobs:
run: composer validate --no-check-all --no-check-lock --strict;
- name: Linting all php files
run: find src/ tests/ public/ -name ''*.php'' -print0 | xargs -0 -n 1 -P 4 php -l; php -l;
- name: Running unit tests
- name: Running unit tests
run: vendor/bin/phpunit tests/Unit/;
continue-on-error: ${{matrix.php-versions == '8.0' }} # [temp-php8]
- name: Running the integration tests
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/client-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
- name: Setup PHP with Composer and Extensions
uses: shivammathur/setup-php@v2
with:
php-version: 8.1
php-version: 8.2
extensions: mbstring, dom, fileinfo, mysql

- name: Cache Composer Dependencies
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/front-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
- name: Setup PHP with Composer and Extensions
uses: shivammathur/setup-php@v2
with:
php-version: 8.1
php-version: 8.2
extensions: mbstring, dom, fileinfo, mysql

- name: Cache Composer Dependencies
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/restapi-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
- name: Setup PHP with Composer and Extensions
uses: shivammathur/setup-php@v2
with:
php-version: 8.1
php-version: 8.2
extensions: mbstring, dom, fileinfo, mysql

- name: Cache Composer Dependencies
Expand Down
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@
"source": "https://github.com/phpList/rest-api"
},
"require": {
"php": "^8.1",
"phplist/core": "dev-main",
"php": "^8.2",
"phplist/core": "dev-dev",
"elasticsearch/elasticsearch": "^8.9",
"friendsofsymfony/rest-bundle": "*",
"symfony/test-pack": "^1.0",
Expand Down
83 changes: 83 additions & 0 deletions src/Identity/Controller/AdminNotificationController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<?php

declare(strict_types=1);

namespace PhpList\RestBundle\Identity\Controller;

use OpenApi\Attributes as OA;
use PhpList\Core\Domain\Identity\Model\Dto\AdminNotification;
use PhpList\Core\Domain\Identity\Service\Authentication;
use PhpList\Core\Domain\Identity\Service\Notification\AdminNotificationCollector;
use PhpList\RestBundle\Common\Controller\BaseController;
use PhpList\RestBundle\Common\Validator\RequestValidator;
use PhpList\RestBundle\Identity\Serializer\AdminNotificationNormalizer;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

/**
* Returns the in-app notifications to administrators after login.
*/
#[Route('/notifications', name: 'admin_notification_')]
class AdminNotificationController extends BaseController
{
public function __construct(
Authentication $authentication,
RequestValidator $validator,
private readonly AdminNotificationCollector $collector,
private readonly AdminNotificationNormalizer $normalizer,
) {
parent::__construct($authentication, $validator);
}

#[Route('', name: 'list', methods: ['GET'])]
#[OA\Get(
path: '/api/v2/notifications',
description: '🚧 **Status: Beta** – This method is under development. Avoid using in production. ' .
'Returns the in-app notifications for the authenticated administrator.',
summary: 'Gets admin notifications.',
tags: ['notifications'],
parameters: [
new OA\Parameter(
name: 'php-auth-pw',
description: 'Session key obtained from login',
in: 'header',
required: true,
schema: new OA\Schema(type: 'string')
),
],
responses: [
new OA\Response(
response: 200,
description: 'Success',
content: new OA\JsonContent(
properties: [
new OA\Property(
property: 'items',
type: 'array',
items: new OA\Items(ref: '#/components/schemas/AdminNotification')
),
],
type: 'object'
)
),
new OA\Response(
response: 403,
description: 'Failure',
content: new OA\JsonContent(ref: '#/components/schemas/UnauthorizedResponse')
),
]
)]
public function list(Request $request): JsonResponse
{
$administrator = $this->requireAuthentication($request);

$items = array_map(
fn (AdminNotification $notification): array => $this->normalizer->normalize($notification),
$this->collector->collect($administrator)
);

return $this->json(['items' => $items], Response::HTTP_OK);
}
}
60 changes: 60 additions & 0 deletions src/Identity/Serializer/AdminNotificationNormalizer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

declare(strict_types=1);

namespace PhpList\RestBundle\Identity\Serializer;

use InvalidArgumentException;
use OpenApi\Attributes as OA;
use PhpList\Core\Domain\Identity\Model\Dto\AdminNotification;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;

#[OA\Schema(
schema: 'AdminNotification',
properties: [
new OA\Property(property: 'code', type: 'string', example: 'campaigns_in_queue'),
new OA\Property(property: 'level', type: 'string', enum: ['info', 'warning', 'error']),
new OA\Property(property: 'message', type: 'string'),
new OA\Property(property: 'context', type: 'object'),
],
type: 'object'
)]
class AdminNotificationNormalizer implements NormalizerInterface
{
/**
* @SuppressWarnings("PHPMD.UnusedFormalParameter")
* @throws InvalidArgumentException
*/
public function normalize($object, string $format = null, array $context = []): array
{
if (!$object instanceof AdminNotification) {
throw new InvalidArgumentException('Expected an AdminNotification object.');
}

return [
'code' => $object->code,
'level' => $object->level,
'message' => $object->message,
// an object, so that an empty context is encoded as {} and not []
'context' => (object) $object->context,
];
}

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

/**
* @SuppressWarnings("PHPMD.UnusedFormalParameter")
*/
public function getSupportedTypes(?string $format): array
{
return [
AdminNotification::class => true,
];
}
}
2 changes: 1 addition & 1 deletion src/Messaging/Controller/AttachmentController.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public function download(
}

return new StreamedResponse(
callback: function () use ($downloadable) {
function () use ($downloadable) {
$stream = $downloadable->content;

if ($stream->isSeekable()) {
Expand Down
128 changes: 128 additions & 0 deletions src/Messaging/Controller/CampaignMessageDataController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
<?php

declare(strict_types=1);

namespace PhpList\RestBundle\Messaging\Controller;

use Doctrine\ORM\EntityManagerInterface;
use OpenApi\Attributes as OA;
use PhpList\Core\Domain\Identity\Service\Authentication;
use PhpList\Core\Domain\Identity\Service\PermissionChecker;
use PhpList\Core\Domain\Messaging\Model\Message;
use PhpList\Core\Domain\Messaging\Service\Manager\MessageDataManager;
use PhpList\Core\Domain\Messaging\Service\MessageDataLoader;
use PhpList\RestBundle\Common\Controller\BaseController;
use PhpList\RestBundle\Common\Validator\RequestValidator;
use PhpList\RestBundle\Messaging\Dto\CampaignMessageData;
use PhpList\RestBundle\Messaging\Request\UpdateMessageDataRequest;
use PhpList\RestBundle\Messaging\Serializer\MessageDataNormalizer;
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 to set the campaign settings that are kept in the message data.
*/
#[Route('/campaigns', name: 'campaign_message_data_')]
class CampaignMessageDataController extends BaseController
{
public function __construct(
Authentication $authentication,
RequestValidator $validator,
private readonly MessageDataManager $messageDataManager,
private readonly MessageDataLoader $messageDataLoader,
private readonly PermissionChecker $permissionChecker,
private readonly EntityManagerInterface $entityManager,
private readonly MessageDataNormalizer $messageDataNormalizer,
) {
parent::__construct($authentication, $validator);
}

#[Route(
'/{messageId}/message-data',
name: 'update',
requirements: ['messageId' => '\d+'],
methods: ['PATCH']
)]
#[OA\Patch(
path: '/api/v2/campaigns/{messageId}/message-data',
description: '🚧 **Status: Beta** – This method is under development. Avoid using in production. ' .
'Sets campaign settings that are not part of the campaign itself: analytics tracking, notification ' .
'addresses and excluded lists. Only the fields that are sent are changed.',
summary: 'Set campaign message data.',
requestBody: new OA\RequestBody(
description: 'The settings to change.',
required: true,
content: new OA\JsonContent(ref: '#/components/schemas/UpdateMessageDataRequest')
),
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: 200,
description: 'Success, returns all the settings of the campaign',
content: new OA\JsonContent(ref: '#/components/schemas/MessageData')
),
new OA\Response(
response: 401,
description: 'Not authenticated',
content: new OA\JsonContent(ref: '#/components/schemas/UnauthorizedResponse')
),
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')
),
]
Comment thread
TatevikGr marked this conversation as resolved.
Resolved
)]
public function updateMessageData(
Request $request,
#[MapEntity(mapping: ['messageId' => 'id'])] ?Message $message = null,
): JsonResponse {
$authUser = $this->requireAuthentication($request);
if ($message === null) {
throw $this->createNotFoundException('Campaign not found.');
}

if (!$this->permissionChecker->canEdit($authUser, $message)) {
throw $this->createAccessDeniedException('You are not allowed to update campaigns.');
}

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

$this->messageDataManager->storeMessageData($message, $messageDataRequest->getDto());
$this->entityManager->flush();

$messageData = new CampaignMessageData(($this->messageDataLoader)($message));

return $this->json($this->messageDataNormalizer->normalize($messageData), Response::HTTP_OK);
}
}
10 changes: 9 additions & 1 deletion src/Messaging/Controller/ListMessageController.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Doctrine\ORM\EntityManagerInterface;
use OpenApi\Attributes as OA;
use PhpList\Core\Domain\Identity\Service\PermissionChecker;
use PhpList\Core\Domain\Messaging\Model\Message;
use PhpList\Core\Domain\Messaging\Service\Manager\ListMessageManager;
use PhpList\Core\Domain\Subscription\Model\SubscriberList;
Expand Down Expand Up @@ -35,6 +36,7 @@ public function __construct(
private readonly SubscriberListNormalizer $subscriberListNormalizer,
private readonly MessageNormalizer $messageNormalizer,
private readonly EntityManagerInterface $entityManager,
private readonly PermissionChecker $permissionChecker,
) {
parent::__construct($authentication, $validator);
}
Expand Down Expand Up @@ -244,7 +246,7 @@ public function associateMessageWithList(
#[MapEntity(mapping: ['messageId' => 'id'])] ?Message $message = null,
#[MapEntity(mapping: ['listId' => 'id'])] ?SubscriberList $subscriberList = null
): JsonResponse {
$this->requireAuthentication($request);
$authUser = $this->requireAuthentication($request);

if ($message === null) {
throw $this->createNotFoundException('Message not found.');
Expand All @@ -254,6 +256,12 @@ public function associateMessageWithList(
throw $this->createNotFoundException('Subscriber list not found.');
}

if (!$this->permissionChecker->canEdit($authUser, $message)
|| !$this->permissionChecker->canEdit($authUser, $subscriberList)
) {
throw $this->createAccessDeniedException('Access denied.');
}

$listMessage = $this->listMessageManager->associateMessageWithList($message, $subscriberList);
$this->entityManager->flush();

Expand Down
16 changes: 16 additions & 0 deletions src/Messaging/Dto/CampaignMessageData.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace PhpList\RestBundle\Messaging\Dto;

/**
* The message data of a campaign, as loaded by core's MessageDataLoader.
*/
class CampaignMessageData
{
/** @param array<string, mixed> $values */
public function __construct(public readonly array $values)
{
}
}
Loading
Loading