From 4456fb3749a78544451f6561605dd6db5a3b26c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 13 Jun 2023 11:01:40 +0200 Subject: [PATCH] Fixing generic doc providers from calendar + fix cs --- .../ActivityDocumentACLAwareRepository.php | 4 +- ...ityDocumentACLAwareRepositoryInterface.php | 9 ++ ...anyingPeriodActivityGenericDocProvider.php | 2 +- .../PersonActivityGenericDocProvider.php | 3 +- ...anyingPeriodActivityGenericDocRenderer.php | 1 - ...ActivityDocumentACLAwareRepositoryTest.php | 13 ++ ...anyingPeriodCalendarGenericDocProvider.php | 113 +++++++++++++-- .../PersonCalendarGenericDocProvider.php | 51 +++---- .../AccompanyingCourseDocumentProvider.php | 54 +++++++ ...ngPeriodCalendarGenericDocProviderTest.php | 134 ++++++++++++++++++ .../PersonCalendarGenericDocProviderTest.php | 70 +++++++++ 11 files changed, 401 insertions(+), 53 deletions(-) create mode 100644 src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/AccompanyingCourseDocumentProvider.php create mode 100644 src/Bundle/ChillPersonBundle/Tests/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProviderTest.php create mode 100644 src/Bundle/ChillPersonBundle/Tests/Service/GenericDoc/Providers/PersonCalendarGenericDocProviderTest.php diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepository.php b/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepository.php index f3f61ad3d..180d7fc4a 100644 --- a/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepository.php +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepository.php @@ -40,8 +40,8 @@ final readonly class ActivityDocumentACLAwareRepository implements ActivityDocum private EntityManagerInterface $em, private CenterResolverManagerInterface $centerResolverManager, private AuthorizationHelperForCurrentUserInterface $authorizationHelperForCurrentUser, - private Security $security) - { + private Security $security + ) { } public function buildFetchQueryActivityDocumentLinkedToPersonFromPersonContext(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQueryInterface diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepositoryInterface.php b/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepositoryInterface.php index eb9e82cbe..9f4a9c0f8 100644 --- a/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepositoryInterface.php +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepositoryInterface.php @@ -1,5 +1,14 @@ getSchemaName().'.'.$classMetadata->getTableName().' AS cd' ); $query->addJoinClause( - sprintf('JOIN %s doc_store ON doc_store.%s = cd.%s', - $storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName(), - $storedObjectMetadata->getColumnName('id'), - $classMetadata->getSingleAssociationJoinColumnName('storedObject') - )); + sprintf( + 'JOIN %s doc_store ON doc_store.%s = cd.%s', + $storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName(), + $storedObjectMetadata->getColumnName('id'), + $classMetadata->getSingleAssociationJoinColumnName('storedObject') + ) + ); $query->addJoinClause( - sprintf('JOIN %s calendar ON calendar.%s = cd.%s', + sprintf( + 'JOIN %s calendar ON calendar.%s = cd.%s', $calendarMetadata->getSchemaName().'.'.$calendarMetadata->getTableName(), $calendarMetadata->getColumnName('id'), $classMetadata->getSingleAssociationJoinColumnName('calendar') @@ -65,12 +76,91 @@ final class AccompanyingPeriodCalendarGenericDocProvider implements GenericDocFo ); $query->addWhereClause( - sprintf('calendar.%s = ?', - $calendarMetadata->getAssociationMapping('accompanyingPeriod')['joinColumns'][0]['name']), + sprintf( + 'calendar.%s = ?', + $calendarMetadata->getAssociationMapping('accompanyingPeriod')['joinColumns'][0]['name'] + ), [$accompanyingPeriod->getId()], [Types::INTEGER] ); + return $query; + } + + public function isAllowedForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool + { + return $this->security->isGranted(CalendarVoter::SEE, $accompanyingPeriod); + } + + public function buildFetchQueryForPerson(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface + { + $classMetadata = $this->em->getClassMetadata(CalendarDoc::class); + $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class); + $calendarMetadata = $this->em->getClassMetadata(Calendar::class); + + $query = new FetchQuery( + self::KEY, + sprintf("jsonb_build_object('id', cd.%s)", $classMetadata->getColumnName('id')), + 'cd.'.$storedObjectMetadata->getColumnName('createdAt'), + $classMetadata->getSchemaName().'.'.$classMetadata->getTableName().' AS cd' + ); + $query->addJoinClause( + sprintf( + 'JOIN %s doc_store ON doc_store.%s = cd.%s', + $storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName(), + $storedObjectMetadata->getColumnName('id'), + $classMetadata->getSingleAssociationJoinColumnName('storedObject') + ) + ); + + $query->addJoinClause( + sprintf( + 'JOIN %s calendar ON calendar.%s = cd.%s', + $calendarMetadata->getSchemaName().'.'.$calendarMetadata->getTableName(), + $calendarMetadata->getColumnName('id'), + $classMetadata->getSingleAssociationJoinColumnName('calendar') + ) + ); + + // get the documents associated with accompanying periods in which person participates + $or = []; + $orParams = []; + $orTypes = []; + foreach ($person->getAccompanyingPeriodParticipations() as $participation) { + if (!$this->security->isGranted(CalendarVoter::SEE, $participation->getAccompanyingPeriod())) { + continue; + } + + $or[] = sprintf( + '(calendar.%s = ? AND cd.%s BETWEEN ?::date AND COALESCE(?::date, \'infinity\'::date))', + $calendarMetadata->getSingleAssociationJoinColumnName('accompanyingPeriod'), + $storedObjectMetadata->getColumnName('createdAt') + ); + $orParams = [...$orParams, $participation->getAccompanyingPeriod()->getId(), + DateTimeImmutable::createFromInterface($participation->getStartDate()), + null === $participation->getEndDate() ? null : DateTimeImmutable::createFromInterface($participation->getEndDate())]; + $orTypes = [...$orTypes, Types::INTEGER, Types::DATE_IMMUTABLE, Types::DATE_IMMUTABLE]; + } + + if ([] === $or) { + $query->addWhereClause('TRUE = FALSE'); + + return $query; + } + return $this->addWhereClausesToQuery($query, $startDate, $endDate, $content); + } + + public function isAllowedForPerson(Person $person): bool + { + // check that the person is allowed to see an accompanying period. If yes, the + // ACL on each accompanying period will be checked when the query is build + return $this->security->isGranted(AccompanyingPeriodVoter::SEE, $person); + } + + private function addWhereClausesToQuery(FetchQuery $query, ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate, ?string $content): FetchQuery + { + $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class); + if (null !== $startDate) { $query->addWhereClause( sprintf('doc_store.%s >= ?', $storedObjectMetadata->getColumnName('createdAt')), @@ -98,10 +188,5 @@ final class AccompanyingPeriodCalendarGenericDocProvider implements GenericDocFo return $query; } - public function isAllowedForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool - { - return $this->security->isGranted(CalendarVoter::SEE, $accompanyingPeriod); - } - } diff --git a/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/PersonCalendarGenericDocProvider.php b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/PersonCalendarGenericDocProvider.php index 640f0d197..f5d4b3cbb 100644 --- a/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/PersonCalendarGenericDocProvider.php +++ b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/PersonCalendarGenericDocProvider.php @@ -24,9 +24,15 @@ use DateTimeImmutable; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\MappingException; +use Service\GenericDoc\Providers\PersonCalendarGenericDocProviderTest; use Symfony\Component\Security\Core\Security; -final class PersonCalendarGenericDocProvider implements GenericDocForPersonProviderInterface +/** + * Provide calendar documents for calendar associated to persons + * + * @see PersonCalendarGenericDocProviderTest + */ +final readonly class PersonCalendarGenericDocProvider implements GenericDocForPersonProviderInterface { public const KEY = 'person_calendar_document'; @@ -36,7 +42,6 @@ final class PersonCalendarGenericDocProvider implements GenericDocForPersonProvi ) { } - private function addWhereClausesToQuery(FetchQuery $query, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery { $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class); @@ -84,50 +89,30 @@ final class PersonCalendarGenericDocProvider implements GenericDocForPersonProvi $classMetadata->getSchemaName().'.'.$classMetadata->getTableName().' AS cd' ); $query->addJoinClause( - sprintf('JOIN %s doc_store ON doc_store.%s = cd.%s', + sprintf( + 'JOIN %s doc_store ON doc_store.%s = cd.%s', $storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName(), $storedObjectMetadata->getColumnName('id'), $classMetadata->getSingleAssociationJoinColumnName('storedObject') - )); + ) + ); $query->addJoinClause( - sprintf('JOIN %s calendar ON calendar.%s = cd.%s', + sprintf( + 'JOIN %s calendar ON calendar.%s = cd.%s', $calendarMetadata->getSchemaName().'.'.$calendarMetadata->getTableName(), $calendarMetadata->getColumnName('id'), $classMetadata->getSingleAssociationJoinColumnName('calendar') ) ); - // get the documents associated with accompanying periods in which person participates - $or = []; - $orParams = []; - $orTypes = []; - foreach ($person->getAccompanyingPeriodParticipations() as $participation) { - if (!$this->security->isGranted(CalendarVoter::SEE, $participation->getAccompanyingPeriod())) { - continue; - } - - $or[] = sprintf( - '(calendar.%s = ? AND cd.%s BETWEEN ?::date AND COALESCE(?::date, \'infinity\'::date))', - $calendarMetadata->getSingleAssociationJoinColumnName('accompanyingPeriod'), - $storedObjectMetadata->getColumnName('createdAt') - ); - $orParams = [...$orParams, $participation->getAccompanyingPeriod()->getId(), - DateTimeImmutable::createFromInterface($participation->getStartDate()), - null === $participation->getEndDate() ? null : DateTimeImmutable::createFromInterface($participation->getEndDate())]; - $orTypes = [...$orTypes, Types::INTEGER, Types::DATE_IMMUTABLE, Types::DATE_IMMUTABLE]; - } - - if ([] === $or) { - $query->addWhereClause('TRUE = FALSE'); - - return $query; - } - -// $query->addWhereClause(sprintf('(%s)', implode(' OR ', $or)), $orParams, $orTypes); + $query->addWhereClause( + sprintf('calendar.%s = ?', $calendarMetadata->getSingleAssociationJoinColumnName('person')), + [$person->getId()], + [Types::INTEGER] + ); return $this->addWhereClausesToQuery($query, $startDate, $endDate, $content); - } /** diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/AccompanyingCourseDocumentProvider.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/AccompanyingCourseDocumentProvider.php new file mode 100644 index 000000000..163d17458 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/AccompanyingCourseDocumentProvider.php @@ -0,0 +1,54 @@ +entityManager->getClassMetadata(AccompanyingCourseDocument::class); + + $query = new FetchQuery( + 'accompanying_course_document', + sprintf('jsonb_build_object(\'id\', %s)', $classMetadata->getIdentifierColumnNames()[0]), + sprintf($classMetadata->getColumnName('date')), + $classMetadata->getSchemaName() . '.' . $classMetadata->getTableName() + ); + + $query->addWhereClause( + sprintf('%s = ?', $classMetadata->getSingleAssociationJoinColumnName('course')), + [$accompanyingPeriod->getId()] + ); + + return $query; + } + + public function isAllowedForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool + { + return $this->security->isGranted(AccompanyingCourseDocumentVoter::SEE, $accompanyingPeriod); + } +} diff --git a/src/Bundle/ChillPersonBundle/Tests/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProviderTest.php b/src/Bundle/ChillPersonBundle/Tests/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProviderTest.php new file mode 100644 index 000000000..8aff23673 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProviderTest.php @@ -0,0 +1,134 @@ +security = self::$container->get(Security::class); + $this->entityManager = self::$container->get(EntityManagerInterface::class); + } + + /** + * @dataProvider provideDataForAccompanyingPeriod + */ + public function testBuildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate, ?string $content): void + { + $provider = new AccompanyingPeriodCalendarGenericDocProvider($this->security, $this->entityManager); + + $query = $provider->buildFetchQueryForAccompanyingPeriod($accompanyingPeriod, $startDate, $endDate, $content); + + ['sql' => $sql, 'params' => $params, 'types' => $types] = (new FetchQueryToSqlBuilder())->toSql($query); + + $nb = $this->entityManager->getConnection()->fetchOne("SELECT COUNT(*) FROM ({$sql}) AS sq", $params, $types); + + self::assertIsInt($nb); + } + + /** + * @dataProvider provideDataForPerson + */ + public function testBuildFetchQueryForPerson(Person $person, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate, ?string $content): void + { + $security = $this->prophesize(Security::class); + $security->isGranted(CalendarVoter::SEE, Argument::any())->willReturn(true); + + $provider = new AccompanyingPeriodCalendarGenericDocProvider($security->reveal(), $this->entityManager); + + $query = $provider->buildFetchQueryForPerson($person, $startDate, $endDate, $content); + + ['sql' => $sql, 'params' => $params, 'types' => $types] = (new FetchQueryToSqlBuilder())->toSql($query); + + $nb = $this->entityManager->getConnection()->fetchOne("SELECT COUNT(*) FROM ({$sql}) AS sq", $params, $types); + + self::assertIsInt($nb); + self::assertStringNotContainsStringIgnoringCase('FALSE = TRUE', $sql); + self::assertStringNotContainsStringIgnoringCase('TRUE = FALSE', $sql); + } + + /** + * @dataProvider provideDataForPerson + */ + public function testBuildFetchQueryForPersonWithoutAnyRight(Person $person, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate, ?string $content): void + { + $security = $this->prophesize(Security::class); + $security->isGranted(CalendarVoter::SEE, Argument::any())->willReturn(false); + + $provider = new AccompanyingPeriodCalendarGenericDocProvider($security->reveal(), $this->entityManager); + + $query = $provider->buildFetchQueryForPerson($person, $startDate, $endDate, $content); + + ['sql' => $sql, 'params' => $params, 'types' => $types] = (new FetchQueryToSqlBuilder())->toSql($query); + + $nb = $this->entityManager->getConnection()->fetchOne("SELECT COUNT(*) FROM ({$sql}) AS sq", $params, $types); + + self::assertIsInt($nb); + self::assertStringContainsStringIgnoringCase('TRUE = FALSE', $sql); + } + + 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("There is no person"); + } + + yield [$person, null, null, null]; + yield [$person, new \DateTimeImmutable("1 year ago"), null, null]; + yield [$person, new \DateTimeImmutable("1 year ago"), new \DateTimeImmutable("6 month ago"), null]; + yield [$person, new \DateTimeImmutable("1 year ago"), new \DateTimeImmutable("6 month ago"), "text"]; + yield [$person, null, null, "text"]; + yield [$person, null, new \DateTimeImmutable("6 month ago"), null]; + } + + public function provideDataForAccompanyingPeriod(): iterable + { + $this->setUp(); + + if (null === $period = $this->entityManager->createQuery("SELECT p FROM " . AccompanyingPeriod::class . " p ") + ->setMaxResults(1)->getSingleResult()) { + throw new \RuntimeException("There is no accompanying period"); + } + + yield [$period, null, null, null]; + yield [$period, new \DateTimeImmutable("1 year ago"), null, null]; + yield [$period, new \DateTimeImmutable("1 year ago"), new \DateTimeImmutable("6 month ago"), null]; + yield [$period, new \DateTimeImmutable("1 year ago"), new \DateTimeImmutable("6 month ago"), "text"]; + yield [$period, null, null, "text"]; + yield [$period, null, new \DateTimeImmutable("6 month ago"), null]; + } +} diff --git a/src/Bundle/ChillPersonBundle/Tests/Service/GenericDoc/Providers/PersonCalendarGenericDocProviderTest.php b/src/Bundle/ChillPersonBundle/Tests/Service/GenericDoc/Providers/PersonCalendarGenericDocProviderTest.php new file mode 100644 index 000000000..dbad052ca --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/Service/GenericDoc/Providers/PersonCalendarGenericDocProviderTest.php @@ -0,0 +1,70 @@ +security = self::$container->get(Security::class); + $this->entityManager = self::$container->get(EntityManagerInterface::class); + } + + /** + * @dataProvider provideDataForPerson + */ + public function testBuildFetchQueryForPerson(Person $person, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate, ?string $content): void + { + $provider = new PersonCalendarGenericDocProvider($this->security, $this->entityManager); + + $query = $provider->buildFetchQueryForPerson($person, $startDate, $endDate, $content); + + ['sql' => $sql, 'params' => $params, 'types' => $types] = (new FetchQueryToSqlBuilder())->toSql($query); + + $nb = $this->entityManager->getConnection()->fetchOne("SELECT COUNT(*) FROM ({$sql}) AS sq", $params, $types); + + self::assertIsInt($nb); + } + + public function provideDataForPerson(): iterable + { + $this->setUp(); + + if (null === $person = $this->entityManager->createQuery("SELECT p FROM " . Person::class . " p ") + ->setMaxResults(1)->getSingleResult()) { + throw new \RuntimeException("There is no person"); + } + + yield [$person, null, null, null]; + yield [$person, new \DateTimeImmutable("1 year ago"), null, null]; + yield [$person, new \DateTimeImmutable("1 year ago"), new \DateTimeImmutable("6 month ago"), null]; + yield [$person, new \DateTimeImmutable("1 year ago"), new \DateTimeImmutable("6 month ago"), "text"]; + yield [$person, null, null, "text"]; + yield [$person, null, new \DateTimeImmutable("6 month ago"), null]; + } +}