From 5a5d259d1853a9e4c0e3a4c2c8f0b559e98e7ca9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 24 Sep 2024 14:18:50 +0200 Subject: [PATCH] Add duplicate workflow prevention in MetadataExtractor Integrate DuplicateEntityWorkflowFinder to prevent creating workflows for entities with existing opened or positive final workflows. Updated EntityWorkflowVoter to implement the same check before allowing creation. Removed unnecessary blank workflow parameter from Twig template. --- .../Authorization/EntityWorkflowVoter.php | 16 +++++- .../Helper/DuplicateEntityWorkflowFinder.php | 57 +++++++++++++++++++ .../Workflow/Helper/MetadataExtractor.php | 17 +++++- .../WorkflowTwigExtensionRuntime.php | 1 - 4 files changed, 87 insertions(+), 4 deletions(-) create mode 100644 src/Bundle/ChillMainBundle/Workflow/Helper/DuplicateEntityWorkflowFinder.php diff --git a/src/Bundle/ChillMainBundle/Security/Authorization/EntityWorkflowVoter.php b/src/Bundle/ChillMainBundle/Security/Authorization/EntityWorkflowVoter.php index 44766a583..ee45d1e56 100644 --- a/src/Bundle/ChillMainBundle/Security/Authorization/EntityWorkflowVoter.php +++ b/src/Bundle/ChillMainBundle/Security/Authorization/EntityWorkflowVoter.php @@ -13,6 +13,7 @@ namespace Chill\MainBundle\Security\Authorization; use Chill\MainBundle\Entity\Workflow\EntityWorkflow; use Chill\MainBundle\Workflow\EntityWorkflowManager; +use Chill\MainBundle\Workflow\Helper\DuplicateEntityWorkflowFinder; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authorization\Voter\Voter; use Symfony\Component\Security\Core\Security; @@ -27,7 +28,11 @@ class EntityWorkflowVoter extends Voter final public const SHOW_ENTITY_LINK = 'CHILL_MAIN_WORKFLOW_LINK_SHOW'; - public function __construct(private readonly EntityWorkflowManager $manager, private readonly Security $security) {} + public function __construct( + private readonly EntityWorkflowManager $manager, + private readonly Security $security, + private readonly DuplicateEntityWorkflowFinder $duplicateEntityWorkflowFinder, + ) {} protected function supports($attribute, $subject) { @@ -41,6 +46,15 @@ class EntityWorkflowVoter extends Voter { switch ($attribute) { case self::CREATE: + if (false === $this->voteOnAttribute(self::SEE, $subject, $token)) { + return false; + } + + if ($this->duplicateEntityWorkflowFinder->hasDuplicateOpenedOrFinalPositiveEntityWorkflow($subject)) { + return false; + } + + return true; case self::SEE: $handler = $this->manager->getHandler($subject); diff --git a/src/Bundle/ChillMainBundle/Workflow/Helper/DuplicateEntityWorkflowFinder.php b/src/Bundle/ChillMainBundle/Workflow/Helper/DuplicateEntityWorkflowFinder.php new file mode 100644 index 000000000..7ade75223 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Workflow/Helper/DuplicateEntityWorkflowFinder.php @@ -0,0 +1,57 @@ +entityWorkflowRepository->findByRelatedEntity($entityWorkflow->getRelatedEntityClass(), $entityWorkflow->getRelatedEntityId()); + + foreach ($otherWorkflows as $otherWorkflow) { + if ($entityWorkflow === $otherWorkflow) { + continue; + } + + if ($otherWorkflow->getWorkflowName() !== $entityWorkflow->getWorkflowName()) { + continue; + } + + if (!$this->openedEntityWorkflowHelper->isFinal($otherWorkflow)) { + return true; + } + + if ($this->openedEntityWorkflowHelper->isFinalPositive($otherWorkflow)) { + return true; + } + } + + return false; + } +} diff --git a/src/Bundle/ChillMainBundle/Workflow/Helper/MetadataExtractor.php b/src/Bundle/ChillMainBundle/Workflow/Helper/MetadataExtractor.php index f99449dc3..0774ad10a 100644 --- a/src/Bundle/ChillMainBundle/Workflow/Helper/MetadataExtractor.php +++ b/src/Bundle/ChillMainBundle/Workflow/Helper/MetadataExtractor.php @@ -19,20 +19,33 @@ use Symfony\Component\Workflow\WorkflowInterface; class MetadataExtractor { - public function __construct(private readonly Registry $registry, private readonly TranslatableStringHelperInterface $translatableStringHelper) {} + public function __construct( + private readonly Registry $registry, + private readonly TranslatableStringHelperInterface $translatableStringHelper, + private readonly DuplicateEntityWorkflowFinder $duplicateEntityWorkflowFinder, + ) {} public function availableWorkflowFor(string $relatedEntityClass, ?int $relatedEntityId = 0): array { $blankEntityWorkflow = new EntityWorkflow(); $blankEntityWorkflow ->setRelatedEntityId($relatedEntityId) - ->setRelatedEntityClass($relatedEntityClass); + ->setRelatedEntityClass($relatedEntityClass) + ; // build the list of available workflows, and extract their names from metadata $workflows = $this->registry->all($blankEntityWorkflow); $workflowsList = []; foreach ($workflows as $workflow) { + // shortcut: we must not be able to create a new workflow if there are already created workflows, + // so, we find if there are workflows opened or final positive for the same entity + $blankEntityWorkflow->setWorkflowName($workflow->getName()); + if ($this->duplicateEntityWorkflowFinder->hasDuplicateOpenedOrFinalPositiveEntityWorkflow($blankEntityWorkflow)) { + // if yes, we skip suggesting workflow + continue; + } + $metadata = $workflow->getMetadataStore()->getWorkflowMetadata(); $text = \array_key_exists('label', $metadata) ? $this->translatableStringHelper->localize($metadata['label']) : $workflow->getName(); diff --git a/src/Bundle/ChillMainBundle/Workflow/Templating/WorkflowTwigExtensionRuntime.php b/src/Bundle/ChillMainBundle/Workflow/Templating/WorkflowTwigExtensionRuntime.php index 332ccd2e5..87fbf4498 100644 --- a/src/Bundle/ChillMainBundle/Workflow/Templating/WorkflowTwigExtensionRuntime.php +++ b/src/Bundle/ChillMainBundle/Workflow/Templating/WorkflowTwigExtensionRuntime.php @@ -59,7 +59,6 @@ class WorkflowTwigExtensionRuntime implements RuntimeExtensionInterface return $environment->render('@ChillMain/Workflow/_extension_list_workflow_for.html.twig', [ 'entity_workflows_json' => $this->normalizer->normalize($entityWorkflows, 'json', ['groups' => 'read']), - 'blank_workflow' => $blankEntityWorkflow, 'workflows_available' => $workflowsAvailable, 'relatedEntityClass' => $relatedEntityClass, 'relatedEntityId' => $relatedEntityId,