mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
Implement the controller action to view the EntityworkflowSend
This commit is contained in:
parent
a0b5c208eb
commit
5c0f3cb317
@ -12,14 +12,69 @@ declare(strict_types=1);
|
|||||||
namespace Chill\MainBundle\Controller;
|
namespace Chill\MainBundle\Controller;
|
||||||
|
|
||||||
use Chill\MainBundle\Entity\Workflow\EntityWorkflowSend;
|
use Chill\MainBundle\Entity\Workflow\EntityWorkflowSend;
|
||||||
|
use Chill\MainBundle\Entity\Workflow\EntityWorkflowSendView;
|
||||||
|
use Chill\MainBundle\Workflow\EntityWorkflowManager;
|
||||||
|
use Chill\MainBundle\Workflow\Exception\HandlerWithPublicViewNotFoundException;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Symfony\Component\Clock\ClockInterface;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||||
use Symfony\Component\Routing\Annotation\Route;
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
use Twig\Environment;
|
||||||
|
|
||||||
class WorkflowViewSendPublicController
|
final readonly class WorkflowViewSendPublicController
|
||||||
{
|
{
|
||||||
#[Route('/public/main/workflow/send/{uuid}/view/{verificationKey}', methods: ['GET'], name: 'chill_main_workflow_send_view_public')]
|
public const LOG_PREFIX = '[workflow-view-send-public-controller] ';
|
||||||
public function __invoke(EntityWorkflowSend $workflowSend, string $verificationKey): Response
|
|
||||||
|
public function __construct(
|
||||||
|
private EntityManagerInterface $entityManager,
|
||||||
|
private LoggerInterface $chillLogger,
|
||||||
|
private EntityWorkflowManager $entityWorkflowManager,
|
||||||
|
private ClockInterface $clock,
|
||||||
|
private Environment $environment,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
#[Route('/public/main/workflow/send/{uuid}/view/{verificationKey}', name: 'chill_main_workflow_send_view_public', methods: ['GET'])]
|
||||||
|
public function __invoke(EntityWorkflowSend $workflowSend, string $verificationKey, Request $request): Response
|
||||||
{
|
{
|
||||||
return new Response('ok');
|
if (50 < $workflowSend->getNumberOfErrorTrials()) {
|
||||||
|
throw new AccessDeniedHttpException('number of trials exceeded, no more access allowed');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($verificationKey !== $workflowSend->getPrivateToken()) {
|
||||||
|
$this->chillLogger->info(self::LOG_PREFIX.'Invalid trial for this send', ['client_ip' => $request->getClientIp()]);
|
||||||
|
$workflowSend->increaseErrorTrials();
|
||||||
|
$this->entityManager->flush();
|
||||||
|
|
||||||
|
throw new AccessDeniedHttpException('invalid verification key');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->clock->now() > $workflowSend->getExpireAt()) {
|
||||||
|
return new Response(
|
||||||
|
$this->environment->render('@ChillMain/Workflow/workflow_view_send_public_expired.html.twig'),
|
||||||
|
409
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (100 < $workflowSend->getViews()->count()) {
|
||||||
|
$this->chillLogger->info(self::LOG_PREFIX.'100 view reached, not allowed to see it again');
|
||||||
|
throw new AccessDeniedHttpException('100 views reached, not allowed to see it again');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$response = new Response(
|
||||||
|
$this->entityWorkflowManager->renderPublicView($workflowSend),
|
||||||
|
);
|
||||||
|
|
||||||
|
$view = new EntityWorkflowSendView($workflowSend, $this->clock->now(), $request->getClientIp());
|
||||||
|
$this->entityManager->persist($view);
|
||||||
|
$this->entityManager->flush();
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
} catch (HandlerWithPublicViewNotFoundException $e) {
|
||||||
|
throw new \RuntimeException('Could not render the public view', previous: $e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -119,6 +119,11 @@ class EntityWorkflowSend implements TrackCreationInterface
|
|||||||
return $this->privateToken;
|
return $this->privateToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getEntityWorkflowStep(): EntityWorkflowStep
|
||||||
|
{
|
||||||
|
return $this->entityWorkflowStep;
|
||||||
|
}
|
||||||
|
|
||||||
public function getUuid(): UuidInterface
|
public function getUuid(): UuidInterface
|
||||||
{
|
{
|
||||||
return $this->uuid;
|
return $this->uuid;
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>{{ 'workflow.public_link.expired_link_title'|trans }}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>{{ 'workflow.public_link.expired_link_title'|trans }}</h1>
|
||||||
|
|
||||||
|
<p>{{ 'workflow.public_link.expired_link_explanation'|trans }}</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,234 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Tests\Controller;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Controller\WorkflowViewSendPublicController;
|
||||||
|
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
|
||||||
|
use Chill\MainBundle\Entity\Workflow\EntityWorkflowSend;
|
||||||
|
use Chill\MainBundle\Entity\Workflow\EntityWorkflowSendView;
|
||||||
|
use Chill\MainBundle\Workflow\EntityWorkflowHandlerInterface;
|
||||||
|
use Chill\MainBundle\Workflow\EntityWorkflowManager;
|
||||||
|
use Chill\MainBundle\Workflow\EntityWorkflowWithPublicViewInterface;
|
||||||
|
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Prophecy\Argument;
|
||||||
|
use Prophecy\PhpUnit\ProphecyTrait;
|
||||||
|
use Psr\Log\NullLogger;
|
||||||
|
use Symfony\Component\Clock\MockClock;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||||
|
use Symfony\Component\Workflow\Registry;
|
||||||
|
use Twig\Environment;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
class WorkflowViewSendPublicControllerTest extends TestCase
|
||||||
|
{
|
||||||
|
use ProphecyTrait;
|
||||||
|
|
||||||
|
public function testTooMuchTrials(): void
|
||||||
|
{
|
||||||
|
$environment = $this->prophesize(Environment::class);
|
||||||
|
$entityManager = $this->prophesize(EntityManagerInterface::class);
|
||||||
|
$entityManager->flush()->shouldNotBeCalled();
|
||||||
|
|
||||||
|
$controller = new WorkflowViewSendPublicController($entityManager->reveal(), new NullLogger(), new EntityWorkflowManager([], new Registry()), new MockClock(), $environment->reveal());
|
||||||
|
|
||||||
|
self::expectException(AccessDeniedHttpException::class);
|
||||||
|
|
||||||
|
$send = $this->buildEntityWorkflowSend();
|
||||||
|
|
||||||
|
for ($i = 0; $i < 51; ++$i) {
|
||||||
|
$send->increaseErrorTrials();
|
||||||
|
}
|
||||||
|
|
||||||
|
$controller($send, $send->getPrivateToken(), new Request());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testInvalidVerificationKey(): void
|
||||||
|
{
|
||||||
|
$environment = $this->prophesize(Environment::class);
|
||||||
|
$entityManager = $this->prophesize(EntityManagerInterface::class);
|
||||||
|
$entityManager->flush()->shouldBeCalled();
|
||||||
|
|
||||||
|
$controller = new WorkflowViewSendPublicController($entityManager->reveal(), new NullLogger(), new EntityWorkflowManager([], new Registry()), new MockClock(), $environment->reveal());
|
||||||
|
|
||||||
|
self::expectException(AccessDeniedHttpException::class);
|
||||||
|
|
||||||
|
$send = $this->buildEntityWorkflowSend();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$controller($send, 'some-token', new Request());
|
||||||
|
} catch (AccessDeniedHttpException $e) {
|
||||||
|
self::assertEquals(1, $send->getNumberOfErrorTrials());
|
||||||
|
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testExpiredLink(): void
|
||||||
|
{
|
||||||
|
$environment = $this->prophesize(Environment::class);
|
||||||
|
$environment->render('@ChillMain/Workflow/workflow_view_send_public_expired.html.twig')->willReturn('test');
|
||||||
|
|
||||||
|
$entityManager = $this->prophesize(EntityManagerInterface::class);
|
||||||
|
$entityManager->flush()->shouldNotBeCalled();
|
||||||
|
|
||||||
|
$controller = new WorkflowViewSendPublicController($entityManager->reveal(), new NullLogger(), new EntityWorkflowManager([], new Registry()), new MockClock('next year'), $environment->reveal());
|
||||||
|
|
||||||
|
$send = $this->buildEntityWorkflowSend();
|
||||||
|
|
||||||
|
$response = $controller($send, $send->getPrivateToken(), new Request());
|
||||||
|
|
||||||
|
self::assertEquals('test', $response->getContent());
|
||||||
|
self::assertEquals(409, $response->getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNoHandlerFound(): void
|
||||||
|
{
|
||||||
|
$environment = $this->prophesize(Environment::class);
|
||||||
|
$entityManager = $this->prophesize(EntityManagerInterface::class);
|
||||||
|
$entityManager->flush()->shouldNotBeCalled();
|
||||||
|
|
||||||
|
$controller = new WorkflowViewSendPublicController(
|
||||||
|
$entityManager->reveal(),
|
||||||
|
new NullLogger(),
|
||||||
|
new EntityWorkflowManager([], new Registry()),
|
||||||
|
new MockClock(),
|
||||||
|
$environment->reveal(),
|
||||||
|
);
|
||||||
|
|
||||||
|
self::expectException(\RuntimeException::class);
|
||||||
|
|
||||||
|
$send = $this->buildEntityWorkflowSend();
|
||||||
|
$controller($send, $send->getPrivateToken(), new Request());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testHappyScenario(): void
|
||||||
|
{
|
||||||
|
$send = $this->buildEntityWorkflowSend();
|
||||||
|
$environment = $this->prophesize(Environment::class);
|
||||||
|
$entityManager = $this->prophesize(EntityManagerInterface::class);
|
||||||
|
$entityManager->persist(Argument::that(function (EntityWorkflowSendView $view) use ($send) {
|
||||||
|
return $send === $view->getSend();
|
||||||
|
}))->shouldBeCalled();
|
||||||
|
$entityManager->flush()->shouldBeCalled();
|
||||||
|
|
||||||
|
$controller = new WorkflowViewSendPublicController(
|
||||||
|
$entityManager->reveal(),
|
||||||
|
new NullLogger(),
|
||||||
|
new EntityWorkflowManager([
|
||||||
|
$this->buildFakeHandler(),
|
||||||
|
], new Registry()),
|
||||||
|
new MockClock(),
|
||||||
|
$environment->reveal(),
|
||||||
|
);
|
||||||
|
|
||||||
|
$response = $controller($send, $send->getPrivateToken(), $this->buildRequest());
|
||||||
|
|
||||||
|
self::assertEquals(200, $response->getStatusCode());
|
||||||
|
self::assertEquals('content', $response->getContent());
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildFakeHandler(): EntityWorkflowHandlerInterface&EntityWorkflowWithPublicViewInterface
|
||||||
|
{
|
||||||
|
return new class () implements EntityWorkflowWithPublicViewInterface, EntityWorkflowHandlerInterface {
|
||||||
|
public function getDeletionRoles(): array
|
||||||
|
{
|
||||||
|
throw new \BadMethodCallException('not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEntityData(EntityWorkflow $entityWorkflow, array $options = []): array
|
||||||
|
{
|
||||||
|
throw new \BadMethodCallException('not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEntityTitle(EntityWorkflow $entityWorkflow, array $options = []): string
|
||||||
|
{
|
||||||
|
throw new \BadMethodCallException('not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRelatedEntity(EntityWorkflow $entityWorkflow): ?object
|
||||||
|
{
|
||||||
|
throw new \BadMethodCallException('not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRelatedObjects(object $object): array
|
||||||
|
{
|
||||||
|
throw new \BadMethodCallException('not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRoleShow(EntityWorkflow $entityWorkflow): ?string
|
||||||
|
{
|
||||||
|
throw new \BadMethodCallException('not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSuggestedUsers(EntityWorkflow $entityWorkflow): array
|
||||||
|
{
|
||||||
|
throw new \BadMethodCallException('not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTemplate(EntityWorkflow $entityWorkflow, array $options = []): string
|
||||||
|
{
|
||||||
|
throw new \BadMethodCallException('not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTemplateData(EntityWorkflow $entityWorkflow, array $options = []): array
|
||||||
|
{
|
||||||
|
throw new \BadMethodCallException('not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isObjectSupported(object $object): bool
|
||||||
|
{
|
||||||
|
throw new \BadMethodCallException('not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supports(EntityWorkflow $entityWorkflow, array $options = []): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsFreeze(EntityWorkflow $entityWorkflow, array $options = []): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findByRelatedEntity(object $object): array
|
||||||
|
{
|
||||||
|
throw new \BadMethodCallException('not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function renderPublicView(EntityWorkflowSend $entityWorkflowSend): string
|
||||||
|
{
|
||||||
|
return 'content';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildRequest(): Request
|
||||||
|
{
|
||||||
|
return Request::create('/test', server: ['REMOTE_ADDR' => '10.0.0.10']);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildEntityWorkflowSend(): EntityWorkflowSend
|
||||||
|
{
|
||||||
|
$entityWorkflow = new EntityWorkflow();
|
||||||
|
|
||||||
|
$step = $entityWorkflow->getCurrentStep();
|
||||||
|
|
||||||
|
return new EntityWorkflowSend($step, new ThirdParty(), new \DateTimeImmutable('next month'));
|
||||||
|
}
|
||||||
|
}
|
@ -62,7 +62,7 @@ class PostSendExternalMessageHandlerTest extends TestCase
|
|||||||
|
|
||||||
private function buildCheckAddressCallback(string $emailToCheck): callable
|
private function buildCheckAddressCallback(string $emailToCheck): callable
|
||||||
{
|
{
|
||||||
return fn(TemplatedEmail $email): bool => in_array($emailToCheck, array_map(fn (Address $addr) => $addr->getAddress(), $email->getTo()), true);
|
return fn (TemplatedEmail $email): bool => in_array($emailToCheck, array_map(fn (Address $addr) => $addr->getAddress(), $email->getTo()), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function buildEntityWorkflow(): EntityWorkflow
|
private function buildEntityWorkflow(): EntityWorkflow
|
||||||
|
@ -13,9 +13,16 @@ namespace Chill\MainBundle\Workflow;
|
|||||||
|
|
||||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
|
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
|
||||||
|
use Chill\MainBundle\Entity\Workflow\EntityWorkflowSend;
|
||||||
use Chill\MainBundle\Workflow\Exception\HandlerNotFoundException;
|
use Chill\MainBundle\Workflow\Exception\HandlerNotFoundException;
|
||||||
|
use Chill\MainBundle\Workflow\Exception\HandlerWithPublicViewNotFoundException;
|
||||||
use Symfony\Component\Workflow\Registry;
|
use Symfony\Component\Workflow\Registry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manage the handler and performs some operation on handlers.
|
||||||
|
*
|
||||||
|
* Each handler must implement @{EntityWorkflowHandlerInterface::class}.
|
||||||
|
*/
|
||||||
class EntityWorkflowManager
|
class EntityWorkflowManager
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
@ -63,4 +70,26 @@ class EntityWorkflowManager
|
|||||||
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the public view for the given entity workflow send.
|
||||||
|
*
|
||||||
|
* @param EntityWorkflowSend $entityWorkflowSend the entity workflow send object
|
||||||
|
*
|
||||||
|
* @return string the rendered public view
|
||||||
|
*
|
||||||
|
* @throws HandlerWithPublicViewNotFoundException if no handler with public view is found
|
||||||
|
*/
|
||||||
|
public function renderPublicView(EntityWorkflowSend $entityWorkflowSend): string
|
||||||
|
{
|
||||||
|
$entityWorkflow = $entityWorkflowSend->getEntityWorkflowStep()->getEntityWorkflow();
|
||||||
|
|
||||||
|
foreach ($this->handlers as $handler) {
|
||||||
|
if ($handler instanceof EntityWorkflowWithPublicViewInterface && $handler->supports($entityWorkflow)) {
|
||||||
|
return $handler->renderPublicView($entityWorkflowSend);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new HandlerWithPublicViewNotFoundException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Workflow;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\Workflow\EntityWorkflowSend;
|
||||||
|
|
||||||
|
interface EntityWorkflowWithPublicViewInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Render the public view for EntityWorkflowSend.
|
||||||
|
*
|
||||||
|
* The public view must be a safe html string
|
||||||
|
*/
|
||||||
|
public function renderPublicView(EntityWorkflowSend $entityWorkflowSend): string;
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Workflow\Exception;
|
||||||
|
|
||||||
|
class HandlerWithPublicViewNotFoundException extends \RuntimeException {}
|
@ -565,6 +565,9 @@ workflow:
|
|||||||
transition_destinee_remove_emails: Supprimer
|
transition_destinee_remove_emails: Supprimer
|
||||||
transition_destinee_emails_help: Le lien sécurisé sera envoyé à chaque adresse indiquée
|
transition_destinee_emails_help: Le lien sécurisé sera envoyé à chaque adresse indiquée
|
||||||
|
|
||||||
|
public_link:
|
||||||
|
expired_link_title: Lien expiré
|
||||||
|
expired_link_explanation: Le lien a expiré, vous ne pouvez plus visualiser ce document.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user