mirror of
				https://gitlab.com/Chill-Projet/chill-bundles.git
				synced 2025-10-31 01:08:26 +00:00 
			
		
		
		
	Allow to remove the canceled workflow to be deleted, if canceled
- OP#812 - https://champs-libres.openproject.com/work_packages/812
This commit is contained in:
		| @@ -56,7 +56,7 @@ class EntityWorkflowSend implements TrackCreationInterface | ||||
|     /** | ||||
|      * @var Collection<int, EntityWorkflowSendView> | ||||
|      */ | ||||
|     #[ORM\OneToMany(targetEntity: EntityWorkflowSendView::class, mappedBy: 'send')] | ||||
|     #[ORM\OneToMany(mappedBy: 'send', targetEntity: EntityWorkflowSendView::class, cascade: ['remove'])] | ||||
|     private Collection $views; | ||||
|  | ||||
|     public function __construct( | ||||
|   | ||||
| @@ -66,7 +66,7 @@ class EntityWorkflowStep | ||||
|     /** | ||||
|      * @var Collection <int, EntityWorkflowStepSignature> | ||||
|      */ | ||||
|     #[ORM\OneToMany(mappedBy: 'step', targetEntity: EntityWorkflowStepSignature::class, cascade: ['persist'], orphanRemoval: true)] | ||||
|     #[ORM\OneToMany(mappedBy: 'step', targetEntity: EntityWorkflowStepSignature::class, cascade: ['persist', 'remove'], orphanRemoval: true)] | ||||
|     private Collection $signatures; | ||||
|  | ||||
|     #[ORM\ManyToOne(targetEntity: EntityWorkflow::class, inversedBy: 'steps')] | ||||
| @@ -115,7 +115,7 @@ class EntityWorkflowStep | ||||
|     /** | ||||
|      * @var Collection<int, EntityWorkflowSend> | ||||
|      */ | ||||
|     #[ORM\OneToMany(mappedBy: 'entityWorkflowStep', targetEntity: EntityWorkflowSend::class, cascade: ['persist'], orphanRemoval: true)] | ||||
|     #[ORM\OneToMany(mappedBy: 'entityWorkflowStep', targetEntity: EntityWorkflowSend::class, cascade: ['persist', 'remove'], orphanRemoval: true)] | ||||
|     private Collection $sends; | ||||
|  | ||||
|     public function __construct() | ||||
|   | ||||
| @@ -17,6 +17,7 @@ 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; | ||||
| use Symfony\Component\Workflow\Registry; | ||||
|  | ||||
| class EntityWorkflowVoter extends Voter | ||||
| { | ||||
| @@ -32,6 +33,7 @@ class EntityWorkflowVoter extends Voter | ||||
|         private readonly EntityWorkflowManager $manager, | ||||
|         private readonly Security $security, | ||||
|         private readonly DuplicateEntityWorkflowFinder $duplicateEntityWorkflowFinder, | ||||
|         private readonly Registry $registry, | ||||
|     ) {} | ||||
|  | ||||
|     protected function supports($attribute, $subject) | ||||
| @@ -83,7 +85,25 @@ class EntityWorkflowVoter extends Voter | ||||
|                 return false; | ||||
|  | ||||
|             case self::DELETE: | ||||
|                 return 'initial' === $subject->getStep(); | ||||
|                 if ('initial' === $subject->getStep()) { | ||||
|                     return true; | ||||
|                 } | ||||
|  | ||||
|                 if (!$subject->isFinal()) { | ||||
|                     return false; | ||||
|                 } | ||||
|  | ||||
|                 // the entity workflow is finalized. We check if the final is not positive. If yes, we can | ||||
|                 // delete the entity | ||||
|                 $workflow = $this->registry->get($subject, $subject->getWorkflowName()); | ||||
|                 foreach ($workflow->getMarking($subject)->getPlaces() as $place => $key) { | ||||
|                     $metadata = $workflow->getMetadataStore()->getPlaceMetadata($place); | ||||
|                     if (false === ($metadata['isFinalPositive'] ?? true)) { | ||||
|                         return true; | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 return false; | ||||
|  | ||||
|             case self::SHOW_ENTITY_LINK: | ||||
|                 if ('initial' === $subject->getStep()) { | ||||
|   | ||||
| @@ -0,0 +1,194 @@ | ||||
| <?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\Security\Authorization; | ||||
|  | ||||
| use Chill\MainBundle\Entity\User; | ||||
| use Chill\MainBundle\Entity\Workflow\EntityWorkflow; | ||||
| use Chill\MainBundle\Security\Authorization\EntityWorkflowVoter; | ||||
| use Chill\MainBundle\Workflow\EntityWorkflowManager; | ||||
| use Chill\MainBundle\Workflow\EntityWorkflowMarkingStore; | ||||
| use Chill\MainBundle\Workflow\EventSubscriber\EntityWorkflowTransitionEventSubscriber; | ||||
| use Chill\MainBundle\Workflow\Helper\DuplicateEntityWorkflowFinder; | ||||
| use Chill\MainBundle\Workflow\WorkflowTransitionContextDTO; | ||||
| use PHPUnit\Framework\TestCase; | ||||
| use Psr\Log\NullLogger; | ||||
| use Symfony\Component\EventDispatcher\EventDispatcher; | ||||
| use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; | ||||
| use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; | ||||
| use Symfony\Component\Security\Core\Authorization\Voter\Voter; | ||||
| use Symfony\Component\Security\Core\Security; | ||||
| use Symfony\Component\Workflow\DefinitionBuilder; | ||||
| use Symfony\Component\Workflow\Metadata\InMemoryMetadataStore; | ||||
| use Symfony\Component\Workflow\Registry; | ||||
| use Symfony\Component\Workflow\SupportStrategy\WorkflowSupportStrategyInterface; | ||||
| use Symfony\Component\Workflow\Transition; | ||||
| use Symfony\Component\Workflow\Workflow; | ||||
| use Symfony\Component\Workflow\WorkflowInterface; | ||||
|  | ||||
| /** | ||||
|  * @internal | ||||
|  * | ||||
|  * @coversNothing | ||||
|  */ | ||||
| class EntityWorkflowVoterTest extends TestCase | ||||
| { | ||||
|     public function testVoteDeleteEntityWorkflowForInitialPlace(): void | ||||
|     { | ||||
|         $entityWorkflow = $this->buildEntityWorkflow(); | ||||
|  | ||||
|         $voter = $this->buildVoter(); | ||||
|         $token = $this->buildToken(); | ||||
|  | ||||
|         $actual = $voter->vote($token, $entityWorkflow, [EntityWorkflowVoter::DELETE]); | ||||
|  | ||||
|         self::assertEquals(Voter::ACCESS_GRANTED, $actual); | ||||
|     } | ||||
|  | ||||
|     public function testVoteDeleteEntityWorkflowForSomeOherPlace(): void | ||||
|     { | ||||
|         $entityWorkflow = $this->buildEntityWorkflow(); | ||||
|         $registry = $this->buildRegistry(); | ||||
|         $workflow = $registry->get($entityWorkflow, $entityWorkflow->getWorkflowName()); | ||||
|         $dto = new WorkflowTransitionContextDTO($entityWorkflow); | ||||
|         $dto->futureDestUsers = [new User()]; | ||||
|         $workflow->apply($entityWorkflow, 'move_to_in_between', ['context' => $dto, 'transition' => 'move_to_in_between', 'transitionAt' => new \DateTimeImmutable()]); | ||||
|  | ||||
|         assert('in_between' === $entityWorkflow->getStep(), 'we ensure that the workflow is well transitionned'); | ||||
|  | ||||
|         $voter = $this->buildVoter(); | ||||
|         $token = $this->buildToken(); | ||||
|  | ||||
|         $actual = $voter->vote($token, $entityWorkflow, [EntityWorkflowVoter::DELETE]); | ||||
|  | ||||
|         self::assertEquals(Voter::ACCESS_DENIED, $actual); | ||||
|     } | ||||
|  | ||||
|     public function testVoteDeleteEntityWorkflowForFinalPositive(): void | ||||
|     { | ||||
|         $entityWorkflow = $this->buildEntityWorkflow(); | ||||
|         $registry = $this->buildRegistry(); | ||||
|         $workflow = $registry->get($entityWorkflow, $entityWorkflow->getWorkflowName()); | ||||
|         $dto = new WorkflowTransitionContextDTO($entityWorkflow); | ||||
|         $dto->futureDestUsers = [new User()]; | ||||
|         $workflow->apply($entityWorkflow, 'move_to_final_positive', ['context' => $dto, 'transition' => 'move_to_final_positive', 'transitionAt' => new \DateTimeImmutable()]); | ||||
|  | ||||
|         assert('final_positive' === $entityWorkflow->getStep(), 'we ensure that the workflow is well transitionned'); | ||||
|  | ||||
|         $voter = $this->buildVoter(); | ||||
|         $token = $this->buildToken(); | ||||
|  | ||||
|         $actual = $voter->vote($token, $entityWorkflow, [EntityWorkflowVoter::DELETE]); | ||||
|  | ||||
|         self::assertEquals(Voter::ACCESS_DENIED, $actual); | ||||
|     } | ||||
|  | ||||
|     public function testVoteDeleteEntityWorkflowForFinalNegative(): void | ||||
|     { | ||||
|         $entityWorkflow = $this->buildEntityWorkflow(); | ||||
|         $registry = $this->buildRegistry(); | ||||
|         $workflow = $registry->get($entityWorkflow, $entityWorkflow->getWorkflowName()); | ||||
|         $dto = new WorkflowTransitionContextDTO($entityWorkflow); | ||||
|         $dto->futureDestUsers = [new User()]; | ||||
|         $workflow->apply($entityWorkflow, 'move_to_final_negative', ['context' => $dto, 'transition' => 'move_to_final_negative', 'transitionAt' => new \DateTimeImmutable()]); | ||||
|  | ||||
|         $voter = $this->buildVoter(); | ||||
|         $token = $this->buildToken(); | ||||
|  | ||||
|         $actual = $voter->vote($token, $entityWorkflow, [EntityWorkflowVoter::DELETE]); | ||||
|  | ||||
|         self::assertEquals(Voter::ACCESS_GRANTED, $actual); | ||||
|     } | ||||
|  | ||||
|     private function buildToken(): TokenInterface | ||||
|     { | ||||
|         return new UsernamePasswordToken($user = new User(), 'main', $user->getRoles()); | ||||
|     } | ||||
|  | ||||
|     private function buildVoter(): EntityWorkflowVoter | ||||
|     { | ||||
|         $manager = $this->createMock(EntityWorkflowManager::class); | ||||
|         $security = $this->createMock(Security::class); | ||||
|         $duplicateEntityWorkflowFind = $this->createMock(DuplicateEntityWorkflowFinder::class); | ||||
|  | ||||
|         return new EntityWorkflowVoter( | ||||
|             $manager, | ||||
|             $security, | ||||
|             $duplicateEntityWorkflowFind, | ||||
|             $this->buildRegistry() | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     private function buildEntityWorkflow(): EntityWorkflow | ||||
|     { | ||||
|         $entityWorkflow = new EntityWorkflow(); | ||||
|         $entityWorkflow->setWorkflowName('dummy'); | ||||
|         $entityWorkflow->setRelatedEntityId(1)->setRelatedEntityClass(\stdClass::class); | ||||
|  | ||||
|         return $entityWorkflow; | ||||
|     } | ||||
|  | ||||
|     private function buildRegistry(): Registry | ||||
|     { | ||||
|         $security = $this->createMock(Security::class); | ||||
|         $security->method('getUser')->willReturn(new User()); | ||||
|  | ||||
|         $builder = new DefinitionBuilder(); | ||||
|         $builder->addPlaces(['initial', 'in_between', 'final_positive', 'final_negative']); | ||||
|  | ||||
|         $metadataStore = new InMemoryMetadataStore( | ||||
|             placesMetadata: [ | ||||
|                 'final_positive' => [ | ||||
|                     'isFinal' => true, | ||||
|                     'isFinalPositive' => true, | ||||
|                 ], | ||||
|                 'final_negative' => [ | ||||
|                     'isFinal' => true, | ||||
|                     'isFinalPositive' => false, | ||||
|                 ], | ||||
|             ] | ||||
|         ); | ||||
|  | ||||
|         $builder->setMetadataStore($metadataStore); | ||||
|  | ||||
|         $transitions = [ | ||||
|             new Transition('move_to_in_between', 'initial', 'in_between'), | ||||
|             new Transition('move_to_final_positive', 'initial', 'final_positive'), | ||||
|             new Transition('move_to_final_negative', 'initial', 'final_negative'), | ||||
|         ]; | ||||
|  | ||||
|         foreach ($transitions as $transition) { | ||||
|             $builder->addTransition($transition); | ||||
|         } | ||||
|  | ||||
|         $definition = $builder->build(); | ||||
|  | ||||
|         $eventSubscriber = new EventDispatcher(); | ||||
|         $eventSubscriber->addSubscriber( | ||||
|             new EntityWorkflowTransitionEventSubscriber( | ||||
|                 new NullLogger(), | ||||
|                 $security | ||||
|             ) | ||||
|         ); | ||||
|  | ||||
|         $registry = new Registry(); | ||||
|         $workflow = new Workflow($definition, new EntityWorkflowMarkingStore(), $eventSubscriber, name: 'dummy'); | ||||
|  | ||||
|         $registry->addWorkflow($workflow, new class () implements WorkflowSupportStrategyInterface { | ||||
|             public function supports(WorkflowInterface $workflow, $subject): bool | ||||
|             { | ||||
|                 return true; | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         return $registry; | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user