From 937caa878e3f9b601a0efc3473260f0bd05b8c61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 13 Nov 2024 12:20:10 +0100 Subject: [PATCH 1/5] remove Date constraint on marital status date --- src/Bundle/ChillPersonBundle/Entity/Person.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Bundle/ChillPersonBundle/Entity/Person.php b/src/Bundle/ChillPersonBundle/Entity/Person.php index 5d24f3114..27a1a3f09 100644 --- a/src/Bundle/ChillPersonBundle/Entity/Person.php +++ b/src/Bundle/ChillPersonBundle/Entity/Person.php @@ -303,7 +303,6 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI /** * The date of the last marital status change of the person. */ - #[Assert\Date] #[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATE_MUTABLE, nullable: true)] private ?\DateTime $maritalStatusDate = null; From d6c55c830b88a8eb7494b551181952baddc711c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 13 Nov 2024 12:40:53 +0100 Subject: [PATCH 2/5] Persist EntityWorkflow and its steps with EntityManager Refactored NotificationToUserGroupsOnTransition to utilize EntityManager for persisting EntityWorkflowStep. This ensures entities have generated IDs, leading to proper email notifications during workflow transitions. --- .../NotificationToUserGroupsOnTransitionTest.php | 12 +++--------- .../NotificationToUserGroupsOnTransition.php | 8 ++++++++ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/Bundle/ChillMainBundle/Tests/Workflow/EventSubscriber/NotificationToUserGroupsOnTransitionTest.php b/src/Bundle/ChillMainBundle/Tests/Workflow/EventSubscriber/NotificationToUserGroupsOnTransitionTest.php index 329294ecc..ffa102978 100644 --- a/src/Bundle/ChillMainBundle/Tests/Workflow/EventSubscriber/NotificationToUserGroupsOnTransitionTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Workflow/EventSubscriber/NotificationToUserGroupsOnTransitionTest.php @@ -59,9 +59,7 @@ class NotificationToUserGroupsOnTransitionTest extends KernelTestCase public function testOnCompletedSendNotificationToUserGroupWithEmailAddress(): void { $entityWorkflow = new EntityWorkflow(); - $reflection = new \ReflectionClass($entityWorkflow); - $idProperty = $reflection->getProperty('id'); - $idProperty->setValue($entityWorkflow, 1); + $this->em->persist($entityWorkflow); $entityWorkflow->setWorkflowName('dummy'); $dto = new WorkflowTransitionContextDTO($entityWorkflow); @@ -69,11 +67,7 @@ class NotificationToUserGroupsOnTransitionTest extends KernelTestCase $ug->setEmail('test@email.com')->setLabel(['fr' => 'test group']); $mailer = $this->prophesize(MailerInterface::class); - $sendMethod = $mailer->send(Argument::that(function (RawMessage $message) use ($entityWorkflow): bool { - // we need to set an id to the current step of the entity workflow - $this->em->persist($entityWorkflow); - $this->em->persist($entityWorkflow->getCurrentStep()); - + $sendMethod = $mailer->send(Argument::that(function (RawMessage $message): bool { if (!$message instanceof TemplatedEmail) { return false; } @@ -140,7 +134,7 @@ class NotificationToUserGroupsOnTransitionTest extends KernelTestCase } }); - $notificationEventSubscriber = new NotificationToUserGroupsOnTransition($this->twig, $metadataExtractor, $registry, $mailer); + $notificationEventSubscriber = new NotificationToUserGroupsOnTransition($this->twig, $metadataExtractor, $registry, $mailer, $this->em); $eventDispatcher->addSubscriber($notificationEventSubscriber); return $registry; diff --git a/src/Bundle/ChillMainBundle/Workflow/EventSubscriber/NotificationToUserGroupsOnTransition.php b/src/Bundle/ChillMainBundle/Workflow/EventSubscriber/NotificationToUserGroupsOnTransition.php index 8ac5ab750..63682ec1a 100644 --- a/src/Bundle/ChillMainBundle/Workflow/EventSubscriber/NotificationToUserGroupsOnTransition.php +++ b/src/Bundle/ChillMainBundle/Workflow/EventSubscriber/NotificationToUserGroupsOnTransition.php @@ -14,6 +14,7 @@ namespace Chill\MainBundle\Workflow\EventSubscriber; use Chill\MainBundle\Entity\Notification; use Chill\MainBundle\Entity\Workflow\EntityWorkflow; use Chill\MainBundle\Workflow\Helper\MetadataExtractor; +use Doctrine\ORM\EntityManagerInterface; use Symfony\Bridge\Twig\Mime\TemplatedEmail; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Mailer\MailerInterface; @@ -27,6 +28,7 @@ final readonly class NotificationToUserGroupsOnTransition implements EventSubscr private MetadataExtractor $metadataExtractor, private Registry $registry, private MailerInterface $mailer, + private EntityManagerInterface $entityManager, ) {} public static function getSubscribedEvents(): array @@ -61,6 +63,12 @@ final readonly class NotificationToUserGroupsOnTransition implements EventSubscr $this->registry->get($entityWorkflow, $entityWorkflow->getWorkflowName()) ); + $currentStep = $entityWorkflow->getCurrentStep(); + if (!$this->entityManager->contains($currentStep)) { + // this is necessary to generate an id for the step + $this->entityManager->persist($currentStep); + } + // send to groups foreach ($entityWorkflow->getCurrentStep()->getDestUserGroups() as $userGroup) { if (!$userGroup->hasEmail()) { From 5ad11041e05722a447ba8937f1b5d2396fe1d649 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 13 Nov 2024 17:54:20 +0100 Subject: [PATCH 3/5] Add line splitting in user render string functionality Introduce a new option to split lines in user job and scope render strings based on a specified character limit. Implement a corresponding test to verify the correct behavior of this feature. --- .../Templating/Entity/UserRender.php | 32 ++++++++++-- .../Templating/Entity/UserRenderTest.php | 49 ++++++++++++++++++- 2 files changed, 76 insertions(+), 5 deletions(-) diff --git a/src/Bundle/ChillMainBundle/Templating/Entity/UserRender.php b/src/Bundle/ChillMainBundle/Templating/Entity/UserRender.php index 053e8d611..50fe26a3f 100644 --- a/src/Bundle/ChillMainBundle/Templating/Entity/UserRender.php +++ b/src/Bundle/ChillMainBundle/Templating/Entity/UserRender.php @@ -13,8 +13,6 @@ namespace Chill\MainBundle\Templating\Entity; use Chill\MainBundle\Entity\User; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; -use DateTime; -use DateTimeImmutable; use Symfony\Component\Clock\ClockInterface; use Symfony\Contracts\Translation\TranslatorInterface; use Twig\Error\LoaderError; @@ -26,11 +24,17 @@ use Twig\Error\SyntaxError; */ class UserRender implements ChillEntityRenderInterface { + public const SPLIT_LINE_BEFORE_CHARACTER = 'split_lines_before_characters'; final public const DEFAULT_OPTIONS = [ 'main_scope' => true, 'user_job' => true, 'absence' => true, 'at_date' => null, // instanceof DateTimeInterface + /* + * when set, the jobs and service will be splitted in multiple lines. The line will be splitted + * before the given character. Only for renderString, renderBox is not concerned. + */ + self::SPLIT_LINE_BEFORE_CHARACTER => null, ]; public function __construct( @@ -65,8 +69,6 @@ class UserRender implements ChillEntityRenderInterface { $opts = \array_merge(self::DEFAULT_OPTIONS, $options); - // $immutableAtDate = $opts['at_date'] instanceOf DateTime ? DateTimeImmutable::createFromMutable($opts['at_date']) : $opts['at_date']; - if (null === $opts['at_date']) { $opts['at_date'] = $this->clock->now(); } elseif ($opts['at_date'] instanceof \DateTime) { @@ -89,6 +91,28 @@ class UserRender implements ChillEntityRenderInterface $str .= ' ('.$this->translator->trans('absence.Absent').')'; } + if (null !== $opts[self::SPLIT_LINE_BEFORE_CHARACTER]) { + if (!is_int($opts[self::SPLIT_LINE_BEFORE_CHARACTER])) { + throw new \InvalidArgumentException('Only integer for option split_lines_before_characters is allowed'); + } + + $characterPerLine = $opts[self::SPLIT_LINE_BEFORE_CHARACTER]; + $exploded = explode(' ', $str); + $charOnLine = 0; + $str = ''; + foreach ($exploded as $word) { + if ($charOnLine + strlen($word) > $characterPerLine) { + $str .= "\n"; + $charOnLine = 0; + } + if ($charOnLine > 0) { + $str .= ' '; + } + $str .= $word; + $charOnLine += strlen($word); + } + } + return $str; } diff --git a/src/Bundle/ChillMainBundle/Tests/Templating/Entity/UserRenderTest.php b/src/Bundle/ChillMainBundle/Tests/Templating/Entity/UserRenderTest.php index 2b0e07732..b22580569 100644 --- a/src/Bundle/ChillMainBundle/Tests/Templating/Entity/UserRenderTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Templating/Entity/UserRenderTest.php @@ -35,7 +35,6 @@ class UserRenderTest extends TestCase public function testRenderUserWithJobAndScopeAtCertainDate(): void { // Create a user with a certain user job - $user = new User(); $userJobA = new UserJob(); $scopeA = new Scope(); @@ -106,4 +105,52 @@ class UserRenderTest extends TestCase $expectedStringC = 'BOB ISLA (directrice) (service B)'; $this->assertEquals($expectedStringC, $renderer->renderString($user, $optionsNoDate)); } + + public function testRenderStringWithSplitLines(): void + { + + // Create a user with a certain user job + $user = new User(); + $userJobA = new UserJob(); + $scopeA = new Scope(); + + $userJobA->setLabel(['fr' => 'assistant social en maison de service accompagné']) + ->setActive(true); + $scopeA->setName(['fr' => 'service de l\'assistant professionnel']); + $user->setLabel('Robert Van Zorrizzeen Gorikke'); + + $userJobHistoryA = (new User\UserJobHistory()) + ->setUser($user) + ->setJob($userJobA) + ->setStartDate(new \DateTimeImmutable('2023-11-01 12:00:00')) + ->setEndDate(new \DateTimeImmutable('2023-11-30 00:00:00')); + + $userScopeHistoryA = (new User\UserScopeHistory()) + ->setUser($user) + ->setScope($scopeA) + ->setStartDate(new \DateTimeImmutable('2023-11-01 12:00:00')) + ->setEndDate(new \DateTimeImmutable('2023-11-30 00:00:00')); + + $user->getUserJobHistories()->add($userJobHistoryA); + $user->getUserScopeHistories()->add($userScopeHistoryA); + + // Create renderer + $translatableStringHelperMock = $this->prophesize(TranslatableStringHelperInterface::class); + $translatableStringHelperMock->localize(Argument::type('array'))->will(fn ($args) => $args[0]['fr']); + + $engineMock = $this->createMock(Environment::class); + $translatorMock = $this->createMock(TranslatorInterface::class); + $clock = new MockClock(new \DateTimeImmutable('2023-11-15 12:00:00')); + + $renderer = new UserRender($translatableStringHelperMock->reveal(), $engineMock, $translatorMock, $clock); + + $actual = $renderer->renderString($user, ['split_lines_before_characters' => 30]); + self::assertEquals(<<<'STR' + Robert Van Zorrizzeen Gorikke + (assistant social en maison de + service accompagné) (service de + l'assistant professionnel) + STR, $actual); + + } } From 94f9ebd726de9cae7e08e2aa7c22778bcc42b83a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 13 Nov 2024 17:54:28 +0100 Subject: [PATCH 4/5] Add UserRender option to SignatureRequestController Integrated a new UserRender option 'SPLIT_LINE_BEFORE_CHARACTER' with a value of 30 in the SignatureRequestController. This enhances the rendering format for user details. --- .../Controller/SignatureRequestController.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Bundle/ChillDocStoreBundle/Controller/SignatureRequestController.php b/src/Bundle/ChillDocStoreBundle/Controller/SignatureRequestController.php index c6c564be1..b443779bb 100644 --- a/src/Bundle/ChillDocStoreBundle/Controller/SignatureRequestController.php +++ b/src/Bundle/ChillDocStoreBundle/Controller/SignatureRequestController.php @@ -19,6 +19,7 @@ use Chill\MainBundle\Entity\Workflow\EntityWorkflowSignatureStateEnum; use Chill\MainBundle\Entity\Workflow\EntityWorkflowStepSignature; use Chill\MainBundle\Templating\Entity\ChillEntityRenderManagerInterface; use Chill\MainBundle\Security\Authorization\EntityWorkflowStepSignatureVoter; +use Chill\MainBundle\Templating\Entity\UserRender; use Chill\MainBundle\Workflow\EntityWorkflowManager; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; @@ -75,6 +76,7 @@ class SignatureRequestController // options for user render 'absence' => false, 'main_scope' => false, + UserRender::SPLIT_LINE_BEFORE_CHARACTER => 30, // options for person render 'addAge' => false, ]), From aad10cc61f7d3c8b099d3a56690bcb8a2909baba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 13 Nov 2024 22:41:30 +0100 Subject: [PATCH 5/5] Add workflow permission check to StoredObjectVoter This commit introduces logic to grant permissions based on workflow conditions in the `AbstractStoredObjectVoter`. It also includes a new test case to ensure the workflow-based permission check functions correctly. --- .../AbstractStoredObjectVoter.php | 5 +++++ .../AbstractStoredObjectVoterTest.php | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/Bundle/ChillDocStoreBundle/Security/Authorization/StoredObjectVoter/AbstractStoredObjectVoter.php b/src/Bundle/ChillDocStoreBundle/Security/Authorization/StoredObjectVoter/AbstractStoredObjectVoter.php index 1b9378e72..f8713f2dc 100644 --- a/src/Bundle/ChillDocStoreBundle/Security/Authorization/StoredObjectVoter/AbstractStoredObjectVoter.php +++ b/src/Bundle/ChillDocStoreBundle/Security/Authorization/StoredObjectVoter/AbstractStoredObjectVoter.php @@ -49,6 +49,11 @@ abstract class AbstractStoredObjectVoter implements StoredObjectVoterInterface // Retrieve the related accompanying course document $entity = $this->getRepository()->findAssociatedEntityToStoredObject($subject); + if ($this->workflowDocumentService->isAllowedByWorkflow($entity)) { + // read and write permissions are granted by workflow + return true; + } + // Determine the attribute to pass to AccompanyingCourseDocumentVoter $voterAttribute = $this->attributeToRole($attribute); diff --git a/src/Bundle/ChillDocStoreBundle/Tests/Security/Authorization/AbstractStoredObjectVoterTest.php b/src/Bundle/ChillDocStoreBundle/Tests/Security/Authorization/AbstractStoredObjectVoterTest.php index 6fbb9c2e4..0090496db 100644 --- a/src/Bundle/ChillDocStoreBundle/Tests/Security/Authorization/AbstractStoredObjectVoterTest.php +++ b/src/Bundle/ChillDocStoreBundle/Tests/Security/Authorization/AbstractStoredObjectVoterTest.php @@ -99,6 +99,25 @@ class AbstractStoredObjectVoterTest extends TestCase $this->workflowDocumentService->method('notBlockedByWorkflow')->willReturn($workflowAllowed); } + public function testIsAllowedByWorkflow(): void + { + [$user, $token, $subject, $entity] = $this->setupMockObjects(); + $workflowRelatedEntityPermissionHelper = $this->createMock(WorkflowRelatedEntityPermissionHelper::class); + $workflowRelatedEntityPermissionHelper->method('isAllowedByWorkflow')->withAnyParameters()->willReturn(true); + + $associatedObjectRepository = $this->createMock(AssociatedEntityToStoredObjectInterface::class); + $associatedObjectRepository->method('findAssociatedEntityToStoredObject')->willReturn($entity); + + $voter = $this->buildStoredObjectVoter( + true, + $associatedObjectRepository, + $this->createMock(Security::class), + $workflowRelatedEntityPermissionHelper + ); + + self::assertTrue($voter->voteOnAttribute(StoredObjectRoleEnum::EDIT, $subject, $token)); + } + public function testSupportsOnAttribute(): void { [$user, $token, $subject, $entity] = $this->setupMockObjects();