adapt UI and controller for Person without centers

This commit is contained in:
Julien Fastré 2021-09-03 12:41:41 +02:00
parent 2450655452
commit 5b70fb2ee5
14 changed files with 196 additions and 53 deletions

View File

@ -95,9 +95,12 @@ class CenterType extends AbstractType
public function configureOptions(OptionsResolver $resolver) public function configureOptions(OptionsResolver $resolver)
{ {
if (count($this->reachableCenters) > 1) { if (count($this->reachableCenters) > 1) {
$resolver->setDefault('class', Center::class); $resolver->setDefault('class', Center::class)
$resolver->setDefault('choices', $this->reachableCenters); ->setDefault('choices', $this->reachableCenters)
->setDefault('placeholder', 'Pick a center')
;
} }
} }
/** /**

View File

@ -0,0 +1,34 @@
<?php
namespace Chill\MainBundle\Security\Resolver;
class CenterResolverDispatcher
{
/**
* @var iterabble|CenterResolverInterface[]
*/
private iterable $resolvers = [];
private CenterResolverInterface $defaultResolver;
public function __construct(iterable $resolvers)
{
$this->resolvers = $resolvers;
}
/**
* @param mixed $entity
* @param array|null $options
* @return null|Center|Center[]
*/
public function resolveCenter($entity, ?array $options = [])
{
foreach($this->resolvers as $priority => $resolver) {
if ($resolver->supports($entity, $options)) {
return $resolver->resolveCenter($entity, $options);
}
}
return null;
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace Chill\MainBundle\Security\Resolver;
use Chill\MainBundle\Entity\Center;
interface CenterResolverInterface
{
public function supports($entity, ?array $options = []): bool;
/**
* @param $entity
* @param array|null $options
* @return Center|array|Center[]
*/
public function resolveCenter($entity, ?array $options = []);
public static function getDefaultPriority(): int;
}

View File

@ -0,0 +1,30 @@
<?php
namespace Chill\MainBundle\Security\Resolver;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\HasCenterInterface;
class DefaultCenterResolver implements CenterResolverInterface
{
public function supports($entity, ?array $options = []): bool
{
return $entity instanceof HasCenterInterface;
}
/**
* @inheritDoc
*
* @param HasCenterInterface $entity
* @param array $options
*/
public function resolveCenter($entity, ?array $options = [])
{
return $entity->getCenter();
}
public static function getDefaultPriority(): int
{
return -256;
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace Chill\MainBundle\Security\Resolver;
use Twig\TwigFilter;
final class ResolverTwigExtension extends \Twig\Extension\AbstractExtension
{
private CenterResolverDispatcher $centerResolverDispatcher;
/**
* @param CenterResolverDispatcher $centerResolverDispatcher
*/
public function __construct(CenterResolverDispatcher $centerResolverDispatcher)
{
$this->centerResolverDispatcher = $centerResolverDispatcher;
}
public function getFilters()
{
return [
new TwigFilter('chill_resolve_center', [$this, 'resolveCenter'])
];
}
/**
* @param mixed $entity
* @param array|null $options
* @return Center|Center[]|null
*/
public function resolveCenter($entity, ?array $options = [])
{
return $this->centerResolverDispatcher->resolveCenter($entity, $options);
}
}

View File

@ -177,6 +177,7 @@ Exports list: Liste des exports
Create an export: Créer un export Create an export: Créer un export
#export creation step 'center' : pick a center #export creation step 'center' : pick a center
Pick centers: Choisir les centres Pick centers: Choisir les centres
Pick a center: Choisir un centre
The export will contains only data from the picked centers.: L'export ne contiendra que les données des centres choisis. The export will contains only data from the picked centers.: L'export ne contiendra que les données des centres choisis.
This will eventually restrict your possibilities in filtering the data.: Les possibilités de filtrages seront adaptées aux droits de consultation pour les centres choisis. This will eventually restrict your possibilities in filtering the data.: Les possibilités de filtrages seront adaptées aux droits de consultation pour les centres choisis.
Go to export options: Vers la préparation de l'export Go to export options: Vers la préparation de l'export

View File

@ -230,13 +230,16 @@ final class PersonController extends AbstractController
*/ */
public function newAction(Request $request) public function newAction(Request $request)
{ {
$defaultCenter = $this->security $person = new Person();
->getUser()
->getGroupCenters()[0]
->getCenter();
$person = (new Person(new \DateTime('now'))) if (1 === count($this->security->getUser()
->setCenter($defaultCenter); ->getGroupCenters())) {
$person->setCenter(
$this->security->getUser()
->getGroupCenters()[0]
->getCenter()
);
}
$form = $this->createForm(CreationPersonType::class, $person, [ $form = $this->createForm(CreationPersonType::class, $person, [
'validation_groups' => ['create'] 'validation_groups' => ['create']

View File

@ -21,7 +21,9 @@
namespace Chill\PersonBundle\Form; namespace Chill\PersonBundle\Form;
use Chill\MainBundle\Form\Event\CustomizeFormEvent;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\OptionsResolver\OptionsResolver;
@ -33,6 +35,7 @@ use Chill\PersonBundle\Form\Type\GenderType;
use Chill\MainBundle\Form\Type\DataTransformer\CenterTransformer; use Chill\MainBundle\Form\Type\DataTransformer\CenterTransformer;
use Chill\PersonBundle\Config\ConfigPersonAltNamesHelper; use Chill\PersonBundle\Config\ConfigPersonAltNamesHelper;
use Chill\PersonBundle\Form\Type\PersonAltNameType; use Chill\PersonBundle\Form\Type\PersonAltNameType;
use Chill\MainBundle\Form\Type\Export\PickCenterType;
final class CreationPersonType extends AbstractType final class CreationPersonType extends AbstractType
{ {
@ -40,10 +43,6 @@ final class CreationPersonType extends AbstractType
// TODO: See if this is still valid and update accordingly. // TODO: See if this is still valid and update accordingly.
const NAME = 'chill_personbundle_person_creation'; const NAME = 'chill_personbundle_person_creation';
const FORM_NOT_REVIEWED = 'not_reviewed';
const FORM_REVIEWED = 'reviewed' ;
const FORM_BEING_REVIEWED = 'being_reviewed';
/** /**
* *
* @var CenterTransformer * @var CenterTransformer
@ -56,12 +55,16 @@ final class CreationPersonType extends AbstractType
*/ */
protected $configPersonAltNamesHelper; protected $configPersonAltNamesHelper;
private EventDispatcherInterface $dispatcher;
public function __construct( public function __construct(
CenterTransformer $centerTransformer, CenterTransformer $centerTransformer,
ConfigPersonAltNamesHelper $configPersonAltNamesHelper ConfigPersonAltNamesHelper $configPersonAltNamesHelper,
EventDispatcherInterface $dispatcher
) { ) {
$this->centerTransformer = $centerTransformer; $this->centerTransformer = $centerTransformer;
$this->configPersonAltNamesHelper = $configPersonAltNamesHelper; $this->configPersonAltNamesHelper = $configPersonAltNamesHelper;
$this->dispatcher = $dispatcher;
} }
/** /**
@ -79,7 +82,9 @@ final class CreationPersonType extends AbstractType
->add('gender', GenderType::class, array( ->add('gender', GenderType::class, array(
'required' => true, 'placeholder' => null 'required' => true, 'placeholder' => null
)) ))
->add('center', CenterType::class) ->add('center', CenterType::class, [
'required' => false
])
; ;
if ($this->configPersonAltNamesHelper->hasAltNames()) { if ($this->configPersonAltNamesHelper->hasAltNames()) {
@ -87,6 +92,11 @@ final class CreationPersonType extends AbstractType
'by_reference' => false 'by_reference' => false
]); ]);
} }
$this->dispatcher->dispatch(
new CustomizeFormEvent(static::class, $builder),
CustomizeFormEvent::NAME
);
} }
/** /**

View File

@ -146,10 +146,10 @@
{% endif %} {% endif %}
{% endif %} {% endif %}
</li> </li>
{% if options['addCenter'] %} {% if options['addCenter'] and person|chill_resolve_center is not null %}
<li> <li>
<i class="fa fa-li fa-long-arrow-right"></i> <i class="fa fa-li fa-long-arrow-right"></i>
{{ person.center }} {{ person|chill_resolve_center.name }}
</li> </li>
{% endif %} {% endif %}
</ul> </ul>

View File

@ -17,11 +17,11 @@
{%- endif -%} {%- endif -%}
</div> </div>
<div class="text-md-end"> <div class="text-md-end">
{%- if chill_person.fields.spoken_languages == 'visible' -%} {% if person|chill_resolve_center is not null%}
<span class="open_sansbold"> <span class="open_sansbold">
{{ 'Center'|trans|upper}} : {{ 'Center'|trans|upper}} :
</span> </span>
{{ person.center.name|upper }} {{ person|chill_resolve_center.name|upper }}
{%- endif -%} {%- endif -%}
</div> </div>
</div> </div>

View File

@ -76,10 +76,9 @@
{{ form_row(form.gender, { 'label' : 'Gender'|trans }) }} {{ form_row(form.gender, { 'label' : 'Gender'|trans }) }}
<div style="display: none"> {% if form.center is defined %}
{# TODO remove this field (vendee) #} {{ form_row(form.center) }}
{{ form_row(form.center, { 'label' : 'Center'|trans }) }} {% endif %}
</div>
<ul class="record_actions sticky-form-buttons"> <ul class="record_actions sticky-form-buttons">
<li class="dropdown"> <li class="dropdown">

View File

@ -153,7 +153,7 @@ class PersonSearch extends AbstractSearch implements ContainerAwareInterface,
protected function search(array $terms, $start, $limit, array $options = array()) protected function search(array $terms, $start, $limit, array $options = array())
{ {
$qb = $this->createQuery($terms, 'search'); $qb = $this->createQuery($terms, 'search');
if ($options['simplify'] ?? false) { if ($options['simplify'] ?? false) {
$qb->select( $qb->select(
'p.id', 'p.id',
@ -176,7 +176,7 @@ class PersonSearch extends AbstractSearch implements ContainerAwareInterface,
$qb $qb
->orderBy('p.firstName') ->orderBy('p.firstName')
->addOrderBy('p.lastName'); ->addOrderBy('p.lastName');
if ($options['simplify'] ?? false) { if ($options['simplify'] ?? false) {
return $qb->getQuery()->getResult(Query::HYDRATE_ARRAY); return $qb->getQuery()->getResult(Query::HYDRATE_ARRAY);
} else { } else {
@ -291,10 +291,15 @@ class PersonSearch extends AbstractSearch implements ContainerAwareInterface,
//restraint center for security //restraint center for security
$reachableCenters = $this->helper->getReachableCenters($this->user, $reachableCenters = $this->helper->getReachableCenters($this->user,
new Role('CHILL_PERSON_SEE')); new Role('CHILL_PERSON_SEE'));
$qb->andWhere($qb->expr() $qb->andWhere(
->in('p.center', ':centers')) $qb->expr()->orX(
->setParameter('centers', $reachableCenters) $qb->expr()
; ->in('p.center', ':centers'),
$qb->expr()
->isNull('p.center')
)
);
$qb->setParameter('centers', $reachableCenters);
$this->_cacheQuery[$cacheKey] = $qb; $this->_cacheQuery[$cacheKey] = $qb;

View File

@ -23,16 +23,12 @@ use Chill\MainBundle\Security\Authorization\AbstractChillVoter;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\MainBundle\Security\ProvideRoleHierarchyInterface; use Chill\MainBundle\Security\ProvideRoleHierarchyInterface;
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Entity\Center;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Role\Role; use Symfony\Component\Security\Core\Role\Role;
/**
*
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class PersonVoter extends AbstractChillVoter implements ProvideRoleHierarchyInterface class PersonVoter extends AbstractChillVoter implements ProvideRoleHierarchyInterface
{ {
const CREATE = 'CHILL_PERSON_CREATE'; const CREATE = 'CHILL_PERSON_CREATE';
@ -41,18 +37,19 @@ class PersonVoter extends AbstractChillVoter implements ProvideRoleHierarchyInte
const STATS = 'CHILL_PERSON_STATS'; const STATS = 'CHILL_PERSON_STATS';
const LISTS = 'CHILL_PERSON_LISTS'; const LISTS = 'CHILL_PERSON_LISTS';
const DUPLICATE = 'CHILL_PERSON_DUPLICATE'; const DUPLICATE = 'CHILL_PERSON_DUPLICATE';
/** protected AuthorizationHelper $helper;
*
* @var AuthorizationHelper protected CenterResolverDispatcher $centerResolverDispatcher;
*/
protected $helper; public function __construct(
AuthorizationHelper $helper,
public function __construct(AuthorizationHelper $helper) CenterResolverDispatcher $centerResolverDispatcher
{ ) {
$this->helper = $helper; $this->helper = $helper;
$this->centerResolverDispatcher = $centerResolverDispatcher;
} }
protected function supports($attribute, $subject) protected function supports($attribute, $subject)
{ {
if ($subject instanceof Person) { if ($subject instanceof Person) {
@ -69,23 +66,30 @@ class PersonVoter extends AbstractChillVoter implements ProvideRoleHierarchyInte
return false; return false;
} }
} }
protected function voteOnAttribute($attribute, $subject, TokenInterface $token) protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
{ {
if (!$token->getUser() instanceof User) { if (!$token->getUser() instanceof User) {
return false; return false;
} }
if ($subject === null) { if ($subject === null) {
$centers = $this->helper->getReachableCenters($token->getUser(), $centers = $this->helper->getReachableCenters($token->getUser(),
new Role($attribute)); new Role($attribute));
return count($centers) > 0; return count($centers) > 0;
} }
$center = $this->centerResolverDispatcher->resolveCenter($subject);
if (NULL === $center && $subject instanceof Person) {
// person without any center are seen by everybody
return true;
}
return $this->helper->userHasAccess($token->getUser(), $subject, $attribute); return $this->helper->userHasAccess($token->getUser(), $subject, $attribute);
} }
private function getAttributes() private function getAttributes()
{ {
return array(self::CREATE, self::UPDATE, self::SEE, self::STATS, self::LISTS, self::DUPLICATE); return array(self::CREATE, self::UPDATE, self::SEE, self::STATS, self::LISTS, self::DUPLICATE);
@ -100,7 +104,7 @@ class PersonVoter extends AbstractChillVoter implements ProvideRoleHierarchyInte
{ {
return $this->getAttributes(); return $this->getAttributes();
} }
public function getRolesWithHierarchy() public function getRolesWithHierarchy()
{ {
return [ 'Person' => $this->getRoles() ]; return [ 'Person' => $this->getRoles() ];

View File

@ -1,8 +1,7 @@
services: services:
chill.person.security.authorization.person: chill.person.security.authorization.person:
autowire: true
class: Chill\PersonBundle\Security\Authorization\PersonVoter class: Chill\PersonBundle\Security\Authorization\PersonVoter
arguments:
- "@chill.main.security.authorization.helper"
tags: tags:
- { name: security.voter } - { name: security.voter }
- { name: chill.role } - { name: chill.role }
@ -13,4 +12,4 @@ services:
tags: tags:
- { name: security.voter } - { name: security.voter }
- { name: chill.role } - { name: chill.role }