mirror of
				https://gitlab.com/Chill-Projet/chill-bundles.git
				synced 2025-10-31 17:28:23 +00:00 
			
		
		
		
	WIP: fix loading of activity document
This commit is contained in:
		| @@ -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); | ||||
| @@ -0,0 +1,28 @@ | ||||
| <?php | ||||
|  | ||||
| namespace Chill\ActivityBundle\Repository; | ||||
|  | ||||
| use Chill\DocStoreBundle\GenericDoc\FetchQuery; | ||||
| use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface; | ||||
| use Chill\PersonBundle\Entity\Person; | ||||
| use DateTimeImmutable; | ||||
|  | ||||
| /** | ||||
|  * Gives queries usable for fetching documents, with ACL aware | ||||
|  */ | ||||
| interface ActivityDocumentACLAwareRepositoryInterface | ||||
| { | ||||
|     /** | ||||
|      * Return a fetch query for querying document's activities for a person | ||||
|      * | ||||
|      * This method must check the rights to see a document: the user must be allowed to see the given activities | ||||
|      */ | ||||
|     public function buildFetchQueryActivityDocumentLinkedToPersonFromPersonContext(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQueryInterface; | ||||
|  | ||||
|     /** | ||||
|      * Return a fetch query for querying document's activities for an activity in accompanying periods, but for a given person | ||||
|      * | ||||
|      * This method must check the rights to see a document: the user must be allowed to see the given accompanying periods | ||||
|      */ | ||||
|     public function buildFetchQueryActivityDocumentLinkedToAccompanyingPeriodFromPersonContext(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery; | ||||
| } | ||||
| @@ -15,7 +15,6 @@ use Chill\ActivityBundle\Entity\Activity; | ||||
| use Chill\PersonBundle\Entity\AccompanyingPeriod; | ||||
| use Chill\PersonBundle\Entity\Person; | ||||
| use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; | ||||
| use Doctrine\ORM\Query\Expr; | ||||
| use Doctrine\Persistence\ManagerRegistry; | ||||
|  | ||||
| /** | ||||
|   | ||||
| @@ -12,25 +12,30 @@ declare(strict_types=1); | ||||
| namespace Chill\ActivityBundle\Service\GenericDoc\Providers; | ||||
|  | ||||
| use Chill\ActivityBundle\Entity\Activity; | ||||
| use Chill\ActivityBundle\Repository\ActivityDocumentACLAwareRepositoryInterface; | ||||
| use Chill\ActivityBundle\Security\Authorization\ActivityVoter; | ||||
| use Chill\DocStoreBundle\Entity\StoredObject; | ||||
| use Chill\DocStoreBundle\GenericDoc\FetchQuery; | ||||
| use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface; | ||||
| use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface; | ||||
| use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface; | ||||
| use Chill\PersonBundle\Entity\AccompanyingPeriod; | ||||
| use Chill\PersonBundle\Entity\Person; | ||||
| use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter; | ||||
| use DateTimeImmutable; | ||||
| use Doctrine\DBAL\Types\Types; | ||||
| use Doctrine\ORM\EntityManagerInterface; | ||||
| use Doctrine\ORM\Mapping\MappingException; | ||||
| use Symfony\Component\Security\Core\Security; | ||||
|  | ||||
| final class AccompanyingPeriodActivityGenericDocProvider implements GenericDocForAccompanyingPeriodProviderInterface | ||||
| final class AccompanyingPeriodActivityGenericDocProvider implements GenericDocForAccompanyingPeriodProviderInterface, GenericDocForPersonProviderInterface | ||||
| { | ||||
|     public const KEY = 'accompanying_period_activity_document'; | ||||
|  | ||||
|     public function __construct( | ||||
|         private EntityManagerInterface $em, | ||||
|         private Security $security | ||||
|         private Security $security, | ||||
|         private ActivityDocumentACLAwareRepositoryInterface $activityDocumentACLAwareRepository, | ||||
|     ){ | ||||
|     } | ||||
|  | ||||
| @@ -95,4 +100,15 @@ final class AccompanyingPeriodActivityGenericDocProvider implements GenericDocFo | ||||
|     { | ||||
|         return $this->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); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -0,0 +1,113 @@ | ||||
| <?php | ||||
|  | ||||
| namespace Chill\ActivityBundle\Tests\Repository; | ||||
|  | ||||
| use Chill\ActivityBundle\Repository\ActivityDocumentACLAwareRepository; | ||||
| use Chill\ActivityBundle\Security\Authorization\ActivityVoter; | ||||
| use Chill\DocStoreBundle\GenericDoc\FetchQueryToSqlBuilder; | ||||
| 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 Doctrine\ORM\EntityManagerInterface; | ||||
| use phpseclib3\Math\BinaryField; | ||||
| use Prophecy\Argument; | ||||
| use Prophecy\PhpUnit\ProphecyTrait; | ||||
| use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; | ||||
| use Symfony\Component\Security\Core\Security; | ||||
|  | ||||
| class ActivityDocumentACLAwareRepositoryTest extends KernelTestCase | ||||
| { | ||||
|     use ProphecyTrait; | ||||
|  | ||||
|     private EntityManagerInterface $entityManager; | ||||
|  | ||||
|     private CenterResolverManagerInterface $centerResolverManager; | ||||
|  | ||||
|     private AuthorizationHelperForCurrentUserInterface $authorizationHelperForCurrentUser; | ||||
|  | ||||
|     private Security $security; | ||||
|  | ||||
|     protected function setUp(): void | ||||
|     { | ||||
|         self::bootKernel(); | ||||
|         $this->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"]; | ||||
|     } | ||||
|  | ||||
| } | ||||
		Reference in New Issue
	
	Block a user