From 54606403b4051ad57ee907a4ad56ce33dd643ad5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 7 Dec 2023 15:13:54 +0100 Subject: [PATCH] Refactor confidential filtering on list to let it usable by other lists --- .../Export/Export/ListAccompanyingPeriod.php | 82 +----------- .../FilterListAccompanyingPeriodHelper.php | 105 +++++++++++++++ ...rListAccompanyingPeriodHelperInterface.php | 23 ++++ .../Export/ListAccompanyingPeriodTest.php | 22 +-- ...FilterListAccompanyingPeriodHelperTest.php | 125 ++++++++++++++++++ 5 files changed, 271 insertions(+), 86 deletions(-) create mode 100644 src/Bundle/ChillPersonBundle/Export/Helper/FilterListAccompanyingPeriodHelper.php create mode 100644 src/Bundle/ChillPersonBundle/Export/Helper/FilterListAccompanyingPeriodHelperInterface.php create mode 100644 src/Bundle/ChillPersonBundle/Tests/Export/Helper/FilterListAccompanyingPeriodHelperTest.php diff --git a/src/Bundle/ChillPersonBundle/Export/Export/ListAccompanyingPeriod.php b/src/Bundle/ChillPersonBundle/Export/Export/ListAccompanyingPeriod.php index ef729642d..7d0e52169 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/ListAccompanyingPeriod.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/ListAccompanyingPeriod.php @@ -11,44 +11,30 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Export; -use Chill\MainBundle\Entity\User; use Chill\MainBundle\Export\AccompanyingCourseExportHelper; use Chill\MainBundle\Export\FormatterInterface; use Chill\MainBundle\Export\GroupedExportInterface; use Chill\MainBundle\Export\ListInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; -use Chill\MainBundle\Repository\CenterRepositoryInterface; -use Chill\MainBundle\Security\Authorization\AuthorizationHelperForCurrentUserInterface; use Chill\MainBundle\Service\RollingDate\RollingDate; use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface; use Chill\PersonBundle\Entity\AccompanyingPeriod; -use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation; -use Chill\PersonBundle\Entity\Person\PersonCenterHistory; use Chill\PersonBundle\Export\Declarations; +use Chill\PersonBundle\Export\Helper\FilterListAccompanyingPeriodHelperInterface; use Chill\PersonBundle\Export\Helper\ListAccompanyingPeriodHelper; -use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter; use Chill\PersonBundle\Security\Authorization\PersonVoter; use Doctrine\ORM\AbstractQuery; use Doctrine\ORM\EntityManagerInterface; -use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\Security\Core\Security; final readonly class ListAccompanyingPeriod implements ListInterface, GroupedExportInterface { - private bool $filterStatsByCenters; - public function __construct( private EntityManagerInterface $entityManager, private RollingDateConverterInterface $rollingDateConverter, private ListAccompanyingPeriodHelper $listAccompanyingPeriodHelper, - private Security $security, - private CenterRepositoryInterface $centerRepository, - private AuthorizationHelperForCurrentUserInterface $authorizationHelperForCurrentUser, - ParameterBagInterface $parameterBag, - ) { - $this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center']; - } + private FilterListAccompanyingPeriodHelperInterface $filterListAccompanyingPeriodHelper, + ) {} public function buildForm(FormBuilderInterface $builder) { @@ -109,17 +95,6 @@ final readonly class ListAccompanyingPeriod implements ListInterface, GroupedExp public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) { - $centers = match ($this->filterStatsByCenters) { - true => array_map(static fn ($el) => $el['center'], $acl), - false => $this->centerRepository->findAll(), - }; - - $user = $this->security->getUser(); - - if (!$user instanceof User) { - throw new \RuntimeException('only a regular user can run this export'); - } - $qb = $this->entityManager->createQueryBuilder(); $qb @@ -127,56 +102,7 @@ final readonly class ListAccompanyingPeriod implements ListInterface, GroupedExp ->andWhere('acp.step != :list_acp_step') ->setParameter('list_acp_step', AccompanyingPeriod::STEP_DRAFT); - // add filtering on confidential accompanying period. The confidential is applyed on the current status of - // the accompanying period (we do not use the 'calc_date' here - $aclConditionsOrX = $qb->expr()->orX( - // either the current user is the refferer for the course - 'acp.user = :list_acp_current_user', - ); - $qb->setParameter('list_acp_current_user', $user); - - $i = 0; - foreach ($centers as $center) { - $scopes = $this->authorizationHelperForCurrentUser->getReachableScopes(AccompanyingPeriodVoter::SEE_DETAILS, $center); - $scopesConfidential = - $this->authorizationHelperForCurrentUser->getReachableScopes(AccompanyingPeriodVoter::SEE_CONFIDENTIAL_ALL, $center); - $orScopes = $qb->expr()->orX(); - - foreach ($scopes as $scope) { - $scopeCondition = match (in_array($scope, $scopesConfidential, true)) { - true => ":scope_{$i} MEMBER OF acp.scopes", - false => $qb->expr()->andX( - 'acp.confidential = FALSE', - ":scope_{$i} MEMBER OF acp.scopes", - ), - }; - - $orScopes->add($scopeCondition); - $qb->setParameter("scope_{$i}", $scope); - ++$i; - } - - if ($this->filterStatsByCenters) { - $andX = $qb->expr()->andX( - $qb->expr()->exists( - 'SELECT 1 FROM '.AccompanyingPeriodParticipation::class." acl_count_part_{$i} - JOIN ".PersonCenterHistory::class." acl_count_person_history_{$i} WITH IDENTITY(acl_count_person_history_{$i}.person) = IDENTITY(acl_count_part_{$i}.person) - WHERE acl_count_part_{$i}.accompanyingPeriod = acp.id AND acl_count_person_history_{$i}.center IN (:authorized_center_{$i}) - AND acl_count_person_history_{$i}.startDate <= CURRENT_DATE() AND (acl_count_person_history_{$i}.endDate IS NULL or acl_count_person_history_{$i}.endDate > CURRENT_DATE()) - " - ), - $orScopes, - ); - $qb->setParameter('authorized_center_'.$i, $center); - $aclConditionsOrX->add($andX); - } else { - $aclConditionsOrX->add($orScopes); - } - - ++$i; - } - - $qb->andWhere($aclConditionsOrX); + $this->filterListAccompanyingPeriodHelper->addFilterAccompanyingPeriods($qb, $requiredModifiers, $acl, $data); $this->listAccompanyingPeriodHelper->addSelectClauses($qb, $this->rollingDateConverter->convert($data['calc_date'])); diff --git a/src/Bundle/ChillPersonBundle/Export/Helper/FilterListAccompanyingPeriodHelper.php b/src/Bundle/ChillPersonBundle/Export/Helper/FilterListAccompanyingPeriodHelper.php new file mode 100644 index 000000000..2c42d931f --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Export/Helper/FilterListAccompanyingPeriodHelper.php @@ -0,0 +1,105 @@ +filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center']; + } + + public function addFilterAccompanyingPeriods(QueryBuilder &$qb, array $requiredModifiers, array $acl, array $data = []): void + { + $centers = match ($this->filterStatsByCenters) { + true => array_map(static fn ($el) => $el['center'], $acl), + false => $this->centerRepository->findAll(), + }; + + $user = $this->security->getUser(); + + if (!$user instanceof User) { + throw new \RuntimeException('only a regular user can run this export'); + } + + // add filtering on confidential accompanying period. The confidential is applyed on the current status of + // the accompanying period (we do not use the 'calc_date' here + $aclConditionsOrX = $qb->expr()->orX( + // either the current user is the refferer for the course + 'acp.user = :list_acp_current_user', + ); + $qb->setParameter('list_acp_current_user', $user); + + $i = 0; + foreach ($centers as $center) { + $scopes = $this->authorizationHelperForCurrentUser->getReachableScopes(AccompanyingPeriodVoter::SEE_DETAILS, $center); + $scopesConfidential = + $this->authorizationHelperForCurrentUser->getReachableScopes(AccompanyingPeriodVoter::SEE_CONFIDENTIAL_ALL, $center); + $orScopes = $qb->expr()->orX(); + + foreach ($scopes as $scope) { + $scopeCondition = match (in_array($scope, $scopesConfidential, true)) { + true => ":scope_{$i} MEMBER OF acp.scopes", + false => $qb->expr()->andX( + 'acp.confidential = FALSE', + ":scope_{$i} MEMBER OF acp.scopes", + ), + }; + + $orScopes->add($scopeCondition); + $qb->setParameter("scope_{$i}", $scope); + ++$i; + } + + if ($this->filterStatsByCenters) { + $andX = $qb->expr()->andX( + $qb->expr()->exists( + 'SELECT 1 FROM '.AccompanyingPeriodParticipation::class." acl_count_part_{$i} + JOIN ".PersonCenterHistory::class." acl_count_person_history_{$i} WITH IDENTITY(acl_count_person_history_{$i}.person) = IDENTITY(acl_count_part_{$i}.person) + WHERE acl_count_part_{$i}.accompanyingPeriod = acp.id AND acl_count_person_history_{$i}.center IN (:authorized_center_{$i}) + AND acl_count_person_history_{$i}.startDate <= CURRENT_DATE() AND (acl_count_person_history_{$i}.endDate IS NULL or acl_count_person_history_{$i}.endDate > CURRENT_DATE()) + " + ), + $orScopes, + ); + $qb->setParameter('authorized_center_'.$i, $center); + $aclConditionsOrX->add($andX); + } else { + $aclConditionsOrX->add($orScopes); + } + + ++$i; + } + + $qb->andWhere($aclConditionsOrX); + } +} diff --git a/src/Bundle/ChillPersonBundle/Export/Helper/FilterListAccompanyingPeriodHelperInterface.php b/src/Bundle/ChillPersonBundle/Export/Helper/FilterListAccompanyingPeriodHelperInterface.php new file mode 100644 index 000000000..cde6aceec --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Export/Helper/FilterListAccompanyingPeriodHelperInterface.php @@ -0,0 +1,23 @@ +reveal(), - $centerRepository, - $authorizationHelper->reveal(), - $this->getParameters(true) + new FilterListAccompanyingPeriodHelper( + $security->reveal(), + $centerRepository, + $authorizationHelper->reveal(), + $this->getParameters(true) + ) ); + yield new ListAccompanyingPeriod( $em, $rollingDateConverter, $listAccompanyingPeriodHelper, - $security->reveal(), - $centerRepository, - $authorizationHelper->reveal(), - $this->getParameters(false) + new FilterListAccompanyingPeriodHelper( + $security->reveal(), + $centerRepository, + $authorizationHelper->reveal(), + $this->getParameters(false) + ) ); } diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Helper/FilterListAccompanyingPeriodHelperTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Helper/FilterListAccompanyingPeriodHelperTest.php new file mode 100644 index 000000000..f0e1129fa --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Helper/FilterListAccompanyingPeriodHelperTest.php @@ -0,0 +1,125 @@ +centerRepository = self::$container->get(CenterRepositoryInterface::class); + $this->scopeRepository = self::$container->get(ScopeRepositoryInterface::class); + $this->entityManager = self::$container->get(EntityManagerInterface::class); + } + + /** + * @return void + * @dataProvider dataProviderTestAddFilterAccompanyingPeriod + */ + public function testAddFilterAccompanyingPeriod(QueryBuilder $qb, ParameterBagInterface $parameterBag): void + { + // mock security + $user = $this->entityManager->createQuery('SELECT u FROM '.User::class.' u') + ->setMaxResults(1)->getSingleResult(); + if (null === $user) { + throw new \RuntimeException('no user found'); + } + $security = $this->prophesize(Security::class); + $security->getUser()->willReturn($user); + + // mock authorization helper + $scopes = $this->scopeRepository->findAll(); + $scopesConfidentials = [] !== $scopes ? [$scopes[0]] : []; + $authorizationHelper = $this->prophesize(AuthorizationHelperForCurrentUserInterface::class); + $authorizationHelper->getReachableScopes(AccompanyingPeriodVoter::SEE_DETAILS, Argument::type(Center::class)) + ->willReturn($scopes); + $authorizationHelper->getReachableScopes(AccompanyingPeriodVoter::SEE_CONFIDENTIAL_ALL, Argument::type(Center::class)) + ->willReturn($scopesConfidentials); + + $filter = new FilterListAccompanyingPeriodHelper( + $security->reveal(), + $this->centerRepository, + $authorizationHelper->reveal(), + $parameterBag + ); + + $filter->addFilterAccompanyingPeriods($qb, [], $this->getACL(), []); + + $qb->setMaxResults(1); + $result = $qb->getQuery()->getResult(); + + self::assertIsArray($result); + } + + public function dataProviderTestAddFilterAccompanyingPeriod(): iterable + { + self::setUp(); + $qb = $this->entityManager->createQueryBuilder(); + + $qb + ->select('acp.id') + ->from(AccompanyingPeriod::class, 'acp'); + + yield [ + $qb, + new ParameterBag(['chill_main' => ['acl' => ['filter_stats_by_center' => true]]]) + ]; + + yield [ + $qb, + new ParameterBag(['chill_main' => ['acl' => ['filter_stats_by_center' => false]]]) + ]; + } + + /** + * @return list}> The ACL, structured as an array. + * + * @throws \RuntimeException When no center or circle is found. + */ + private function getACL(): array + { + $centers = $this->centerRepository->findAll(); + $circles = $this->scopeRepository->findAll(); + + if (0 === \count($centers)) { + throw new \RuntimeException('No center found. Did you forget to run `doctrine:fixtures:load` command before ?'); + } + + if (0 === \count($circles)) { + throw new \RuntimeException('No circle found. Did you forget to run `doctrine:fixtures:load` command before ?'); + } + + return [[ + 'center' => $centers[0], + 'circles' => [ + $circles, + ], ]]; + } +}