From 727e9d0f74fa53818ca0cadd2f9da1470e1e91b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 12 Jun 2023 17:58:27 +0200 Subject: [PATCH] WIP: fix loading of activity document --- ...=> ActivityDocumentACLAwareRepository.php} | 113 +++++++----------- ...ityDocumentACLAwareRepositoryInterface.php | 28 +++++ .../Repository/ActivityRepository.php | 1 - ...anyingPeriodActivityGenericDocProvider.php | 20 +++- .../PersonActivityGenericDocProvider.php | 19 ++- ...ActivityDocumentACLAwareRepositoryTest.php | 113 ++++++++++++++++++ 6 files changed, 209 insertions(+), 85 deletions(-) rename src/Bundle/ChillActivityBundle/Repository/{PersonActivityDocumentACLAwareRepository.php => ActivityDocumentACLAwareRepository.php} (69%) create mode 100644 src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepositoryInterface.php create mode 100644 src/Bundle/ChillActivityBundle/Tests/Repository/ActivityDocumentACLAwareRepositoryTest.php diff --git a/src/Bundle/ChillActivityBundle/Repository/PersonActivityDocumentACLAwareRepository.php b/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepository.php similarity index 69% rename from src/Bundle/ChillActivityBundle/Repository/PersonActivityDocumentACLAwareRepository.php rename to src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepository.php index 84bb08fb4..f3f61ad3d 100644 --- a/src/Bundle/ChillActivityBundle/Repository/PersonActivityDocumentACLAwareRepository.php +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepository.php @@ -23,16 +23,18 @@ use Chill\DocStoreBundle\Security\Authorization\PersonDocumentVoter; use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Security\Authorization\AuthorizationHelperForCurrentUserInterface; use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface; +use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodWorkVoter; use DateTimeImmutable; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\Mapping\MappingException; use Doctrine\ORM\QueryBuilder; use Symfony\Component\HttpKernel\HttpCache\Store; use Symfony\Component\Security\Core\Security; -class PersonActivityDocumentACLAwareRepository implements PersonDocumentACLAwareRepositoryInterface +final readonly class ActivityDocumentACLAwareRepository implements ActivityDocumentACLAwareRepositoryInterface { public function __construct( private EntityManagerInterface $em, @@ -42,26 +44,43 @@ class PersonActivityDocumentACLAwareRepository implements PersonDocumentACLAware { } - public function buildQueryByPerson(Person $person): QueryBuilder + public function buildFetchQueryActivityDocumentLinkedToPersonFromPersonContext(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQueryInterface { - $qb = $this->em->getRepository(PersonDocument::class)->createQueryBuilder('d'); - - $qb - ->where($qb->expr()->eq('d.person', ':person')) - ->setParameter('person', $person); - - return $qb; - } - - - public function buildFetchQueryForPerson(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQueryInterface - { - $query = $this->buildBaseFetchQueryForPerson($person, $startDate, $endDate, $content); + $query = $this->buildBaseFetchQueryActivityDocumentLinkedToPersonFromPersonContext($person, $startDate, $endDate, $content); return $this->addFetchQueryByPersonACL($query, $person); } - public function buildBaseFetchQueryForPerson(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery + public function buildBaseFetchQueryActivityDocumentLinkedToPersonFromPersonContext(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery + { + $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class); + $activityMetadata = $this->em->getClassMetadata(Activity::class); + + $query = new FetchQuery( + PersonDocumentGenericDocProvider::KEY, + sprintf('jsonb_build_object(\'id\', stored_obj.%s, \'activity_id\', activity.%s)', $storedObjectMetadata->getSingleIdentifierColumnName(), $activityMetadata->getSingleIdentifierColumnName()), + sprintf('stored_obj.%s', $storedObjectMetadata->getColumnName('createdAt')), + sprintf('%s AS stored_obj', $storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName()) + ); + + $query->addJoinClause( + 'JOIN public.activity_storedobject activity_doc ON activity_doc.storedobject_id = stored_obj.id' + ); + + $query->addJoinClause( + 'JOIN public.activity activity ON activity.id = activity_doc.activity_id' + ); + + $query->addWhereClause( + sprintf('activity.%s = ?', $activityMetadata->getSingleAssociationJoinColumnName('person')), + [$person->getId()], + [Types::INTEGER] + ); + + return $this->addWhereClauses($query, $startDate, $endDate, $content); + } + + public function buildFetchQueryActivityDocumentLinkedToAccompanyingPeriodFromPersonContext(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery { $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class); $activityMetadata = $this->em->getClassMetadata(Activity::class); @@ -109,11 +128,12 @@ class PersonActivityDocumentACLAwareRepository implements PersonDocumentACLAware $query->addWhereClause(sprintf('(%s)', implode(' OR ', $or)), $orParams, $orTypes); -/* $query->addWhereClause( - 'activity.person_id = ?', - [$person->getId()], - [Types::INTEGER] - );*/ + return $this->addWhereClauses($query, $startDate, $endDate, $content); + } + + private function addWhereClauses(FetchQuery $query, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery + { + $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class); if (null !== $startDate) { $query->addWhereClause( @@ -131,7 +151,7 @@ class PersonActivityDocumentACLAwareRepository implements PersonDocumentACLAware ); } - if (null !== $content) { + if (null !== $content or '' !== $content) { $query->addWhereClause( 'stored_obj.title ilike ?', ['%' . $content . '%'], @@ -142,55 +162,6 @@ class PersonActivityDocumentACLAwareRepository implements PersonDocumentACLAware return $query; } - public function countByPerson(Person $person): int - { - $qb = $this->buildQueryByPerson($person)->select('COUNT(d)'); - - $this->addACL($qb, $person); - - return $qb->getQuery()->getSingleScalarResult(); - } - - public function findByPerson(Person $person, array $orderBy = [], int $limit = 20, int $offset = 0): array - { - $qb = $this->buildQueryByPerson($person)->select('d'); - - $this->addACL($qb, $person); - - foreach ($orderBy as $field => $order) { - $qb->addOrderBy('d.' . $field, $order); - } - - $qb->setFirstResult($offset)->setMaxResults($limit); - - return $qb->getQuery()->getResult(); - } - - private function addACL(QueryBuilder $qb, Person $person): void - { - $reachableScopes = []; - - foreach ($this->centerResolverManager->resolveCenters($person) as $center) { - $reachableScopes = [ - ...$reachableScopes, - ...$this->authorizationHelperForCurrentUser - ->getReachableScopes( - ActivityVoter::SEE, - $center - ) - ]; - } - - if ([] === $reachableScopes) { - $qb->andWhere("'FALSE' = 'TRUE'"); - - return; - } - - $qb->andWhere($qb->expr()->in('d.scope', ':scopes')) - ->setParameter('scopes', $reachableScopes); - } - private function addFetchQueryByPersonACL(FetchQuery $fetchQuery, Person $person): FetchQuery { $activityMetadata = $this->em->getClassMetadata(Activity::class); diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepositoryInterface.php b/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepositoryInterface.php new file mode 100644 index 000000000..eb9e82cbe --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepositoryInterface.php @@ -0,0 +1,28 @@ +security->isGranted(ActivityVoter::SEE, $accompanyingPeriod); } + + public function isAllowedForPerson(Person $person): bool + { + return $this->security->isGranted(AccompanyingPeriodVoter::SEE, $person); + } + + public function buildFetchQueryForPerson(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface + { + return $this->activityDocumentACLAwareRepository + ->buildFetchQueryActivityDocumentLinkedToAccompanyingPeriodFromPersonContext($person, $startDate, $endDate, $content); + } } diff --git a/src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/PersonActivityGenericDocProvider.php b/src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/PersonActivityGenericDocProvider.php index d00a23e79..b1bd1d712 100644 --- a/src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/PersonActivityGenericDocProvider.php +++ b/src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/PersonActivityGenericDocProvider.php @@ -11,7 +11,8 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Service\GenericDoc\Providers; -use Chill\ActivityBundle\Repository\PersonActivityDocumentACLAwareRepository; +use Chill\ActivityBundle\Repository\ActivityDocumentACLAwareRepository; +use Chill\ActivityBundle\Repository\ActivityDocumentACLAwareRepositoryInterface; use Chill\ActivityBundle\Security\Authorization\ActivityVoter; use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface; use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface; @@ -21,24 +22,20 @@ use DateTimeImmutable; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Security\Core\Security; -final class PersonActivityGenericDocProvider implements GenericDocForPersonProviderInterface +final readonly class PersonActivityGenericDocProvider implements GenericDocForPersonProviderInterface { public const KEY = 'person_activity_document'; - private Security $security; - - private PersonActivityDocumentACLAwareRepository $personActivityDocumentACLAwareRepository; - - public function __construct(Security $security, PersonActivityDocumentACLAwareRepository $personActivityDocumentACLAwareRepository -) + public function __construct( + private Security $security, + private ActivityDocumentACLAwareRepositoryInterface $personActivityDocumentACLAwareRepository, + ) { - $this->security = $security; - $this->personActivityDocumentACLAwareRepository = $personActivityDocumentACLAwareRepository; } public function buildFetchQueryForPerson(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface { - return $this->personActivityDocumentACLAwareRepository->buildFetchQueryForPerson( + return $this->personActivityDocumentACLAwareRepository->buildFetchQueryActivityDocumentLinkedToPersonFromPersonContext( $person, $startDate, $endDate, diff --git a/src/Bundle/ChillActivityBundle/Tests/Repository/ActivityDocumentACLAwareRepositoryTest.php b/src/Bundle/ChillActivityBundle/Tests/Repository/ActivityDocumentACLAwareRepositoryTest.php new file mode 100644 index 000000000..7390963fc --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Tests/Repository/ActivityDocumentACLAwareRepositoryTest.php @@ -0,0 +1,113 @@ +entityManager = self::$container->get(EntityManagerInterface::class); + $this->centerResolverManager = self::$container->get(CenterResolverManagerInterface::class); + $this->authorizationHelperForCurrentUser = self::$container->get(AuthorizationHelperForCurrentUserInterface::class); + $this->security = self::$container->get(Security::class); + } + + /** + * @dataProvider provideDataForPerson + */ + public function testBuildFetchQueryActivityDocumentLinkedToPersonFromPersonContext(Person $person, array $reachableScopes, bool $_unused, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate, ?string $content): void + { + $authorizationHelper = $this->prophesize(AuthorizationHelperForCurrentUserInterface::class); + $authorizationHelper->getReachableScopes(ActivityVoter::SEE, Argument::any()) + ->willReturn($reachableScopes); + + $repository = new ActivityDocumentACLAwareRepository( + $this->entityManager, + $this->centerResolverManager, + $authorizationHelper->reveal(), + $this->security + ); + + $query = $repository->buildFetchQueryActivityDocumentLinkedToPersonFromPersonContext($person, $startDate, $endDate, $content); + ['sql' => $sql, 'params' => $params, 'types' => $types] = (new FetchQueryToSqlBuilder())->toSql($query); + + $nb = $this->entityManager->getConnection()->fetchOne("SELECT COUNT(*) FROM ({$sql}) sq", $params, $types); + + self::assertIsInt($nb); + } + + /** + * @dataProvider provideDataForPerson + */ + public function testBuildFetchQueryActivityDocumentLinkedToAccompanyingPeriodFromPersonContext(Person $person, array $_unused, bool $canSeePeriod, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate, ?string $content): void + { + $security = $this->prophesize(Security::class); + $security->isGranted(ActivityVoter::SEE, Argument::type(AccompanyingPeriod::class)) + ->willReturn($canSeePeriod); + + $repository = new ActivityDocumentACLAwareRepository( + $this->entityManager, + $this->centerResolverManager, + $this->authorizationHelperForCurrentUser, + $security->reveal() + ); + + $query = $repository->buildFetchQueryActivityDocumentLinkedToAccompanyingPeriodFromPersonContext($person, $startDate, $endDate, $content); + + ['sql' => $sql, 'params' => $params, 'types' => $types] = (new FetchQueryToSqlBuilder())->toSql($query); + + $nb = $this->entityManager->getConnection()->fetchOne("SELECT COUNT(*) FROM ({$sql}) sq", $params, $types); + + self::assertIsInt($nb); + } + + public function provideDataForPerson(): iterable + { + $this->setUp(); + + if (null === $person = $this->entityManager->createQuery("SELECT p FROM " . Person::class . " p WHERE SIZE(p.accompanyingPeriodParticipations) > 0 ") + ->setMaxResults(1) + ->getSingleResult()) { + throw new \RuntimeException("no person in dtabase"); + } + + if ([] === $scopes = $this->entityManager->createQuery("SELECT s FROM " . Scope::class . " s ")->setMaxResults(5)->getResult()) { + throw new \RuntimeException("no scopes in database"); + } + + yield [$person, [], true, null, null, null]; + yield [$person, $scopes, true, null, null, null]; + yield [$person, $scopes, true, new \DateTimeImmutable("1 month ago"), null, null]; + yield [$person, $scopes, true, new \DateTimeImmutable("1 month ago"), new \DateTimeImmutable("1 week ago"), null]; + yield [$person, $scopes, true, new \DateTimeImmutable("1 month ago"), new \DateTimeImmutable("1 week ago"), "content"]; + yield [$person, $scopes, true, null, new \DateTimeImmutable("1 week ago"), "content"]; + yield [$person, [], true, new \DateTimeImmutable("1 month ago"), new \DateTimeImmutable("1 week ago"), "content"]; + } + +}