adaptations for acl with tasks

This commit is contained in:
Julien Fastré 2021-10-26 18:05:06 +02:00
parent bae06fcc9c
commit 965ea528e3
22 changed files with 371 additions and 298 deletions

View File

@ -90,6 +90,10 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface,
$configuration = $this->getConfiguration($configs, $container); $configuration = $this->getConfiguration($configs, $container);
$config = $this->processConfiguration($configuration, $configs); $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', $container->setParameter('chill_main.installation_name',
$config['installation_name']); $config['installation_name']);

View File

@ -16,23 +16,23 @@ use Symfony\Component\HttpFoundation\Request;
*/ */
class Configuration implements ConfigurationInterface class Configuration implements ConfigurationInterface
{ {
use AddWidgetConfigurationTrait; use AddWidgetConfigurationTrait;
/** /**
* *
* @var ContainerBuilder * @var ContainerBuilder
*/ */
private $containerBuilder; private $containerBuilder;
public function __construct(array $widgetFactories = array(), public function __construct(array $widgetFactories = array(),
ContainerBuilder $containerBuilder) ContainerBuilder $containerBuilder)
{ {
$this->setWidgetFactories($widgetFactories); $this->setWidgetFactories($widgetFactories);
$this->containerBuilder = $containerBuilder; $this->containerBuilder = $containerBuilder;
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
@ -97,6 +97,14 @@ class Configuration implements ConfigurationInterface
->end() ->end()
->end() ->end()
->end() ->end()
->arrayNode('acl')
->addDefaultsIfNotSet()
->children()
->booleanNode('form_show_scopes')
->defaultTrue()
->end()
->end()
->end()
->arrayNode('redis') ->arrayNode('redis')
->children() ->children()
->scalarNode('host') ->scalarNode('host')
@ -247,7 +255,7 @@ class Configuration implements ConfigurationInterface
->end() // end of root ->end() // end of root
; ;
return $treeBuilder; return $treeBuilder;
} }
} }

View File

@ -23,7 +23,9 @@ use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Form\DataMapper\ScopePickerDataMapper; use Chill\MainBundle\Form\DataMapper\ScopePickerDataMapper;
use Chill\MainBundle\Repository\ScopeRepository; use Chill\MainBundle\Repository\ScopeRepository;
use Chill\MainBundle\Repository\UserACLAwareRepositoryInterface;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\ORM\EntityRepository; use Doctrine\ORM\EntityRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Bridge\Doctrine\Form\Type\EntityType;
@ -36,6 +38,7 @@ use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Role\Role; use Symfony\Component\Security\Core\Role\Role;
use Symfony\Component\Security\Core\Security;
/** /**
* Allow to pick amongst available scope for the current * 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 * - `center`: the center of the entity
* - `role` : the role of the user * - `role` : the role of the user
* *
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/ */
class ScopePickerType extends AbstractType class ScopePickerType extends AbstractType
{ {
/** protected AuthorizationHelperInterface $authorizationHelper;
* @var AuthorizationHelper
*/
protected $authorizationHelper;
/** /**
* @var TokenStorageInterface * @var TokenStorageInterface
@ -70,22 +69,26 @@ class ScopePickerType extends AbstractType
*/ */
protected $translatableStringHelper; protected $translatableStringHelper;
protected Security $security;
public function __construct( public function __construct(
AuthorizationHelper $authorizationHelper, AuthorizationHelperInterface $authorizationHelper,
TokenStorageInterface $tokenStorage, TokenStorageInterface $tokenStorage,
ScopeRepository $scopeRepository, ScopeRepository $scopeRepository,
Security $security,
TranslatableStringHelper $translatableStringHelper TranslatableStringHelper $translatableStringHelper
) { ) {
$this->authorizationHelper = $authorizationHelper; $this->authorizationHelper = $authorizationHelper;
$this->tokenStorage = $tokenStorage; $this->tokenStorage = $tokenStorage;
$this->scopeRepository = $scopeRepository; $this->scopeRepository = $scopeRepository;
$this->security = $security;
$this->translatableStringHelper = $translatableStringHelper; $this->translatableStringHelper = $translatableStringHelper;
} }
public function buildForm(FormBuilderInterface $builder, array $options) public function buildForm(FormBuilderInterface $builder, array $options)
{ {
$query = $this->buildAccessibleScopeQuery($options['center'], $options['role']); $items = $this->authorizationHelper->getReachableScopes($this->security->getUser(),
$items = $query->getQuery()->execute(); $options['role'], $options['center']);
if (1 !== count($items)) { if (1 !== count($items)) {
$builder->add('scope', EntityType::class, [ $builder->add('scope', EntityType::class, [
@ -94,9 +97,7 @@ class ScopePickerType extends AbstractType
'choice_label' => function (Scope $c) { 'choice_label' => function (Scope $c) {
return $this->translatableStringHelper->localize($c->getName()); return $this->translatableStringHelper->localize($c->getName());
}, },
'query_builder' => function () use ($options) { 'choices' => $items,
return $this->buildAccessibleScopeQuery($options['center'], $options['role']);
},
]); ]);
$builder->setDataMapper(new ScopePickerDataMapper()); $builder->setDataMapper(new ScopePickerDataMapper());
} else { } else {
@ -121,19 +122,22 @@ class ScopePickerType extends AbstractType
$resolver $resolver
// create `center` option // create `center` option
->setRequired('center') ->setRequired('center')
->setAllowedTypes('center', [Center::class]) ->setAllowedTypes('center', [Center::class, 'array', 'null'])
// create ``role` option // create ``role` option
->setRequired('role') ->setRequired('role')
->setAllowedTypes('role', ['string', Role::class]); ->setAllowedTypes('role', ['string', Role::class]);
} }
/** /**
* @param Center|array|Center[] $center
* @param string $role
* @return \Doctrine\ORM\QueryBuilder * @return \Doctrine\ORM\QueryBuilder
*/ */
protected function buildAccessibleScopeQuery(Center $center, Role $role) protected function buildAccessibleScopeQuery($center, $role)
{ {
$roles = $this->authorizationHelper->getParentRoles($role); $roles = $this->authorizationHelper->getParentRoles($role);
$roles[] = $role; $roles[] = $role;
$centers = $center instanceof Center ? [$center]: $center;
$qb = $this->scopeRepository->createQueryBuilder('s'); $qb = $this->scopeRepository->createQueryBuilder('s');
$qb $qb
@ -142,8 +146,8 @@ class ScopePickerType extends AbstractType
->join('rs.permissionsGroups', 'pg') ->join('rs.permissionsGroups', 'pg')
->join('pg.groupCenters', 'gc') ->join('pg.groupCenters', 'gc')
// add center constraint // add center constraint
->where($qb->expr()->eq('IDENTITY(gc.center)', ':center')) ->where($qb->expr()->in('IDENTITY(gc.center)', ':centers'))
->setParameter('center', $center->getId()) ->setParameter('centers', \array_map(fn(Center $c) => $c->getId(), $centers))
// role constraints // role constraints
->andWhere($qb->expr()->in('rs.role', ':roles')) ->andWhere($qb->expr()->in('rs.role', ':roles'))
->setParameter('roles', $roles) ->setParameter('roles', $roles)

View File

@ -17,6 +17,8 @@
*/ */
namespace Chill\MainBundle\Form\Type; namespace Chill\MainBundle\Form\Type;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Repository\UserACLAwareRepositoryInterface;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Doctrine\ORM\EntityRepository; use Doctrine\ORM\EntityRepository;
@ -56,14 +58,18 @@ class UserPickerType extends AbstractType
protected UserRepository $userRepository; protected UserRepository $userRepository;
protected UserACLAwareRepositoryInterface $userACLAwareRepository;
public function __construct( public function __construct(
AuthorizationHelper $authorizationHelper, AuthorizationHelper $authorizationHelper,
TokenStorageInterface $tokenStorage, TokenStorageInterface $tokenStorage,
UserRepository $userRepository UserRepository $userRepository,
UserACLAwareRepositoryInterface $userACLAwareRepository
) { ) {
$this->authorizationHelper = $authorizationHelper; $this->authorizationHelper = $authorizationHelper;
$this->tokenStorage = $tokenStorage; $this->tokenStorage = $tokenStorage;
$this->userRepository = $userRepository; $this->userRepository = $userRepository;
$this->userACLAwareRepository = $userACLAwareRepository;
} }
@ -72,7 +78,7 @@ class UserPickerType extends AbstractType
$resolver $resolver
// create `center` option // create `center` option
->setRequired('center') ->setRequired('center')
->setAllowedTypes('center', [\Chill\MainBundle\Entity\Center::class ]) ->setAllowedTypes('center', [\Chill\MainBundle\Entity\Center::class, 'null', 'array' ])
// create ``role` option // create ``role` option
->setRequired('role') ->setRequired('role')
->setAllowedTypes('role', ['string', \Symfony\Component\Security\Core\Role\Role::class ]) ->setAllowedTypes('role', ['string', \Symfony\Component\Security\Core\Role\Role::class ])
@ -86,17 +92,19 @@ class UserPickerType extends AbstractType
->setDefault('choice_label', function(User $u) { ->setDefault('choice_label', function(User $u) {
return $u->getUsername(); return $u->getUsername();
}) })
->setDefault('scope', null)
->setAllowedTypes('scope', [Scope::class, 'array', 'null'])
->setNormalizer('choices', function(Options $options) { ->setNormalizer('choices', function(Options $options) {
$users = $this->authorizationHelper $users = $this->userACLAwareRepository
->findUsersReaching($options['role'], $options['center']); ->findUsersByReachedACL($options['role'], $options['center'], $options['scope'], true);
if (NULL !== $options['having_permissions_group_flag']) { if (NULL !== $options['having_permissions_group_flag']) {
return $this->userRepository return $this->userRepository
->findUsersHavingFlags($options['having_permissions_group_flag'], $users) ->findUsersHavingFlags($options['having_permissions_group_flag'], $users)
; ;
} }
return $users; return $users;
}) })
; ;

View File

@ -0,0 +1,54 @@
<?php
namespace Chill\MainBundle\Repository;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
class UserACLAwareRepository implements UserACLAwareRepositoryInterface
{
private AuthorizationHelper $authorizationHelper;
public function findUsersByReachedACL(string $role, $center, $scope = null, bool $onlyEnabled = true): array
{
$parents = $this->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();
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace Chill\MainBundle\Repository;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User;
interface UserACLAwareRepositoryInterface
{
/**
* Find the users reaching the given center and scope, for the given role
*
* @param string $role
* @param Center|Center[]|array $center
* @param Scope|Scope[]|array|null $scope
* @param bool $onlyActive true if get only active users
*
* @return User[]
*/
public function findUsersByReachedACL(string $role, $center, $scope = null, bool $onlyActive = true): array;
}

View File

@ -8,7 +8,7 @@
{{ form_start(form) }} {{ form_start(form) }}
<ul class="record_actions"> <ul class="record_actions sticky-form-buttons">
<li class="cancel"> <li class="cancel">
<a href="{{ path(cancel_route, cancel_parameters|default( { } ) ) }}" class="btn btn-cancel"> <a href="{{ path(cancel_route, cancel_parameters|default( { } ) ) }}" class="btn btn-cancel">
{{ 'Cancel'|trans }} {{ 'Cancel'|trans }}
@ -18,5 +18,5 @@
{{ form_widget(form.submit, { 'attr' : { 'class' : "btn btn-delete" } } ) }} {{ form_widget(form.submit, { 'attr' : { 'class' : "btn btn-delete" } } ) }}
</li> </li>
</ul> </ul>
{{ form_end(form) }} {{ form_end(form) }}

View File

@ -23,6 +23,8 @@ use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\HasCenterInterface; use Chill\MainBundle\Entity\HasCenterInterface;
use Chill\MainBundle\Entity\HasScopeInterface; 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\CenterResolverDispatcher;
use Chill\MainBundle\Security\Resolver\ScopeResolverDispatcher; use Chill\MainBundle\Security\Resolver\ScopeResolverDispatcher;
use Chill\MainBundle\Security\Resolver\ScopeResolverInterface; use Chill\MainBundle\Security\Resolver\ScopeResolverInterface;
@ -62,6 +64,8 @@ class AuthorizationHelper implements AuthorizationHelperInterface
protected LoggerInterface $logger; protected LoggerInterface $logger;
private UserACLAwareRepositoryInterface $userACLAwareRepository;
public function __construct( public function __construct(
RoleHierarchyInterface $roleHierarchy, RoleHierarchyInterface $roleHierarchy,
ParameterBagInterface $parameterBag, 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[] * @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); return $this->userACLAwareRepository->findUsersByReachedACL($role, $center, $scope, $onlyEnabled);
$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();
} }
/** /**

View File

@ -11,6 +11,8 @@ services:
autowire: true autowire: true
autoconfigure: true autoconfigure: true
Chill\MainBundle\Repository\UserACLAwareRepositoryInterface: '@Chill\MainBundle\Repository\UserACLAwareRepository'
Chill\MainBundle\Serializer\Normalizer\: Chill\MainBundle\Serializer\Normalizer\:
resource: '../Serializer/Normalizer' resource: '../Serializer/Normalizer'
autoconfigure: true autoconfigure: true

View File

@ -3,9 +3,12 @@
namespace Chill\TaskBundle\Controller; namespace Chill\TaskBundle\Controller;
use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher;
use Chill\MainBundle\Security\Resolver\CenterResolverInterface;
use Chill\PersonBundle\Privacy\PrivacyEvent; use Chill\PersonBundle\Privacy\PrivacyEvent;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Annotation\Route;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
@ -24,7 +27,6 @@ use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Chill\PersonBundle\Repository\PersonRepository; use Chill\PersonBundle\Repository\PersonRepository;
use Chill\TaskBundle\Event\TaskEvent; use Chill\TaskBundle\Event\TaskEvent;
use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Translation\TranslatorInterface;
use Chill\TaskBundle\Event\UI\UIEvent; use Chill\TaskBundle\Event\UI\UIEvent;
use Chill\MainBundle\Repository\CenterRepository; use Chill\MainBundle\Repository\CenterRepository;
use Chill\MainBundle\Timeline\TimelineBuilder; use Chill\MainBundle\Timeline\TimelineBuilder;
@ -33,6 +35,7 @@ use Chill\PersonBundle\Repository\AccompanyingPeriodRepository;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter; use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Contracts\Translation\TranslatorInterface;
use Symfony\Contracts\Translation\TranslatorInterface as TranslationTranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface as TranslationTranslatorInterface;
/** /**
@ -40,126 +43,100 @@ use Symfony\Contracts\Translation\TranslatorInterface as TranslationTranslatorIn
* *
* @package Chill\TaskBundle\Controller * @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;
/** private EventDispatcherInterface $eventDispatcher;
* @var RequestStack private TimelineBuilder $timelineBuilder;
*/ private LoggerInterface $logger;
protected $request; private CenterResolverDispatcher $centerResolverDispatcher;
private TranslatorInterface $translator;
/**
* SingleTaskController constructor.
*
* @param EventDispatcherInterface $eventDispatcher
*/
public function __construct( public function __construct(
CenterResolverDispatcher $centerResolverDispatcher,
TranslatorInterface $translator,
EventDispatcherInterface $eventDispatcher, EventDispatcherInterface $eventDispatcher,
TimelineBuilder $timelineBuilder, TimelineBuilder $timelineBuilder,
LoggerInterface $logger, LoggerInterface $logger
RequestStack $requestStack
) { ) {
$this->eventDispatcher = $eventDispatcher; $this->eventDispatcher = $eventDispatcher;
$this->timelineBuilder = $timelineBuilder; $this->timelineBuilder = $timelineBuilder;
$this->logger = $logger; $this->logger = $logger;
$this->request = $requestStack->getCurrentRequest(); $this->translator = $translator;
$this->centerResolverDispatcher = $centerResolverDispatcher;
} }
private function getEntityContext(Request $request)
private function getEntityContext()
{ {
if($this->request->query->has('person_id')){ if ($request->query->has('person_id')) {
return 'person'; return 'person';
} else if ($this->request->query->has('course_id')) { } else if ($request->query->has('course_id')) {
return 'course'; return 'course';
} else { } else {
return null; return null;
} }
} }
/** /**
* @Route( * @Route(
* "/{_locale}/task/single-task/new", * "/{_locale}/task/single-task/new",
* name="chill_task_single_task_new" * name="chill_task_single_task_new"
* ) * )
*/ */
public function newAction( public function newAction(Request $request) {
TranslationTranslatorInterface $translator
) {
$task = (new SingleTask()) $task = (new SingleTask())
->setAssignee($this->getUser()) ->setAssignee($this->getUser())
->setType('task_default') ->setType('task_default')
; ;
$entityType = $this->getEntityContext();
if ($entityType !== null) { $entityType = $this->getEntityContext($request);
$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`
if ($entityId === null) { if (NULL === $entityType) {
return new Response("You must provide a {$entityType}_id", Response::HTTP_BAD_REQUEST); 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() $person = $this->getDoctrine()->getManager()
->getRepository(Person::class) ->getRepository(Person::class)
->find($entityId); ->find($entityId);
if ($person === null) { if ($person === null) {
$this->createNotFoundException("Invalid person id"); $this->createNotFoundException("Invalid person id");
} }
$task->setPerson($person); $task->setPerson($person);
} break;
case 'course':
if($entityType === 'course')
{
$course = $this->getDoctrine()->getManager() $course = $this->getDoctrine()->getManager()
->getRepository(AccompanyingPeriod::class) ->getRepository(AccompanyingPeriod::class)
->find($entityId); ->find($entityId);
if($course === null) { if ($course === null) {
$this->createNotFoundException("Invalid accompanying course id"); $this->createNotFoundException("Invalid accompanying course id");
} }
$task->setCourse($course); $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 = $this->setCreateForm($task, new Role(TaskVoter::CREATE));
$form->handleRequest($this->request); $form->handleRequest($request);
if ($form->isSubmitted()) { if ($form->isSubmitted()) {
if ($form->isValid()) { if ($form->isValid()) {
$em = $this->getDoctrine()->getManager(); $em = $this->getDoctrine()->getManager();
@ -169,44 +146,38 @@ class SingleTaskController extends AbstractController
$em->flush(); $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', [ return $this->redirectToRoute('chill_task_singletask_list', [
'person_id' => $task->getPerson()->getId() 'person_id' => $task->getPerson()->getId()
]); ]);
} } elseif ($entityType === 'course') {
if($entityType === 'course')
{
return $this->redirectToRoute('chill_task_singletask_courselist', [ return $this->redirectToRoute('chill_task_singletask_courselist', [
'course_id' => $task->getCourse()->getId() 'course_id' => $task->getCourse()->getId()
]); ]);
} }
} else { } else {
$this->addFlash('error', $translator->trans("This form contains errors")); $this->addFlash('error', $this->translator->trans("This form contains errors"));
} }
} }
switch($this->getEntityContext()){ switch ($entityType) {
case 'person': case 'person':
return $this->render('@ChillTask/SingleTask/Person/new.html.twig', array( return $this->render('@ChillTask/SingleTask/Person/new.html.twig', array(
'form' => $form->createView(), 'form' => $form->createView(),
'task' => $task, 'task' => $task,
'person' => $person, 'person' => $person,
)); ));
break;
case 'course': case 'course':
return $this->render('@ChillTask/SingleTask/AccompanyingCourse/new.html.twig', array( return $this->render('@ChillTask/SingleTask/AccompanyingCourse/new.html.twig', array(
'form' => $form->createView(), 'form' => $form->createView(),
'task' => $task, 'task' => $task,
'accompanyingCourse' => $course, '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" * name="chill_task_single_task_show"
* ) * )
*/ */
public function showAction($id) public function showAction($id, Request $request)
{ {
$em = $this->getDoctrine()->getManager(); $em = $this->getDoctrine()->getManager();
$task = $em->getRepository(SingleTask::class)->find($id); $task = $em->getRepository(SingleTask::class)->find($id);
@ -226,7 +197,7 @@ class SingleTaskController extends AbstractController
} }
if ($task->getPerson() !== null) { if ($task->getPerson() !== null) {
$personId = $task->getPerson()->getId(); $personId = $task->getPerson()->getId();
if ($personId === null) { if ($personId === null) {
@ -250,7 +221,7 @@ class SingleTaskController extends AbstractController
} }
if ($task->getCourse() !== null) if ($task->getCourse() !== null)
{ {
$courseId = $task->getCourse()->getId(); $courseId = $task->getCourse()->getId();
@ -274,7 +245,7 @@ class SingleTaskController extends AbstractController
$timeline = $this->timelineBuilder $timeline = $this->timelineBuilder
->getTimelineHTML('task', array('task' => $task)); ->getTimelineHTML('task', array('task' => $task));
if($task->getContext() instanceof Person){ if($task->getContext() instanceof Person){
return $this->render('@ChillTask/SingleTask/Person/show.html.twig', array( return $this->render('@ChillTask/SingleTask/Person/show.html.twig', array(
@ -299,9 +270,9 @@ class SingleTaskController extends AbstractController
*/ */
public function editAction( public function editAction(
$id, $id,
TranslationTranslatorInterface $translator Request $request
) { ) {
$em = $this->getDoctrine()->getManager(); $em = $this->getDoctrine()->getManager();
$task = $em->getRepository(SingleTask::class)->find($id); $task = $em->getRepository(SingleTask::class)->find($id);
@ -339,16 +310,16 @@ class SingleTaskController extends AbstractController
if (!$task) { if (!$task) {
throw $this->createNotFoundException('Unable to find Task entity.'); throw $this->createNotFoundException('Unable to find Task entity.');
} }
$event = (new UIEvent('single-task', $task)) $event = (new UIEvent('single-task', $task))
->setForm($this->setCreateForm($task, new Role(TaskVoter::UPDATE))) ->setForm($this->setCreateForm($task, new Role(TaskVoter::UPDATE)))
; ;
$this->eventDispatcher->dispatch(UIEvent::EDIT_FORM, $event); $this->eventDispatcher->dispatch(UIEvent::EDIT_FORM, $event);
$form = $event->getForm(); $form = $event->getForm();
$form->handleRequest($this->request); $form->handleRequest($request);
if ($form->isSubmitted()) { if ($form->isSubmitted()) {
if ($form->isValid()) { if ($form->isValid()) {
@ -357,7 +328,7 @@ class SingleTaskController extends AbstractController
$em->flush(); $em->flush();
$this->addFlash('success', $translator $this->addFlash('success', $this->translator
->trans("The task has been updated")); ->trans("The task has been updated"));
if($task->getContext() instanceof Person){ if($task->getContext() instanceof Person){
@ -369,26 +340,26 @@ class SingleTaskController extends AbstractController
$this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event); $this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
return $this->redirectToRoute( return $this->redirectToRoute(
'chill_task_singletask_list', 'chill_task_singletask_list',
$this->request->query->get('list_params', []) $request->query->get('list_params', [])
); );
} else { } else {
return $this->redirectToRoute( return $this->redirectToRoute(
'chill_task_singletask_courselist', 'chill_task_singletask_courselist',
$this->request->query->get('list_params', []) $request->query->get('list_params', [])
); );
} }
} else { } 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); $this->eventDispatcher->dispatch(UIEvent::EDIT_PAGE, $event);
if ($event->hasResponse()) { if ($event->hasResponse()) {
return $event->getResponse(); return $event->getResponse();
} }
if($task->getContext() instanceof Person){ if($task->getContext() instanceof Person){
$event = new PrivacyEvent($person, array( $event = new PrivacyEvent($person, array(
'element_class' => SingleTask::class, 'element_class' => SingleTask::class,
@ -420,10 +391,9 @@ class SingleTaskController extends AbstractController
*/ */
public function deleteAction( public function deleteAction(
Request $request, Request $request,
$id, $id
TranslationTranslatorInterface $translator
) { ) {
$em = $this->getDoctrine()->getManager(); $em = $this->getDoctrine()->getManager();
$task = $em->getRepository(SingleTask::class)->find($id); $task = $em->getRepository(SingleTask::class)->find($id);
@ -471,7 +441,7 @@ class SingleTaskController extends AbstractController
$form->handleRequest($request); $form->handleRequest($request);
if ($form->isValid()) { if ($form->isValid()) {
$this->logger->notice("A task has been removed", array( $this->logger->notice("A task has been removed", array(
'by_user' => $this->getUser()->getUsername(), 'by_user' => $this->getUser()->getUsername(),
'task_id' => $task->getId(), 'task_id' => $task->getId(),
@ -485,18 +455,18 @@ class SingleTaskController extends AbstractController
$em->remove($task); $em->remove($task);
$em->flush(); $em->flush();
$this->addFlash('success', $translator $this->addFlash('success', $this->translator
->trans("The task has been successfully removed.")); ->trans("The task has been successfully removed."));
if($task->getContext() instanceof Person){ if($task->getContext() instanceof Person){
return $this->redirect($this->generateUrl( return $this->redirect($this->generateUrl(
'chill_task_singletask_list', 'chill_task_singletask_list',
$request->query->get('list_params', [ $request->query->get('list_params', [
'person_id' => $person->getId() 'person_id' => $person->getId()
]))); ])));
} else { } else {
return $this->redirect($this->generateUrl( return $this->redirect($this->generateUrl(
'chill_task_singletask_courselist', 'chill_task_singletask_courselist',
$request->query->get('list_params', [ $request->query->get('list_params', [
'course_id' => $course->getId() 'course_id' => $course->getId()
]))); ])));
@ -528,7 +498,7 @@ class SingleTaskController extends AbstractController
protected function setCreateForm(SingleTask $task, Role $role) protected function setCreateForm(SingleTask $task, Role $role)
{ {
$form = $this->createForm(SingleTaskType::class, $task, [ $form = $this->createForm(SingleTaskType::class, $task, [
'center' => $task->getCenter(), 'center' => $this->centerResolverDispatcher->resolveCenter($task),
'role' => $role, 'role' => $role,
]); ]);
@ -545,12 +515,12 @@ class SingleTaskController extends AbstractController
* name="chill_task_single_my_tasks" * name="chill_task_single_my_tasks"
* ) * )
*/ */
public function myTasksAction(TranslationTranslatorInterface $translator) public function myTasksAction()
{ {
return $this->redirectToRoute('chill_task_singletask_list', [ return $this->redirectToRoute('chill_task_singletask_list', [
'user_id' => $this->getUser()->getId(), 'user_id' => $this->getUser()->getId(),
'hide_form' => true, 'hide_form' => true,
'title' => $translator->trans('My tasks') 'title' => $this->translator->trans('My tasks')
]); ]);
} }
@ -575,7 +545,8 @@ class SingleTaskController extends AbstractController
PersonRepository $personRepository, PersonRepository $personRepository,
AccompanyingPeriodRepository $courseRepository, AccompanyingPeriodRepository $courseRepository,
CenterRepository $centerRepository, CenterRepository $centerRepository,
FormFactoryInterface $formFactory FormFactoryInterface $formFactory,
Request $request
) { ) {
/* @var $viewParams array The parameters for the view */ /* @var $viewParams array The parameters for the view */
/* @var $params array The parameters for the query */ /* @var $params array The parameters for the query */
@ -589,9 +560,9 @@ class SingleTaskController extends AbstractController
$viewParams['accompanyingCourse'] = null; $viewParams['accompanyingCourse'] = null;
$params['accompanyingCourse'] = null; $params['accompanyingCourse'] = null;
if (!empty($this->request->query->get('person_id', NULL))) { if (!empty($request->query->get('person_id', NULL))) {
$personId = $this->request->query->getInt('person_id', 0); $personId = $request->query->getInt('person_id', 0);
$person = $personRepository->find($personId); $person = $personRepository->find($personId);
if ($person === null) { if ($person === null) {
@ -603,9 +574,9 @@ class SingleTaskController extends AbstractController
$params['person'] = $person; $params['person'] = $person;
} }
if (!empty($this->request->query->get('course_id', NULL))) { if (!empty($request->query->get('course_id', NULL))) {
$courseId = $this->request->query->getInt('course_id', 0); $courseId = $request->query->getInt('course_id', 0);
$course = $courseRepository->find($courseId); $course = $courseRepository->find($courseId);
if ($course === null) { if ($course === null) {
@ -616,28 +587,28 @@ class SingleTaskController extends AbstractController
$viewParams['accompanyingCourse'] = $course; $viewParams['accompanyingCourse'] = $course;
$params['accompanyingCourse'] = $course; $params['accompanyingCourse'] = $course;
} }
if (!empty($this->request->query->get('center_id', NULL))) { if (!empty($request->query->get('center_id', NULL))) {
$center = $centerRepository->find($this->request->query->getInt('center_id')); $center = $centerRepository->find($request->query->getInt('center_id'));
if ($center === null) { if ($center === null) {
throw $this->createNotFoundException('center not found'); throw $this->createNotFoundException('center not found');
} }
$params['center'] = $center; $params['center'] = $center;
} }
if(!empty($this->request->query->get('types', []))) { if(!empty($request->query->get('types', []))) {
$types = $this->request->query->get('types', []); $types = $request->query->get('types', []);
if (count($types) > 0) { if (count($types) > 0) {
$params['types'] = $types; $params['types'] = $types;
} }
} }
if (!empty($this->request->query->get('user_id', null))) { if (!empty($request->query->get('user_id', null))) {
if ($this->request->query->get('user_id') === '_unassigned') { if ($request->query->get('user_id') === '_unassigned') {
$params['unassigned'] = true; $params['unassigned'] = true;
} else { } else {
$userId = $this->request->query->getInt('user_id', 0); $userId = $request->query->getInt('user_id', 0);
$user = $this->getDoctrine()->getManager() $user = $this->getDoctrine()->getManager()
->getRepository(User::class) ->getRepository(User::class)
->find($userId); ->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() $scope = $this->getDoctrine()->getManager()
->getRepository(Scope::class) ->getRepository(Scope::class)
->find($scopeId); ->find($scopeId);
@ -668,7 +639,7 @@ class SingleTaskController extends AbstractController
} }
$possibleStatuses = \array_merge(SingleTaskRepository::DATE_STATUSES, [ 'closed' ]); $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); $diff = \array_diff($statuses, $possibleStatuses);
if (count($diff) > 0) { if (count($diff) > 0) {
@ -683,7 +654,7 @@ class SingleTaskController extends AbstractController
$tasks_count = 0; $tasks_count = 0;
foreach($statuses as $status) { foreach($statuses as $status) {
if($this->request->query->has('status') if($request->query->has('status')
&& FALSE === \in_array($status, $statuses)) { && FALSE === \in_array($status, $statuses)) {
continue; continue;
} }
@ -729,8 +700,8 @@ class SingleTaskController extends AbstractController
'add_type' => true 'add_type' => true
]); ]);
$form->handleRequest($this->request); $form->handleRequest($request);
if (isset($person)) { if (isset($person)) {
$event = new PrivacyEvent($person, array( $event = new PrivacyEvent($person, array(
'element_class' => SingleTask::class, 'element_class' => SingleTask::class,
@ -738,7 +709,7 @@ class SingleTaskController extends AbstractController
)); ));
$this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event); $this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
} }
return $this->render('@ChillTask/SingleTask/index.html.twig', return $this->render('@ChillTask/SingleTask/index.html.twig',
array_merge($viewParams, [ 'form' => $form->createView() ])); array_merge($viewParams, [ 'form' => $form->createView() ]));
} }
@ -747,7 +718,7 @@ class SingleTaskController extends AbstractController
protected function getPersonParam(EntityManagerInterface $em) protected function getPersonParam(EntityManagerInterface $em)
{ {
$person = $em->getRepository(Person::class) $person = $em->getRepository(Person::class)
->find($this->request->query->getInt('person_id')) ->find($request->query->getInt('person_id'))
; ;
if (NULL === $person) { if (NULL === $person) {
@ -763,7 +734,7 @@ class SingleTaskController extends AbstractController
protected function getUserParam(EntityManagerInterface $em) protected function getUserParam(EntityManagerInterface $em)
{ {
$user = $em->getRepository(User::class) $user = $em->getRepository(User::class)
->find($this->request->query->getInt('user_id')) ->find($request->query->getInt('user_id'))
; ;
if (NULL === $user) { if (NULL === $user) {
@ -799,16 +770,16 @@ class SingleTaskController extends AbstractController
*/ */
public function listCourseTasks( public function listCourseTasks(
AccompanyingPeriodRepository $courseRepository, AccompanyingPeriodRepository $courseRepository,
SingleTaskRepository $taskRepository, SingleTaskRepository $taskRepository,
FormFactoryInterface $formFactory, FormFactoryInterface $formFactory,
TranslationTranslatorInterface $translator Request $request
): Response ): Response
{ {
if (!empty($this->request->query->get('course_id', NULL))) { if (!empty($request->query->get('course_id', NULL))) {
$courseId = $this->request->query->getInt('course_id', 0); $courseId = $request->query->getInt('course_id', 0);
$course = $courseRepository->find($courseId); $course = $courseRepository->find($courseId);
if ($course === null) { if ($course === null) {
@ -834,15 +805,15 @@ class SingleTaskController extends AbstractController
'csrf_protection' => false, 'csrf_protection' => false,
'add_type' => true 'add_type' => true
]); ]);
return $this->render( return $this->render(
'@ChillTask/SingleTask/index.html.twig', '@ChillTask/SingleTask/index.html.twig',
[ [
'tasks' => $tasks, 'tasks' => $tasks,
'accompanyingCourse' => $course, 'accompanyingCourse' => $course,
'layout' => '@ChillPerson/AccompanyingCourse/layout.html.twig', 'layout' => '@ChillPerson/AccompanyingCourse/layout.html.twig',
'form' => $form->createView(), 'form' => $form->createView(),
'title' => $translator->trans('Tasks for this accompanying period') 'title' => $this->translator->trans('Tasks for this accompanying period')
]); ]);
} }

View File

@ -16,10 +16,6 @@ use Chill\PersonBundle\Entity\AccompanyingPeriod;
* AbstractTask * AbstractTask
* *
* @ORM\MappedSuperclass() * @ORM\MappedSuperclass()
* @UserCircleConsistency(
* "CHILL_TASK_TASK_SHOW",
* getUserFunction="getAssignee"
* )
*/ */
abstract class AbstractTask implements HasScopeInterface, HasCenterInterface abstract class AbstractTask implements HasScopeInterface, HasCenterInterface
{ {
@ -52,7 +48,7 @@ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface
* @ORM\Column(name="description", type="text") * @ORM\Column(name="description", type="text")
*/ */
private $description = ''; private $description = '';
/** /**
* *
* @var User * @var User
@ -61,7 +57,7 @@ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface
* ) * )
*/ */
private $assignee; private $assignee;
/** /**
* *
* @var Person * @var Person
@ -78,25 +74,25 @@ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface
*/ */
private $course; private $course;
/** /**
* *
* @var Scope * @var Scope
* @ORM\ManyToOne( * @ORM\ManyToOne(
* targetEntity="\Chill\MainBundle\Entity\Scope" * targetEntity="\Chill\MainBundle\Entity\Scope"
* ) * )
*/ */
private $circle; private $circle;
/** /**
* @var boolean * @var boolean
* @ORM\Column(name="closed", type="boolean", options={ "default"=false }) * @ORM\Column(name="closed", type="boolean", options={ "default"=false })
*/ */
private $closed = false; private $closed = false;
public function __construct() public function __construct()
{ {
} }
/** /**
@ -125,7 +121,7 @@ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface
/** /**
* Set currentStates * Set currentStates
* *
* The current states are sorted in a single array, non associative. * The current states are sorted in a single array, non associative.
* *
* @param $currentStates * @param $currentStates
@ -141,8 +137,8 @@ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface
/** /**
* Get currentStates * Get currentStates
* *
* The states are returned as required by marking store format. * The states are returned as required by marking store format.
* *
* @return array * @return array
*/ */
@ -198,7 +194,7 @@ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface
{ {
return $this->description; return $this->description;
} }
public function getAssignee(): ?User public function getAssignee(): ?User
{ {
return $this->assignee; return $this->assignee;
@ -218,7 +214,7 @@ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface
{ {
return $this->circle; return $this->circle;
} }
public function setAssignee(User $assignee = null) public function setAssignee(User $assignee = null)
{ {
$this->assignee = $assignee; $this->assignee = $assignee;
@ -250,9 +246,9 @@ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface
} else { } else {
return $this->getCourse()->getCenter(); return $this->getCourse()->getCenter();
} }
return null; return null;
} }
public function getContext() public function getContext()
@ -264,7 +260,7 @@ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface
// } // }
return $this->getPerson() ?? $this->getCourse(); return $this->getPerson() ?? $this->getCourse();
} }
public function getScope(): ?\Chill\MainBundle\Entity\Scope public function getScope(): ?\Chill\MainBundle\Entity\Scope
{ {
return $this->getCircle(); return $this->getCircle();
@ -277,9 +273,9 @@ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface
{ {
return $this->closed; return $this->closed;
} }
/** /**
* *
* @param bool $closed * @param bool $closed
*/ */
public function setClosed(bool $closed) public function setClosed(bool $closed)

View File

@ -40,12 +40,12 @@ class SingleTask extends AbstractTask
* *
* @ORM\Column(name="start_date", type="date", nullable=true) * @ORM\Column(name="start_date", type="date", nullable=true)
* @Assert\Date() * @Assert\Date()
* *
* @Assert\Expression( * @Assert\Expression(
* "value === null or this.getEndDate() === null or value < this.getEndDate()", * "value === null or this.getEndDate() === null or value < this.getEndDate()",
* message="The start date must be before the end date" * message="The start date must be before the end date"
* ) * )
* *
* @Assert\Expression( * @Assert\Expression(
* "value === null or this.getWarningDate() === null or this.getWarningDate() > this.getStartDate()", * "value === null or this.getWarningDate() === null or this.getWarningDate() > this.getStartDate()",
* message="The start date must be before warning date" * message="The start date must be before warning date"
@ -66,16 +66,16 @@ class SingleTask extends AbstractTask
* and this.getEndDate() === null * and this.getEndDate() === null
* *
* @ORM\Column(name="warning_interval", type="dateinterval", nullable=true) * @ORM\Column(name="warning_interval", type="dateinterval", nullable=true)
* *
* @Assert\Expression( * @Assert\Expression(
* "!(value != null and this.getEndDate() == null)", * "!(value != null and this.getEndDate() == null)",
* message="An end date is required if a warning interval is set" * message="An end date is required if a warning interval is set"
* ) * )
* *
* *
*/ */
private $warningInterval; private $warningInterval;
/** /**
* *
* @var RecurringTask * @var RecurringTask
@ -85,7 +85,7 @@ class SingleTask extends AbstractTask
* ) * )
*/ */
private $recurringTask; private $recurringTask;
/** /**
* *
* @var \Doctrine\Common\Collections\Collection * @var \Doctrine\Common\Collections\Collection
@ -96,15 +96,15 @@ class SingleTask extends AbstractTask
* ) * )
*/ */
private $taskPlaceEvents; private $taskPlaceEvents;
public function __construct() public function __construct()
{ {
$this->taskPlaceEvents = $events = new \Doctrine\Common\Collections\ArrayCollection; $this->taskPlaceEvents = $events = new \Doctrine\Common\Collections\ArrayCollection;
parent::__construct(); parent::__construct();
} }
/** /**
* Get id * Get id
* *
@ -186,13 +186,13 @@ class SingleTask extends AbstractTask
{ {
return $this->warningInterval; 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 * end date and the warning interval
* *
* Return null if warningDate or endDate is null * Return null if warningDate or endDate is null
* *
* @return \DateTimeImmutable * @return \DateTimeImmutable
*/ */
public function getWarningDate() public function getWarningDate()
@ -200,15 +200,15 @@ class SingleTask extends AbstractTask
if ($this->getWarningInterval() === null) { if ($this->getWarningInterval() === null) {
return null; return null;
} }
if ($this->getEndDate() === null) { if ($this->getEndDate() === null) {
return null; return null;
} }
return \DateTimeImmutable::createFromMutable($this->getEndDate()) return \DateTimeImmutable::createFromMutable($this->getEndDate())
->sub($this->getWarningInterval()); ->sub($this->getWarningInterval());
} }
function getRecurringTask(): RecurringTask function getRecurringTask(): RecurringTask
{ {
return $this->recurringTask; return $this->recurringTask;
@ -227,7 +227,7 @@ class SingleTask extends AbstractTask
public function setTaskPlaceEvents(Collection $taskPlaceEvents) public function setTaskPlaceEvents(Collection $taskPlaceEvents)
{ {
$this->taskPlaceEvents = $taskPlaceEvents; $this->taskPlaceEvents = $taskPlaceEvents;
return $this; return $this;
} }
} }

View File

@ -17,6 +17,9 @@
*/ */
namespace Chill\TaskBundle\Form; 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\AbstractType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Chill\MainBundle\Form\Type\ChillDateType; 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\DateIntervalType;
use Chill\MainBundle\Form\Type\ChillTextareaType; use Chill\MainBundle\Form\Type\ChillTextareaType;
/**
*
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class SingleTaskType extends AbstractType 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) public function buildForm(FormBuilderInterface $builder, array $options)
{ {
if (NULL !== $task = $options['data']) {
$center = $this->centerResolverDispatcher->resolveCenter($task);
$isScopeConcerned = $this->scopeResolverDispatcher->isConcerned($task);
}
$builder $builder
->add('title', TextType::class) ->add('title', TextType::class)
->add('description', ChillTextareaType::class, [ ->add('description', ChillTextareaType::class, [
@ -45,15 +59,10 @@ class SingleTaskType extends AbstractType
]) ])
->add('assignee', UserPickerType::class, [ ->add('assignee', UserPickerType::class, [
'required' => false, 'required' => false,
'center' => $options['center'], 'center' => $center,
'role' => $options['role'], 'role' => $options['role'],
'placeholder' => 'Not assigned' 'placeholder' => 'Not assigned'
]) ])
->add('circle', ScopePickerType::class, [
'center' => $options['center'],
'role' => $options['role'],
'required' => false
])
->add('startDate', ChillDateType::class, [ ->add('startDate', ChillDateType::class, [
'required' => false 'required' => false
]) ])
@ -62,16 +71,26 @@ class SingleTaskType extends AbstractType
]) ])
->add('warningInterval', DateIntervalType::class, [ ->add('warningInterval', DateIntervalType::class, [
'required' => false '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) public function configureOptions(OptionsResolver $resolver)
{ {
$resolver $resolver
->setRequired('center') ->setRequired('center')
->setAllowedTypes('center', [ Center::class ]) ->setAllowedTypes('center', [ Center::class, 'array', 'null' ])
->setRequired('role') ->setRequired('role')
->setAllowedTypes('role', [ Role::class ]) ->setAllowedTypes('role', [ Role::class, 'string' ])
; ;
} }
} }

View File

@ -10,7 +10,7 @@
{{ include('@ChillMain/Util/confirmation_template.html.twig', {{ include('@ChillMain/Util/confirmation_template.html.twig',
{ {
'title' : 'Remove task'|trans, '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_route' : 'chill_task_singletask_courselist',
'cancel_parameters' : app.request.query.get('list_params', { } ), 'cancel_parameters' : app.request.query.get('list_params', { } ),
'form' : delete_form, 'form' : delete_form,

View File

@ -95,11 +95,11 @@
</table> </table>
{% endif %} {% endif %}
<ul class="record_actions"> <ul class="record_actions sticky-form-buttons">
<li> <li>
{% if accompanyingCourse is not null %} {% if accompanyingCourse is not null %}
<a href="{{ path('chill_task_single_task_new', {'course_id': accompanyingCourse.id}) }}" class="btn btn-create"> <a href="{{ path('chill_task_single_task_new', {'course_id': accompanyingCourse.id}) }}" class="btn btn-create">
{{ 'Add a new task' | trans }} {{ 'Create' | trans }}
</a> </a>
{% endif %} {% endif %}
</li> </li>

View File

@ -237,17 +237,11 @@
{% endif %} {% endif %}
{% if isSingleStatus == false %} {% if isSingleStatus == false %}
<ul class="record_actions"> <ul class="record_actions sticky-form-buttons">
<li> <li>
{% if person is not null and is_granted('CHILL_TASK_TASK_CREATE', person) %} {% if person is not null and is_granted('CHILL_TASK_TASK_CREATE', person) %}
<a href="{{ path('chill_task_single_task_new', {'person_id': person.id}) }}" class="btn btn-create"> <a href="{{ path('chill_task_single_task_new', {'person_id': person.id}) }}" class="btn btn-create">
{{ 'Add a new task' | trans }} {{ 'Create' | trans }}
</a>
{% endif %}
{% if accompanyingCourse is not null %}
<a href="{{ path('chill_task_single_task_new', {'course_id': accompanyingCourse.id}) }}" class="btn btn-create">
{{ 'Add a new task' | trans }}
</a> </a>
{% endif %} {% endif %}
</li> </li>

View File

@ -7,14 +7,16 @@
{{ form_row(form.title) }} {{ form_row(form.title) }}
{{ form_row(form.description) }} {{ form_row(form.description) }}
{{ form_row(form.assignee) }} {{ form_row(form.assignee) }}
{{ form_row(form.circle) }} {% if form.circle is defined %}
{{ form_row(form.circle) }}
{% endif %}
{{ form_row(form.startDate) }} {{ form_row(form.startDate) }}
{{ form_row(form.endDate) }} {{ form_row(form.endDate) }}
{{ form_row(form.warningInterval) }} {{ form_row(form.warningInterval) }}
<ul class="record_actions sticky-form-buttons"> <ul class="record_actions sticky-form-buttons">
<li class="cancel"> <li class="cancel">
<a class="btn btn-cancel" href={% if task.person is not null %} "{{ path('chill_task_singletask_list', { 'person_id': task.person.id, 'list_params': app.request.query.get('list_params', {} )} ) }}" {% else %} "{{ chill_return_path_or('chill_task_singletask_courselist', {'course_id': task.course.id}) }}" {% endif %}> <a class="btn btn-cancel" href={% if task.person is not null %}"{{ chill_return_path_or('chill_task_singletask_list', { 'person_id': task.person.id } ) }}"{% else %}"{{ chill_return_path_or('chill_task_singletask_courselist', {'course_id': task.course.id}) }}" {% endif %}>
{{ 'Cancel'|trans }}</a> {{ 'Cancel'|trans }}</a>
</li> </li>
<li> <li>

View File

@ -9,7 +9,9 @@
{{ form_row(form.title) }} {{ form_row(form.title) }}
{{ form_row(form.description) }} {{ form_row(form.description) }}
{{ form_row(form.assignee) }} {{ form_row(form.assignee) }}
{{ form_row(form.circle) }} {% if form.circle is defined %}
{{ form_row(form.circle) }}
{% endif %}
{{ form_row(form.startDate) }} {{ form_row(form.startDate) }}
{{ form_row(form.endDate) }} {{ form_row(form.endDate) }}
{{ form_row(form.warningInterval) }} {{ form_row(form.warningInterval) }}

View File

@ -65,7 +65,9 @@ final class TaskVoter extends AbstractChillVoter implements ProvideRoleHierarchy
protected VoterHelperInterface $voter; protected VoterHelperInterface $voter;
public function __construct( public function __construct(
VoterHelperFactoryInterface $voterHelperFactory,
AccessDecisionManagerInterface $accessDecisionManager, AccessDecisionManagerInterface $accessDecisionManager,
AuthorizationHelper $authorizationHelper, AuthorizationHelper $authorizationHelper,
EventDispatcherInterface $eventDispatcher, EventDispatcherInterface $eventDispatcher,
@ -82,7 +84,8 @@ final class TaskVoter extends AbstractChillVoter implements ProvideRoleHierarchy
$this->voter = $voterFactory $this->voter = $voterFactory
->generate(AbstractTask::class) ->generate(AbstractTask::class)
->addCheckFor(AbstractTask::class, self::ROLES) ->addCheckFor(AbstractTask::class, self::ROLES)
->addCheckFor(Person::class, [self::SHOW]) ->addCheckFor(Person::class, [self::SHOW, self::CREATE])
->addCheckFor(AccompanyingPeriod::class, [self::SHOW, self::CREATE])
->addCheckFor(null, [self::SHOW]) ->addCheckFor(null, [self::SHOW])
->build() ->build()
; ;
@ -91,14 +94,6 @@ final class TaskVoter extends AbstractChillVoter implements ProvideRoleHierarchy
public function supports($attribute, $subject) public function supports($attribute, $subject)
{ {
return $this->voter->supports($attribute, $subject); return $this->voter->supports($attribute, $subject);
/*
return ($subject instanceof AbstractTask && in_array($attribute, self::ROLES))
||
($subject instanceof Person && \in_array($attribute, [ self::CREATE, self::SHOW ]))
||
(NULL === $subject && $attribute === self::SHOW )
;
*/
} }
/** /**
@ -134,19 +129,26 @@ final class TaskVoter extends AbstractChillVoter implements ProvideRoleHierarchy
// do pre-flight check, relying on other decision manager // do pre-flight check, relying on other decision manager
// those pre-flight check concern associated entities // those pre-flight check concern associated entities
if ($subject instanceof AbstractTask) { if ($subject instanceof AbstractTask) {
// a user can always see his own tasks
if ($subject->getAssignee() === $token->getUser()) {
return true;
}
if (NULL !== $person = $subject->getPerson()) { if (NULL !== $person = $subject->getPerson()) {
if (!$this->accessDecisionManager->decide($token, [PersonVoter::SEE], $person)) { if (!$this->accessDecisionManager->decide($token, [PersonVoter::SEE], $person)) {
return false; return false;
} }
} elseif (false) { } elseif (NULL !== $period = $subject->getCourse()) {
// here will come the test if the task is associated to an accompanying course if (!$this->accessDecisionManager->decide($token, [AccompanyingPeriodVoter::SEE], $period)) {
return false;
}
} }
} }
// do regular check. // do regular check.
return $this->voter->voteOnAttribute($attribute, $subject, $token); return $this->voter->voteOnAttribute($attribute, $subject, $token);
if ($subject instanceof AbstractTask) { if ($subject instanceof AbstractTask) {
$associated = $subject->getPerson() ?? $subject->getCourse(); $associated = $subject->getPerson() ?? $subject->getCourse();
if ($associated === null) { if ($associated === null) {
@ -179,7 +181,7 @@ final class TaskVoter extends AbstractChillVoter implements ProvideRoleHierarchy
$subject, $subject,
$attribute $attribute
); );
} }
public function getRoles() public function getRoles()

View File

@ -1,12 +1,6 @@
services: services:
Chill\TaskBundle\Controller\: Chill\TaskBundle\Controller\:
resource: "../../Controller" resource: "../../Controller"
tags: ["controller.service_arguments"] autowire: true
autoconfigure: ture
Chill\TaskBundle\Controller\SingleTaskController:
arguments:
$eventDispatcher: '@Symfony\Component\EventDispatcher\EventDispatcherInterface'
$timelineBuilder: "@chill_main.timeline_builder"
$logger: "@chill.main.logger"
$requestStack: '@Symfony\Component\HttpFoundation\RequestStack'
tags: ["controller.service_arguments"] tags: ["controller.service_arguments"]

View File

@ -1,4 +1,9 @@
services: services:
Chill\TaskBundle\Form\:
resource: '../../Form/'
autowire: true
autoconfigure: true
Chill\TaskBundle\Form\SingleTaskListType: Chill\TaskBundle\Form\SingleTaskListType:
arguments: arguments:
$em: '@Doctrine\ORM\EntityManagerInterface' $em: '@Doctrine\ORM\EntityManagerInterface'

View File

@ -41,7 +41,7 @@ User: Utilisateur
"Delete": "Supprimer" "Delete": "Supprimer"
"Change task status": "Changer le statut" "Change task status": "Changer le statut"
'Are you sure you want to remove the task about "%name%" ?': 'Êtes-vous sûr·e de vouloir supprimer la tâche de "%name%"?' 'Are you sure you want to remove the task about "%name%" ?': 'Êtes-vous sûr·e de vouloir supprimer la tâche de "%name%"?'
'Are you sure you want to remove the task about accompanying period "%id%" ?': 'Êtes-vous sûr·e de vouloir supprimer la tâche du parcours "%id%"?' 'Are you sure you want to remove the task "%title%" ?': 'Êtes-vous sûr·e de vouloir supprimer la tâche "%title%" ?'
"See more": "Voir plus" "See more": "Voir plus"
"Associated tasks": "Tâches associées" "Associated tasks": "Tâches associées"
"My tasks": "Mes tâches" "My tasks": "Mes tâches"