From 965ea528e39788b534038ca055356a329a35afad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 26 Oct 2021 18:05:06 +0200 Subject: [PATCH] adaptations for acl with tasks --- .../ChillMainExtension.php | 4 + .../DependencyInjection/Configuration.php | 20 +- .../Form/Type/ScopePickerType.php | 34 ++- .../Form/Type/UserPickerType.php | 22 +- .../Repository/UserACLAwareRepository.php | 54 ++++ .../UserACLAwareRepositoryInterface.php | 22 ++ .../Util/confirmation_template.html.twig | 6 +- .../Authorization/AuthorizationHelper.php | 34 +-- .../ChillMainBundle/config/services.yaml | 2 + .../Controller/SingleTaskController.php | 277 ++++++++---------- .../ChillTaskBundle/Entity/AbstractTask.php | 38 ++- .../ChillTaskBundle/Entity/SingleTask.php | 36 +-- .../ChillTaskBundle/Form/SingleTaskType.php | 49 +++- .../confirm_delete.html.twig | 2 +- .../AccompanyingCourse/list.html.twig | 4 +- .../views/SingleTask/Person/list.html.twig | 10 +- .../views/SingleTask/_edit.html.twig | 6 +- .../Resources/views/SingleTask/_new.html.twig | 4 +- .../Security/Authorization/TaskVoter.php | 28 +- .../config/services/controller.yaml | 10 +- .../ChillTaskBundle/config/services/form.yaml | 5 + .../translations/messages.fr.yml | 2 +- 22 files changed, 371 insertions(+), 298 deletions(-) create mode 100644 src/Bundle/ChillMainBundle/Repository/UserACLAwareRepository.php create mode 100644 src/Bundle/ChillMainBundle/Repository/UserACLAwareRepositoryInterface.php diff --git a/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php b/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php index 8050fd9b6..7ec8d3585 100644 --- a/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php +++ b/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php @@ -90,6 +90,10 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface, $configuration = $this->getConfiguration($configs, $container); $config = $this->processConfiguration($configuration, $configs); + // replace all config with a main key: + $container->setParameter('chill_main', $config); + + // legacy config $container->setParameter('chill_main.installation_name', $config['installation_name']); diff --git a/src/Bundle/ChillMainBundle/DependencyInjection/Configuration.php b/src/Bundle/ChillMainBundle/DependencyInjection/Configuration.php index c711f911f..b7cff5821 100644 --- a/src/Bundle/ChillMainBundle/DependencyInjection/Configuration.php +++ b/src/Bundle/ChillMainBundle/DependencyInjection/Configuration.php @@ -16,23 +16,23 @@ use Symfony\Component\HttpFoundation\Request; */ class Configuration implements ConfigurationInterface { - + use AddWidgetConfigurationTrait; - + /** * * @var ContainerBuilder */ private $containerBuilder; - - public function __construct(array $widgetFactories = array(), + + public function __construct(array $widgetFactories = array(), ContainerBuilder $containerBuilder) { $this->setWidgetFactories($widgetFactories); $this->containerBuilder = $containerBuilder; } - + /** * {@inheritDoc} */ @@ -97,6 +97,14 @@ class Configuration implements ConfigurationInterface ->end() ->end() ->end() + ->arrayNode('acl') + ->addDefaultsIfNotSet() + ->children() + ->booleanNode('form_show_scopes') + ->defaultTrue() + ->end() + ->end() + ->end() ->arrayNode('redis') ->children() ->scalarNode('host') @@ -247,7 +255,7 @@ class Configuration implements ConfigurationInterface ->end() // end of root ; - + return $treeBuilder; } } diff --git a/src/Bundle/ChillMainBundle/Form/Type/ScopePickerType.php b/src/Bundle/ChillMainBundle/Form/Type/ScopePickerType.php index ebf474657..2785365cf 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/ScopePickerType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/ScopePickerType.php @@ -23,7 +23,9 @@ use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Entity\User; use Chill\MainBundle\Form\DataMapper\ScopePickerDataMapper; use Chill\MainBundle\Repository\ScopeRepository; +use Chill\MainBundle\Repository\UserACLAwareRepositoryInterface; use Chill\MainBundle\Security\Authorization\AuthorizationHelper; +use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface; use Chill\MainBundle\Templating\TranslatableStringHelper; use Doctrine\ORM\EntityRepository; use Symfony\Bridge\Doctrine\Form\Type\EntityType; @@ -36,6 +38,7 @@ use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Role\Role; +use Symfony\Component\Security\Core\Security; /** * Allow to pick amongst available scope for the current @@ -46,14 +49,10 @@ use Symfony\Component\Security\Core\Role\Role; * - `center`: the center of the entity * - `role` : the role of the user * - * @author Julien Fastré */ class ScopePickerType extends AbstractType { - /** - * @var AuthorizationHelper - */ - protected $authorizationHelper; + protected AuthorizationHelperInterface $authorizationHelper; /** * @var TokenStorageInterface @@ -70,22 +69,26 @@ class ScopePickerType extends AbstractType */ protected $translatableStringHelper; + protected Security $security; + public function __construct( - AuthorizationHelper $authorizationHelper, + AuthorizationHelperInterface $authorizationHelper, TokenStorageInterface $tokenStorage, ScopeRepository $scopeRepository, + Security $security, TranslatableStringHelper $translatableStringHelper ) { $this->authorizationHelper = $authorizationHelper; $this->tokenStorage = $tokenStorage; $this->scopeRepository = $scopeRepository; + $this->security = $security; $this->translatableStringHelper = $translatableStringHelper; } public function buildForm(FormBuilderInterface $builder, array $options) { - $query = $this->buildAccessibleScopeQuery($options['center'], $options['role']); - $items = $query->getQuery()->execute(); + $items = $this->authorizationHelper->getReachableScopes($this->security->getUser(), + $options['role'], $options['center']); if (1 !== count($items)) { $builder->add('scope', EntityType::class, [ @@ -94,9 +97,7 @@ class ScopePickerType extends AbstractType 'choice_label' => function (Scope $c) { return $this->translatableStringHelper->localize($c->getName()); }, - 'query_builder' => function () use ($options) { - return $this->buildAccessibleScopeQuery($options['center'], $options['role']); - }, + 'choices' => $items, ]); $builder->setDataMapper(new ScopePickerDataMapper()); } else { @@ -121,19 +122,22 @@ class ScopePickerType extends AbstractType $resolver // create `center` option ->setRequired('center') - ->setAllowedTypes('center', [Center::class]) + ->setAllowedTypes('center', [Center::class, 'array', 'null']) // create ``role` option ->setRequired('role') ->setAllowedTypes('role', ['string', Role::class]); } /** + * @param Center|array|Center[] $center + * @param string $role * @return \Doctrine\ORM\QueryBuilder */ - protected function buildAccessibleScopeQuery(Center $center, Role $role) + protected function buildAccessibleScopeQuery($center, $role) { $roles = $this->authorizationHelper->getParentRoles($role); $roles[] = $role; + $centers = $center instanceof Center ? [$center]: $center; $qb = $this->scopeRepository->createQueryBuilder('s'); $qb @@ -142,8 +146,8 @@ class ScopePickerType extends AbstractType ->join('rs.permissionsGroups', 'pg') ->join('pg.groupCenters', 'gc') // add center constraint - ->where($qb->expr()->eq('IDENTITY(gc.center)', ':center')) - ->setParameter('center', $center->getId()) + ->where($qb->expr()->in('IDENTITY(gc.center)', ':centers')) + ->setParameter('centers', \array_map(fn(Center $c) => $c->getId(), $centers)) // role constraints ->andWhere($qb->expr()->in('rs.role', ':roles')) ->setParameter('roles', $roles) diff --git a/src/Bundle/ChillMainBundle/Form/Type/UserPickerType.php b/src/Bundle/ChillMainBundle/Form/Type/UserPickerType.php index fece2d6d4..2558f4a4f 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/UserPickerType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/UserPickerType.php @@ -17,6 +17,8 @@ */ namespace Chill\MainBundle\Form\Type; +use Chill\MainBundle\Entity\Scope; +use Chill\MainBundle\Repository\UserACLAwareRepositoryInterface; use Symfony\Component\Form\AbstractType; use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Doctrine\ORM\EntityRepository; @@ -56,14 +58,18 @@ class UserPickerType extends AbstractType protected UserRepository $userRepository; + protected UserACLAwareRepositoryInterface $userACLAwareRepository; + public function __construct( AuthorizationHelper $authorizationHelper, TokenStorageInterface $tokenStorage, - UserRepository $userRepository + UserRepository $userRepository, + UserACLAwareRepositoryInterface $userACLAwareRepository ) { $this->authorizationHelper = $authorizationHelper; $this->tokenStorage = $tokenStorage; $this->userRepository = $userRepository; + $this->userACLAwareRepository = $userACLAwareRepository; } @@ -72,7 +78,7 @@ class UserPickerType extends AbstractType $resolver // create `center` option ->setRequired('center') - ->setAllowedTypes('center', [\Chill\MainBundle\Entity\Center::class ]) + ->setAllowedTypes('center', [\Chill\MainBundle\Entity\Center::class, 'null', 'array' ]) // create ``role` option ->setRequired('role') ->setAllowedTypes('role', ['string', \Symfony\Component\Security\Core\Role\Role::class ]) @@ -86,17 +92,19 @@ class UserPickerType extends AbstractType ->setDefault('choice_label', function(User $u) { return $u->getUsername(); }) + ->setDefault('scope', null) + ->setAllowedTypes('scope', [Scope::class, 'array', 'null']) ->setNormalizer('choices', function(Options $options) { - - $users = $this->authorizationHelper - ->findUsersReaching($options['role'], $options['center']); - + + $users = $this->userACLAwareRepository + ->findUsersByReachedACL($options['role'], $options['center'], $options['scope'], true); + if (NULL !== $options['having_permissions_group_flag']) { return $this->userRepository ->findUsersHavingFlags($options['having_permissions_group_flag'], $users) ; } - + return $users; }) ; diff --git a/src/Bundle/ChillMainBundle/Repository/UserACLAwareRepository.php b/src/Bundle/ChillMainBundle/Repository/UserACLAwareRepository.php new file mode 100644 index 000000000..44399000b --- /dev/null +++ b/src/Bundle/ChillMainBundle/Repository/UserACLAwareRepository.php @@ -0,0 +1,54 @@ +authorizationHelper->getParentRoles($role); + $parents[] = $role; + + $qb = $this->em->createQueryBuilder(); + + $qb + ->select('u') + ->from(User::class, 'u') + ->join('u.groupCenters', 'gc') + ->join('gc.permissionsGroup', 'pg') + ->join('pg.roleScopes', 'rs') + ->where($qb->expr()->in('rs.role', $parents)) + ; + + if ($onlyEnabled) { + $qb->andWhere($qb->expr()->eq('u.enabled', "'TRUE'")); + } + + if (NULL !== $center) { + $centers = $center instanceof Center ? [$center] : $center; + $qb + ->andWhere($qb->expr()->in('gc.center', ':centers')) + ->setParameter('centers', $centers) + ; + } + + if (NULL !== $scope) { + $scopes = $scope instanceof Scope ? [$scope] : $scope; + $qb + ->andWhere($qb->expr()->in('rs.scope', ':scopes')) + ->setParameter('scopes', $scopes) + ; + } + + return $qb->getQuery()->getResult(); + } + +} diff --git a/src/Bundle/ChillMainBundle/Repository/UserACLAwareRepositoryInterface.php b/src/Bundle/ChillMainBundle/Repository/UserACLAwareRepositoryInterface.php new file mode 100644 index 000000000..eb249911b --- /dev/null +++ b/src/Bundle/ChillMainBundle/Repository/UserACLAwareRepositoryInterface.php @@ -0,0 +1,22 @@ + + - -{{ form_end(form) }} \ No newline at end of file + +{{ form_end(form) }} diff --git a/src/Bundle/ChillMainBundle/Security/Authorization/AuthorizationHelper.php b/src/Bundle/ChillMainBundle/Security/Authorization/AuthorizationHelper.php index afff931ed..8713d6ce3 100644 --- a/src/Bundle/ChillMainBundle/Security/Authorization/AuthorizationHelper.php +++ b/src/Bundle/ChillMainBundle/Security/Authorization/AuthorizationHelper.php @@ -23,6 +23,8 @@ use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Entity\HasCenterInterface; use Chill\MainBundle\Entity\HasScopeInterface; +use Chill\MainBundle\Repository\UserACLAwareRepository; +use Chill\MainBundle\Repository\UserACLAwareRepositoryInterface; use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher; use Chill\MainBundle\Security\Resolver\ScopeResolverDispatcher; use Chill\MainBundle\Security\Resolver\ScopeResolverInterface; @@ -62,6 +64,8 @@ class AuthorizationHelper implements AuthorizationHelperInterface protected LoggerInterface $logger; + private UserACLAwareRepositoryInterface $userACLAwareRepository; + public function __construct( RoleHierarchyInterface $roleHierarchy, ParameterBagInterface $parameterBag, @@ -320,33 +324,15 @@ class AuthorizationHelper implements AuthorizationHelperInterface /** * + * @deprecated use UserACLAwareRepositoryInterface::findUsersByReachedACL instead + * @param Center|Center[]|array $center + * @param Scope|Scope[]|array|null $scope + * @param bool $onlyActive true if get only active users * @return User[] */ - public function findUsersReaching(string $role, Center $center, Scope $circle = null): array + public function findUsersReaching(string $role, $center, $scope = null, bool $onlyEnabled = true): array { - $parents = $this->getParentRoles($role); - $parents[] = $role; - - $qb = $this->em->createQueryBuilder(); - $qb - ->select('u') - ->from(User::class, 'u') - ->join('u.groupCenters', 'gc') - ->join('gc.permissionsGroup', 'pg') - ->join('pg.roleScopes', 'rs') - ->where('gc.center = :center') - ->andWhere($qb->expr()->in('rs.role', $parents)) - ; - - $qb->setParameter('center', $center); - - if ($circle !== null) { - $qb->andWhere('rs.scope = :circle') - ->setParameter('circle', $circle) - ; - } - - return $qb->getQuery()->getResult(); + return $this->userACLAwareRepository->findUsersByReachedACL($role, $center, $scope, $onlyEnabled); } /** diff --git a/src/Bundle/ChillMainBundle/config/services.yaml b/src/Bundle/ChillMainBundle/config/services.yaml index 6540069fa..f5f10cf57 100644 --- a/src/Bundle/ChillMainBundle/config/services.yaml +++ b/src/Bundle/ChillMainBundle/config/services.yaml @@ -11,6 +11,8 @@ services: autowire: true autoconfigure: true + Chill\MainBundle\Repository\UserACLAwareRepositoryInterface: '@Chill\MainBundle\Repository\UserACLAwareRepository' + Chill\MainBundle\Serializer\Normalizer\: resource: '../Serializer/Normalizer' autoconfigure: true diff --git a/src/Bundle/ChillTaskBundle/Controller/SingleTaskController.php b/src/Bundle/ChillTaskBundle/Controller/SingleTaskController.php index be39f4900..5bb5ebe6c 100644 --- a/src/Bundle/ChillTaskBundle/Controller/SingleTaskController.php +++ b/src/Bundle/ChillTaskBundle/Controller/SingleTaskController.php @@ -3,9 +3,12 @@ namespace Chill\TaskBundle\Controller; use Chill\MainBundle\Entity\Scope; +use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher; +use Chill\MainBundle\Security\Resolver\CenterResolverInterface; use Chill\PersonBundle\Privacy\PrivacyEvent; use Psr\Log\LoggerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\Routing\Annotation\Route; use Chill\PersonBundle\Entity\Person; use Symfony\Component\HttpFoundation\Request; @@ -24,7 +27,6 @@ use Chill\PersonBundle\Security\Authorization\PersonVoter; use Chill\PersonBundle\Repository\PersonRepository; use Chill\TaskBundle\Event\TaskEvent; use Symfony\Component\EventDispatcher\EventDispatcherInterface; -use Symfony\Component\Translation\TranslatorInterface; use Chill\TaskBundle\Event\UI\UIEvent; use Chill\MainBundle\Repository\CenterRepository; use Chill\MainBundle\Timeline\TimelineBuilder; @@ -33,6 +35,7 @@ use Chill\PersonBundle\Repository\AccompanyingPeriodRepository; use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface as TranslationTranslatorInterface; /** @@ -40,126 +43,100 @@ use Symfony\Contracts\Translation\TranslatorInterface as TranslationTranslatorIn * * @package Chill\TaskBundle\Controller */ -class SingleTaskController extends AbstractController +final class SingleTaskController extends AbstractController { - - /** - * @var EventDispatcherInterface - */ - protected $eventDispatcher; - - /** - * - * @var TimelineBuilder - */ - protected $timelineBuilder; - - /** - * @var LoggerInterface - */ - protected $logger; - /** - * @var RequestStack - */ - protected $request; - - /** - * SingleTaskController constructor. - * - * @param EventDispatcherInterface $eventDispatcher - */ + private EventDispatcherInterface $eventDispatcher; + private TimelineBuilder $timelineBuilder; + private LoggerInterface $logger; + private CenterResolverDispatcher $centerResolverDispatcher; + private TranslatorInterface $translator; + public function __construct( + CenterResolverDispatcher $centerResolverDispatcher, + TranslatorInterface $translator, EventDispatcherInterface $eventDispatcher, TimelineBuilder $timelineBuilder, - LoggerInterface $logger, - RequestStack $requestStack + LoggerInterface $logger ) { $this->eventDispatcher = $eventDispatcher; $this->timelineBuilder = $timelineBuilder; $this->logger = $logger; - $this->request = $requestStack->getCurrentRequest(); + $this->translator = $translator; + $this->centerResolverDispatcher = $centerResolverDispatcher; } - - private function getEntityContext() + private function getEntityContext(Request $request) { - if($this->request->query->has('person_id')){ + if ($request->query->has('person_id')) { return 'person'; - } else if ($this->request->query->has('course_id')) { + } else if ($request->query->has('course_id')) { return 'course'; } else { return null; } } - - + + /** * @Route( * "/{_locale}/task/single-task/new", * name="chill_task_single_task_new" * ) */ - public function newAction( - TranslationTranslatorInterface $translator - ) { + public function newAction(Request $request) { $task = (new SingleTask()) ->setAssignee($this->getUser()) ->setType('task_default') ; - - $entityType = $this->getEntityContext(); - if ($entityType !== null) { - - $entityId = $this->request->query->getInt("{$entityType}_id", 0); // sf4 check: - // prevent error: `Argument 2 passed to ::getInt() must be of the type int, null given` + $entityType = $this->getEntityContext($request); - if ($entityId === null) { - return new Response("You must provide a {$entityType}_id", Response::HTTP_BAD_REQUEST); - } + if (NULL === $entityType) { + throw new BadRequestHttpException("You must provide a entity_type"); + } - if($entityType === 'person') - { + $entityId = $request->query->getInt("{$entityType}_id", 0); + + if ($entityId === null) { + return new BadRequestHttpException("You must provide a {$entityType}_id"); + } + + switch ($entityType) { + case 'person': $person = $this->getDoctrine()->getManager() - ->getRepository(Person::class) - ->find($entityId); + ->getRepository(Person::class) + ->find($entityId); if ($person === null) { $this->createNotFoundException("Invalid person id"); } - $task->setPerson($person); - } - - if($entityType === 'course') - { - + $task->setPerson($person); + break; + case 'course': $course = $this->getDoctrine()->getManager() - ->getRepository(AccompanyingPeriod::class) - ->find($entityId); + ->getRepository(AccompanyingPeriod::class) + ->find($entityId); - if($course === null) { + if ($course === null) { $this->createNotFoundException("Invalid accompanying course id"); } $task->setCourse($course); - - } - - + break; + default: + return new BadRequestHttpException("context with {$entityType} is not supported"); } - //TODO : resolve access rights - - // $this->denyAccessUnlessGranted(TaskVoter::CREATE, $task, 'You are not ' - // . 'allowed to create this task'); + $this->denyAccessUnlessGranted(TaskVoter::CREATE, $task, 'You are not ' + . 'allowed to create this task'); $form = $this->setCreateForm($task, new Role(TaskVoter::CREATE)); - $form->handleRequest($this->request); - + $form->handleRequest($request); + if ($form->isSubmitted()) { if ($form->isValid()) { $em = $this->getDoctrine()->getManager(); @@ -169,44 +146,38 @@ class SingleTaskController extends AbstractController $em->flush(); - $this->addFlash('success', $translator->trans("The task is created")); + $this->addFlash('success', $this->translator->trans("The task is created")); - if($entityType === 'person') - { + if ($entityType === 'person') { return $this->redirectToRoute('chill_task_singletask_list', [ 'person_id' => $task->getPerson()->getId() ]); - } - - if($entityType === 'course') - { + } elseif ($entityType === 'course') { return $this->redirectToRoute('chill_task_singletask_courselist', [ 'course_id' => $task->getCourse()->getId() ]); } - } else { - $this->addFlash('error', $translator->trans("This form contains errors")); + $this->addFlash('error', $this->translator->trans("This form contains errors")); } } - switch($this->getEntityContext()){ - case 'person': + switch ($entityType) { + case 'person': return $this->render('@ChillTask/SingleTask/Person/new.html.twig', array( 'form' => $form->createView(), 'task' => $task, 'person' => $person, )); - break; case 'course': return $this->render('@ChillTask/SingleTask/AccompanyingCourse/new.html.twig', array( 'form' => $form->createView(), 'task' => $task, 'accompanyingCourse' => $course, )); - break; - } - + default: + throw new \LogicException("entity context not supported"); + } } /** @@ -215,9 +186,9 @@ class SingleTaskController extends AbstractController * name="chill_task_single_task_show" * ) */ - public function showAction($id) + public function showAction($id, Request $request) { - + $em = $this->getDoctrine()->getManager(); $task = $em->getRepository(SingleTask::class)->find($id); @@ -226,7 +197,7 @@ class SingleTaskController extends AbstractController } if ($task->getPerson() !== null) { - + $personId = $task->getPerson()->getId(); if ($personId === null) { @@ -250,7 +221,7 @@ class SingleTaskController extends AbstractController } - if ($task->getCourse() !== null) + if ($task->getCourse() !== null) { $courseId = $task->getCourse()->getId(); @@ -274,7 +245,7 @@ class SingleTaskController extends AbstractController $timeline = $this->timelineBuilder ->getTimelineHTML('task', array('task' => $task)); - + if($task->getContext() instanceof Person){ return $this->render('@ChillTask/SingleTask/Person/show.html.twig', array( @@ -299,9 +270,9 @@ class SingleTaskController extends AbstractController */ public function editAction( $id, - TranslationTranslatorInterface $translator + Request $request ) { - + $em = $this->getDoctrine()->getManager(); $task = $em->getRepository(SingleTask::class)->find($id); @@ -339,16 +310,16 @@ class SingleTaskController extends AbstractController if (!$task) { throw $this->createNotFoundException('Unable to find Task entity.'); } - + $event = (new UIEvent('single-task', $task)) ->setForm($this->setCreateForm($task, new Role(TaskVoter::UPDATE))) ; - + $this->eventDispatcher->dispatch(UIEvent::EDIT_FORM, $event); - + $form = $event->getForm(); - $form->handleRequest($this->request); + $form->handleRequest($request); if ($form->isSubmitted()) { if ($form->isValid()) { @@ -357,7 +328,7 @@ class SingleTaskController extends AbstractController $em->flush(); - $this->addFlash('success', $translator + $this->addFlash('success', $this->translator ->trans("The task has been updated")); if($task->getContext() instanceof Person){ @@ -369,26 +340,26 @@ class SingleTaskController extends AbstractController $this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event); return $this->redirectToRoute( - 'chill_task_singletask_list', - $this->request->query->get('list_params', []) + 'chill_task_singletask_list', + $request->query->get('list_params', []) ); } else { return $this->redirectToRoute( - 'chill_task_singletask_courselist', - $this->request->query->get('list_params', []) + 'chill_task_singletask_courselist', + $request->query->get('list_params', []) ); } } else { - $this->addFlash('error', $translator->trans("This form contains errors")); + $this->addFlash('error', $this->translator->trans("This form contains errors")); } } - + $this->eventDispatcher->dispatch(UIEvent::EDIT_PAGE, $event); - + if ($event->hasResponse()) { return $event->getResponse(); } - + if($task->getContext() instanceof Person){ $event = new PrivacyEvent($person, array( 'element_class' => SingleTask::class, @@ -420,10 +391,9 @@ class SingleTaskController extends AbstractController */ public function deleteAction( Request $request, - $id, - TranslationTranslatorInterface $translator + $id ) { - + $em = $this->getDoctrine()->getManager(); $task = $em->getRepository(SingleTask::class)->find($id); @@ -471,7 +441,7 @@ class SingleTaskController extends AbstractController $form->handleRequest($request); if ($form->isValid()) { - + $this->logger->notice("A task has been removed", array( 'by_user' => $this->getUser()->getUsername(), 'task_id' => $task->getId(), @@ -485,18 +455,18 @@ class SingleTaskController extends AbstractController $em->remove($task); $em->flush(); - $this->addFlash('success', $translator + $this->addFlash('success', $this->translator ->trans("The task has been successfully removed.")); if($task->getContext() instanceof Person){ return $this->redirect($this->generateUrl( - 'chill_task_singletask_list', + 'chill_task_singletask_list', $request->query->get('list_params', [ 'person_id' => $person->getId() ]))); } else { return $this->redirect($this->generateUrl( - 'chill_task_singletask_courselist', + 'chill_task_singletask_courselist', $request->query->get('list_params', [ 'course_id' => $course->getId() ]))); @@ -528,7 +498,7 @@ class SingleTaskController extends AbstractController protected function setCreateForm(SingleTask $task, Role $role) { $form = $this->createForm(SingleTaskType::class, $task, [ - 'center' => $task->getCenter(), + 'center' => $this->centerResolverDispatcher->resolveCenter($task), 'role' => $role, ]); @@ -545,12 +515,12 @@ class SingleTaskController extends AbstractController * name="chill_task_single_my_tasks" * ) */ - public function myTasksAction(TranslationTranslatorInterface $translator) + public function myTasksAction() { return $this->redirectToRoute('chill_task_singletask_list', [ 'user_id' => $this->getUser()->getId(), 'hide_form' => true, - 'title' => $translator->trans('My tasks') + 'title' => $this->translator->trans('My tasks') ]); } @@ -575,7 +545,8 @@ class SingleTaskController extends AbstractController PersonRepository $personRepository, AccompanyingPeriodRepository $courseRepository, CenterRepository $centerRepository, - FormFactoryInterface $formFactory + FormFactoryInterface $formFactory, + Request $request ) { /* @var $viewParams array The parameters for the view */ /* @var $params array The parameters for the query */ @@ -589,9 +560,9 @@ class SingleTaskController extends AbstractController $viewParams['accompanyingCourse'] = null; $params['accompanyingCourse'] = null; - if (!empty($this->request->query->get('person_id', NULL))) { - - $personId = $this->request->query->getInt('person_id', 0); + if (!empty($request->query->get('person_id', NULL))) { + + $personId = $request->query->getInt('person_id', 0); $person = $personRepository->find($personId); if ($person === null) { @@ -603,9 +574,9 @@ class SingleTaskController extends AbstractController $params['person'] = $person; } - if (!empty($this->request->query->get('course_id', NULL))) { - - $courseId = $this->request->query->getInt('course_id', 0); + if (!empty($request->query->get('course_id', NULL))) { + + $courseId = $request->query->getInt('course_id', 0); $course = $courseRepository->find($courseId); if ($course === null) { @@ -616,28 +587,28 @@ class SingleTaskController extends AbstractController $viewParams['accompanyingCourse'] = $course; $params['accompanyingCourse'] = $course; } - - if (!empty($this->request->query->get('center_id', NULL))) { - $center = $centerRepository->find($this->request->query->getInt('center_id')); + + if (!empty($request->query->get('center_id', NULL))) { + $center = $centerRepository->find($request->query->getInt('center_id')); if ($center === null) { throw $this->createNotFoundException('center not found'); } $params['center'] = $center; } - - if(!empty($this->request->query->get('types', []))) { - $types = $this->request->query->get('types', []); + + if(!empty($request->query->get('types', []))) { + $types = $request->query->get('types', []); if (count($types) > 0) { $params['types'] = $types; } } - - if (!empty($this->request->query->get('user_id', null))) { - if ($this->request->query->get('user_id') === '_unassigned') { + + if (!empty($request->query->get('user_id', null))) { + if ($request->query->get('user_id') === '_unassigned') { $params['unassigned'] = true; } else { - - $userId = $this->request->query->getInt('user_id', 0); + + $userId = $request->query->getInt('user_id', 0); $user = $this->getDoctrine()->getManager() ->getRepository(User::class) ->find($userId); @@ -651,10 +622,10 @@ class SingleTaskController extends AbstractController } } - if (!empty($this->request->query->get('scope_id'))) { + if (!empty($request->query->get('scope_id'))) { + + $scopeId = $request->query->getInt('scope_id', 0); - $scopeId = $this->request->query->getInt('scope_id', 0); - $scope = $this->getDoctrine()->getManager() ->getRepository(Scope::class) ->find($scopeId); @@ -668,7 +639,7 @@ class SingleTaskController extends AbstractController } $possibleStatuses = \array_merge(SingleTaskRepository::DATE_STATUSES, [ 'closed' ]); - $statuses = $this->request->query->get('status', $possibleStatuses); + $statuses = $request->query->get('status', $possibleStatuses); $diff = \array_diff($statuses, $possibleStatuses); if (count($diff) > 0) { @@ -683,7 +654,7 @@ class SingleTaskController extends AbstractController $tasks_count = 0; foreach($statuses as $status) { - if($this->request->query->has('status') + if($request->query->has('status') && FALSE === \in_array($status, $statuses)) { continue; } @@ -729,8 +700,8 @@ class SingleTaskController extends AbstractController 'add_type' => true ]); - $form->handleRequest($this->request); - + $form->handleRequest($request); + if (isset($person)) { $event = new PrivacyEvent($person, array( 'element_class' => SingleTask::class, @@ -738,7 +709,7 @@ class SingleTaskController extends AbstractController )); $this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event); } - + return $this->render('@ChillTask/SingleTask/index.html.twig', array_merge($viewParams, [ 'form' => $form->createView() ])); } @@ -747,7 +718,7 @@ class SingleTaskController extends AbstractController protected function getPersonParam(EntityManagerInterface $em) { $person = $em->getRepository(Person::class) - ->find($this->request->query->getInt('person_id')) + ->find($request->query->getInt('person_id')) ; if (NULL === $person) { @@ -763,7 +734,7 @@ class SingleTaskController extends AbstractController protected function getUserParam(EntityManagerInterface $em) { $user = $em->getRepository(User::class) - ->find($this->request->query->getInt('user_id')) + ->find($request->query->getInt('user_id')) ; if (NULL === $user) { @@ -799,16 +770,16 @@ class SingleTaskController extends AbstractController */ public function listCourseTasks( - AccompanyingPeriodRepository $courseRepository, + AccompanyingPeriodRepository $courseRepository, SingleTaskRepository $taskRepository, FormFactoryInterface $formFactory, - TranslationTranslatorInterface $translator + Request $request ): Response { - if (!empty($this->request->query->get('course_id', NULL))) { - - $courseId = $this->request->query->getInt('course_id', 0); + if (!empty($request->query->get('course_id', NULL))) { + + $courseId = $request->query->getInt('course_id', 0); $course = $courseRepository->find($courseId); if ($course === null) { @@ -834,15 +805,15 @@ class SingleTaskController extends AbstractController 'csrf_protection' => false, 'add_type' => true ]); - + return $this->render( - '@ChillTask/SingleTask/index.html.twig', + '@ChillTask/SingleTask/index.html.twig', [ 'tasks' => $tasks, 'accompanyingCourse' => $course, 'layout' => '@ChillPerson/AccompanyingCourse/layout.html.twig', 'form' => $form->createView(), - 'title' => $translator->trans('Tasks for this accompanying period') + 'title' => $this->translator->trans('Tasks for this accompanying period') ]); } diff --git a/src/Bundle/ChillTaskBundle/Entity/AbstractTask.php b/src/Bundle/ChillTaskBundle/Entity/AbstractTask.php index ce2de5a6f..b3f0c6a9f 100644 --- a/src/Bundle/ChillTaskBundle/Entity/AbstractTask.php +++ b/src/Bundle/ChillTaskBundle/Entity/AbstractTask.php @@ -16,10 +16,6 @@ use Chill\PersonBundle\Entity\AccompanyingPeriod; * AbstractTask * * @ORM\MappedSuperclass() - * @UserCircleConsistency( - * "CHILL_TASK_TASK_SHOW", - * getUserFunction="getAssignee" - * ) */ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface { @@ -52,7 +48,7 @@ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface * @ORM\Column(name="description", type="text") */ private $description = ''; - + /** * * @var User @@ -61,7 +57,7 @@ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface * ) */ private $assignee; - + /** * * @var Person @@ -78,25 +74,25 @@ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface */ private $course; - + /** * * @var Scope * @ORM\ManyToOne( * targetEntity="\Chill\MainBundle\Entity\Scope" - * ) + * ) */ private $circle; - + /** * @var boolean * @ORM\Column(name="closed", type="boolean", options={ "default"=false }) */ private $closed = false; - + public function __construct() { - + } /** @@ -125,7 +121,7 @@ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface /** * Set currentStates - * + * * The current states are sorted in a single array, non associative. * * @param $currentStates @@ -141,8 +137,8 @@ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface /** * Get currentStates - * - * The states are returned as required by marking store format. + * + * The states are returned as required by marking store format. * * @return array */ @@ -198,7 +194,7 @@ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface { return $this->description; } - + public function getAssignee(): ?User { return $this->assignee; @@ -218,7 +214,7 @@ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface { return $this->circle; } - + public function setAssignee(User $assignee = null) { $this->assignee = $assignee; @@ -250,9 +246,9 @@ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface } else { return $this->getCourse()->getCenter(); } - + return null; - + } public function getContext() @@ -264,7 +260,7 @@ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface // } return $this->getPerson() ?? $this->getCourse(); } - + public function getScope(): ?\Chill\MainBundle\Entity\Scope { return $this->getCircle(); @@ -277,9 +273,9 @@ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface { return $this->closed; } - + /** - * + * * @param bool $closed */ public function setClosed(bool $closed) diff --git a/src/Bundle/ChillTaskBundle/Entity/SingleTask.php b/src/Bundle/ChillTaskBundle/Entity/SingleTask.php index 23bdd61df..c22f47361 100644 --- a/src/Bundle/ChillTaskBundle/Entity/SingleTask.php +++ b/src/Bundle/ChillTaskBundle/Entity/SingleTask.php @@ -40,12 +40,12 @@ class SingleTask extends AbstractTask * * @ORM\Column(name="start_date", type="date", nullable=true) * @Assert\Date() - * + * * @Assert\Expression( * "value === null or this.getEndDate() === null or value < this.getEndDate()", * message="The start date must be before the end date" * ) - * + * * @Assert\Expression( * "value === null or this.getWarningDate() === null or this.getWarningDate() > this.getStartDate()", * message="The start date must be before warning date" @@ -66,16 +66,16 @@ class SingleTask extends AbstractTask * and this.getEndDate() === null * * @ORM\Column(name="warning_interval", type="dateinterval", nullable=true) - * + * * @Assert\Expression( * "!(value != null and this.getEndDate() == null)", * message="An end date is required if a warning interval is set" * ) - * - * + * + * */ private $warningInterval; - + /** * * @var RecurringTask @@ -85,7 +85,7 @@ class SingleTask extends AbstractTask * ) */ private $recurringTask; - + /** * * @var \Doctrine\Common\Collections\Collection @@ -96,15 +96,15 @@ class SingleTask extends AbstractTask * ) */ private $taskPlaceEvents; - + public function __construct() { $this->taskPlaceEvents = $events = new \Doctrine\Common\Collections\ArrayCollection; - + parent::__construct(); } - + /** * Get id * @@ -186,13 +186,13 @@ class SingleTask extends AbstractTask { return $this->warningInterval; } - + /** - * Get the Warning date, computed from the difference between the + * Get the Warning date, computed from the difference between the * end date and the warning interval - * + * * Return null if warningDate or endDate is null - * + * * @return \DateTimeImmutable */ public function getWarningDate() @@ -200,15 +200,15 @@ class SingleTask extends AbstractTask if ($this->getWarningInterval() === null) { return null; } - + if ($this->getEndDate() === null) { return null; } - + return \DateTimeImmutable::createFromMutable($this->getEndDate()) ->sub($this->getWarningInterval()); } - + function getRecurringTask(): RecurringTask { return $this->recurringTask; @@ -227,7 +227,7 @@ class SingleTask extends AbstractTask public function setTaskPlaceEvents(Collection $taskPlaceEvents) { $this->taskPlaceEvents = $taskPlaceEvents; - + return $this; } } diff --git a/src/Bundle/ChillTaskBundle/Form/SingleTaskType.php b/src/Bundle/ChillTaskBundle/Form/SingleTaskType.php index e0bae1608..7fbc84800 100644 --- a/src/Bundle/ChillTaskBundle/Form/SingleTaskType.php +++ b/src/Bundle/ChillTaskBundle/Form/SingleTaskType.php @@ -17,6 +17,9 @@ */ namespace Chill\TaskBundle\Form; +use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher; +use Chill\MainBundle\Security\Resolver\ScopeResolverDispatcher; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Chill\MainBundle\Form\Type\ChillDateType; @@ -29,15 +32,26 @@ use Symfony\Component\Security\Core\Role\Role; use Chill\MainBundle\Form\Type\DateIntervalType; use Chill\MainBundle\Form\Type\ChillTextareaType; -/** - * - * - * @author Julien Fastré - */ class SingleTaskType extends AbstractType { + private ParameterBagInterface $parameterBag; + private CenterResolverDispatcher $centerResolverDispatcher; + private ScopeResolverDispatcher $scopeResolverDispatcher; + + public function __construct(ParameterBagInterface $parameterBag, CenterResolverDispatcher $centerResolverDispatcher, ScopeResolverDispatcher $scopeResolverDispatcher) + { + $this->parameterBag = $parameterBag; + $this->centerResolverDispatcher = $centerResolverDispatcher; + $this->scopeResolverDispatcher = $scopeResolverDispatcher; + } + public function buildForm(FormBuilderInterface $builder, array $options) { + if (NULL !== $task = $options['data']) { + $center = $this->centerResolverDispatcher->resolveCenter($task); + $isScopeConcerned = $this->scopeResolverDispatcher->isConcerned($task); + } + $builder ->add('title', TextType::class) ->add('description', ChillTextareaType::class, [ @@ -45,15 +59,10 @@ class SingleTaskType extends AbstractType ]) ->add('assignee', UserPickerType::class, [ 'required' => false, - 'center' => $options['center'], + 'center' => $center, 'role' => $options['role'], 'placeholder' => 'Not assigned' ]) - ->add('circle', ScopePickerType::class, [ - 'center' => $options['center'], - 'role' => $options['role'], - 'required' => false - ]) ->add('startDate', ChillDateType::class, [ 'required' => false ]) @@ -62,16 +71,26 @@ class SingleTaskType extends AbstractType ]) ->add('warningInterval', DateIntervalType::class, [ 'required' => false - ]); + ]); + + if ($this->parameterBag->get('chill_main')['acl']['form_show_scopes'] + && $isScopeConcerned) { + $builder + ->add('circle', ScopePickerType::class, [ + 'center' => $center, + 'role' => $options['role'], + 'required' => false + ]); + } } - + public function configureOptions(OptionsResolver $resolver) { $resolver ->setRequired('center') - ->setAllowedTypes('center', [ Center::class ]) + ->setAllowedTypes('center', [ Center::class, 'array', 'null' ]) ->setRequired('role') - ->setAllowedTypes('role', [ Role::class ]) + ->setAllowedTypes('role', [ Role::class, 'string' ]) ; } } diff --git a/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/AccompanyingCourse/confirm_delete.html.twig b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/AccompanyingCourse/confirm_delete.html.twig index 49857188c..01b7aa499 100644 --- a/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/AccompanyingCourse/confirm_delete.html.twig +++ b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/AccompanyingCourse/confirm_delete.html.twig @@ -10,7 +10,7 @@ {{ include('@ChillMain/Util/confirmation_template.html.twig', { 'title' : 'Remove task'|trans, - 'confirm_question' : 'Are you sure you want to remove the task about accompanying period "%id%" ?'|trans({ '%id%' : course.id } ), + 'confirm_question' : 'Are you sure you want to remove the task "%title%" ?'|trans({ '%title%' : task.title } ), 'cancel_route' : 'chill_task_singletask_courselist', 'cancel_parameters' : app.request.query.get('list_params', { } ), 'form' : delete_form, diff --git a/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/AccompanyingCourse/list.html.twig b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/AccompanyingCourse/list.html.twig index 493fab920..be491e2f9 100644 --- a/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/AccompanyingCourse/list.html.twig +++ b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/AccompanyingCourse/list.html.twig @@ -95,11 +95,11 @@ {% endif %} -