diff --git a/src/Bundle/ChillMainBundle/Test/ProphecyTrait.php b/src/Bundle/ChillMainBundle/Test/ProphecyTrait.php index a6b903af5..7f59b5dd8 100644 --- a/src/Bundle/ChillMainBundle/Test/ProphecyTrait.php +++ b/src/Bundle/ChillMainBundle/Test/ProphecyTrait.php @@ -17,6 +17,7 @@ namespace Chill\MainBundle\Test; * **Usage : ** You must set up trait with `setUpTrait` before use * and use tearDownTrait after usage. * + * @deprecated use @see{\Prophecy\PhpUnit\ProphecyTrait} instead * @codeCoverageIgnore */ trait ProphecyTrait diff --git a/src/Bundle/ChillPersonBundle/Service/DocGenerator/PersonContext.php b/src/Bundle/ChillPersonBundle/Service/DocGenerator/PersonContext.php index 8fbd639c9..a9ebdc0bb 100644 --- a/src/Bundle/ChillPersonBundle/Service/DocGenerator/PersonContext.php +++ b/src/Bundle/ChillPersonBundle/Service/DocGenerator/PersonContext.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Service\DocGenerator; use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithAdminFormInterface; +use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithPublicFormInterface; use Chill\DocGeneratorBundle\Context\Exception\UnexpectedTypeException; use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate; use Chill\DocGeneratorBundle\Service\Context\BaseContextData; @@ -19,46 +20,72 @@ use Chill\DocStoreBundle\Entity\DocumentCategory; use Chill\DocStoreBundle\Entity\PersonDocument; use Chill\DocStoreBundle\Entity\StoredObject; use Chill\DocStoreBundle\Repository\DocumentCategoryRepository; +use Chill\DocStoreBundle\Security\Authorization\PersonDocumentVoter; +use Chill\MainBundle\Entity\Scope; +use Chill\MainBundle\Form\Type\ScopePickerType; +use Chill\MainBundle\Security\Authorization\AuthorizationHelper; +use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface; +use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Chill\PersonBundle\Entity\Person; use DateTime; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; +use LogicException; use Symfony\Bridge\Doctrine\Form\Type\EntityType; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Security\Core\Security; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; + use Symfony\Contracts\Translation\TranslatorInterface; - use function array_key_exists; +use function count; -class PersonContext implements DocGeneratorContextWithAdminFormInterface +class PersonContext implements DocGeneratorContextWithAdminFormInterface, DocGeneratorContextWithPublicFormInterface { + private AuthorizationHelperInterface $authorizationHelper; + private BaseContextData $baseContextData; + private CenterResolverManagerInterface $centerResolverManager; + private DocumentCategoryRepository $documentCategoryRepository; private EntityManagerInterface $em; private NormalizerInterface $normalizer; + private Security $security; + + private bool $showScopes; + private TranslatableStringHelperInterface $translatableStringHelper; private TranslatorInterface $translator; public function __construct( + AuthorizationHelperInterface $authorizationHelper, + BaseContextData $baseContextData, + CenterResolverManagerInterface $centerResolverManager, DocumentCategoryRepository $documentCategoryRepository, - NormalizerInterface $normalizer, - TranslatableStringHelperInterface $translatableStringHelper, EntityManagerInterface $em, + NormalizerInterface $normalizer, + ParameterBagInterface $parameterBag, + Security $security, TranslatorInterface $translator, - BaseContextData $baseContextData + TranslatableStringHelperInterface $translatableStringHelper ) { - $this->documentCategoryRepository = $documentCategoryRepository; - $this->normalizer = $normalizer; - $this->translatableStringHelper = $translatableStringHelper; - $this->em = $em; + $this->authorizationHelper = $authorizationHelper; + $this->centerResolverManager = $centerResolverManager; $this->baseContextData = $baseContextData; + $this->documentCategoryRepository = $documentCategoryRepository; + $this->em = $em; + $this->normalizer = $normalizer; + $this->security = $security; + $this->showScopes = $parameterBag->get('chill_main')['acl']['form_show_scopes']; $this->translator = $translator; + $this->translatableStringHelper = $translatableStringHelper; } public function adminFormReverseTransform(array $data): array @@ -105,6 +132,18 @@ class PersonContext implements DocGeneratorContextWithAdminFormInterface ]); } + /** + * @param Person $entity + */ + public function buildPublicForm(FormBuilderInterface $builder, DocGeneratorTemplate $template, $entity): void + { + $builder->add('scope', ScopePickerType::class, [ + 'center' => $this->centerResolverManager->resolveCenters($entity), + 'role' => PersonDocumentVoter::CREATE, + 'label' => 'Scope', + ]); + } + public function getData(DocGeneratorTemplate $template, $entity, array $contextGenerationData = []): array { if (!$entity instanceof Person) { @@ -156,6 +195,14 @@ class PersonContext implements DocGeneratorContextWithAdminFormInterface return true; } + /** + * @param Person $entity + */ + public function hasPublicForm(DocGeneratorTemplate $template, $entity): bool + { + return $this->isScopeNecessary($entity); + } + /** * @param Person $entity */ @@ -178,6 +225,36 @@ class PersonContext implements DocGeneratorContextWithAdminFormInterface ); } + if ($this->isScopeNecessary($entity)) { + $doc->setScope($contextGenerationData['scope']); + } elseif ($this->showScopes) { + // in this case, it should have only one scope possible, we get it through AuthorizationHelper::getReachableScopes + $scopes = $this->authorizationHelper->getReachableScopes( + $this->security->getUser(), + PersonDocumentVoter::CREATE, + $this->centerResolverManager->resolveCenters($entity) + ); + + if (1 !== count($scopes)) { + throw new LogicException('at this step, it should have only one scope'); + } + + $doc->setScope($scopes[0]); + } + $this->em->persist($doc); } + + private function isScopeNecessary(Person $person): bool + { + if ($this->showScopes && 1 < $this->authorizationHelper->getReachableScopes( + $this->security->getUser(), + PersonDocumentVoter::CREATE, + $this->centerResolverManager->resolveCenters($person) + )) { + return true; + } + + return false; + } } diff --git a/src/Bundle/ChillPersonBundle/Tests/Service/DocGenerator/PersonContextTest.php b/src/Bundle/ChillPersonBundle/Tests/Service/DocGenerator/PersonContextTest.php new file mode 100644 index 000000000..c7c9a00e4 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/Service/DocGenerator/PersonContextTest.php @@ -0,0 +1,265 @@ +setName(['fr' => 'template']); + + $parameter = new ParameterBag(['chill_main' => ['acl' => ['form_show_scopes' => false]]]); + $em = $this->prophesize(EntityManagerInterface::class); + $em->persist(Argument::type(PersonDocument::class)) + ->should(static function ($calls, $object, $method) { + if (1 !== count($calls)) { + throw new FailedPredictionException(sprintf('the persist should be called exactly once, %d receivved', count($calls))); + } + + /** @var PersonDocument $personDocument */ + $personDocument = $calls[0]->getArguments()[0]; + + if (null !== $personDocument->getScope()) { + throw new FailedPredictionException('the person document should not have any scope'); + } + }); + + $personContext = $this->buildPersonContext( + null, + null, + null, + null, + $em->reveal(), + null, + $parameter + ); + + $this->assertFalse($personContext->hasPublicForm($docGen, $person)); + + $personContext->storeGenerated( + $docGen, + new StoredObject(), + $person, + [] + ); + } + + public function testScopeScopeMustBeShownInFormsAndUserAccessMultipleScope() + { + $person = new Person(); + $docGen = (new DocGeneratorTemplate()) + ->setName(['fr' => 'template']); + $scope = new Scope(); + + $em = $this->prophesize(EntityManagerInterface::class); + $em->persist(Argument::type(PersonDocument::class)) + ->should(static function ($calls, $object, $method) use ($scope) { + if (1 !== count($calls)) { + throw new FailedPredictionException(sprintf('the persist should be called exactly once, %d receivved', count($calls))); + } + + /** @var PersonDocument $personDocument */ + $personDocument = $calls[0]->getArguments()[0]; + + if ($personDocument->getScope() !== $scope) { + throw new FailedPredictionException('the person document should show the exactly prepared scope'); + } + }); + + $authorizationHelper = $this->prophesize(AuthorizationHelperInterface::class); + $authorizationHelper->getReachableScopes(Argument::type(UserInterface::class), PersonDocumentVoter::CREATE, Argument::type('array')) + ->willReturn([$scope, new Scope()]); + + $personContext = $this->buildPersonContext( + $authorizationHelper->reveal(), + null, + null, + null, + $em->reveal(), + ); + + $this->assertTrue($personContext->hasPublicForm($docGen, $person)); + + $personContext->storeGenerated( + $docGen, + new StoredObject(), + $person, + ['scope' => $scope] + ); + } + + public function testScopeScopeMustBeShownInFormsAndUserAccessOneScope() + { + $person = new Person(); + $docGen = (new DocGeneratorTemplate()) + ->setName(['fr' => 'template']); + $scope = new Scope(); + + $em = $this->prophesize(EntityManagerInterface::class); + $em->persist(Argument::type(PersonDocument::class)) + ->should(static function ($calls, $object, $method) use ($scope) { + if (1 !== count($calls)) { + throw new FailedPredictionException(sprintf('the persist should be called exactly once, %d receivved', count($calls))); + } + + /** @var PersonDocument $personDocument */ + $personDocument = $calls[0]->getArguments()[0]; + + if ($personDocument->getScope() !== $scope) { + throw new FailedPredictionException('the person document should show the exactly prepared scope'); + } + }); + + $authorizationHelper = $this->prophesize(AuthorizationHelperInterface::class); + $authorizationHelper->getReachableScopes(Argument::type(UserInterface::class), PersonDocumentVoter::CREATE, Argument::type('array')) + ->willReturn([$scope]); + + $personContext = $this->buildPersonContext( + $authorizationHelper->reveal(), + null, + null, + null, + $em->reveal(), + ); + + $this->assertTrue($personContext->hasPublicForm($docGen, $person)); + + $personContext->storeGenerated( + $docGen, + new StoredObject(), + $person, + ['scope' => $scope] + ); + } + + private function buildPersonContext( + ?AuthorizationHelperInterface $authorizationHelper = null, + ?BaseContextData $baseContextData = null, + ?CenterResolverManagerInterface $centerResolverManager = null, + ?DocumentCategoryRepository $documentCategoryRepository = null, + ?EntityManagerInterface $em = null, + ?NormalizerInterface $normalizer = null, + ?ParameterBagInterface $parameterBag = null, + ?Security $security = null, + ?TranslatorInterface $translator = null, + ?TranslatableStringHelperInterface $translatableStringHelper = null + ): PersonContext { + if (null === $authorizationHelper) { + $authorizationHelper = $this->prophesize(AuthorizationHelperInterface::class)->reveal(); + } + + if (null === $baseContextData) { + $baseContextData = $this->prophesize(BaseContextData::class)->reveal(); + } + + if (null === $centerResolverManager) { + $centerResolverManager = $this->prophesize(CenterResolverManagerInterface::class); + $centerResolverManager->resolveCenters(Argument::any(), Argument::any()) + ->willReturn([new Center()]); + $centerResolverManager = $centerResolverManager->reveal(); + } + + if (null === $documentCategoryRepository) { + $documentCategoryRepository = $this->prophesize(DocumentCategoryRepository::class); + $documentCategoryRepository->find(Argument::type('integer'))->willReturn( + new DocumentCategory(PersonDocument::class, 1) + ); + $documentCategoryRepository = $documentCategoryRepository->reveal(); + } + + if (null === $em) { + $em = $this->prophesize(EntityManagerInterface::class)->reveal(); + } + + if (null === $normalizer) { + $normalizer = $this->prophesize(NormalizerInterface::class); + $normalizer->normalize(Argument::type(Person::class), 'docgen', Argument::any()) + ->willReturn(['type' => 'person']); + $normalizer = $normalizer->reveal(); + } + + if (null === $parameterBag) { + $parameterBag = new ParameterBag(['chill_main' => ['acl' => ['form_show_scopes' => true]]]); + } + + if (null === $security) { + $security = $this->prophesize(Security::class); + $security->getUser()->willReturn(new User()); + $security = $security->reveal(); + } + + if (null === $translator) { + $translator = $this->prophesize(TranslatorInterface::class)->reveal(); + } + + if (null === $translatableStringHelper) { + $translatableStringHelper = $this->prophesize(TranslatableStringHelperInterface::class); + // return only the 'fr' key + $translatableStringHelper->localize(Argument::type('array'))->will(static function ($args) { + return $args[0]['fr']; + }); + $translatableStringHelper = $translatableStringHelper->reveal(); + } + + return new PersonContext( + $authorizationHelper, + $baseContextData, + $centerResolverManager, + $documentCategoryRepository, + $em, + $normalizer, + $parameterBag, + $security, + $translator, + $translatableStringHelper + ); + } +}