diff --git a/Form/Type/PickPersonType.php b/Form/Type/PickPersonType.php
new file mode 100644
index 000000000..b9932c4ed
--- /dev/null
+++ b/Form/Type/PickPersonType.php
@@ -0,0 +1,161 @@
+
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+namespace Chill\PersonBundle\Form\Type;
+
+use Symfony\Component\Form\AbstractType;
+use Symfony\Component\Form\FormBuilderInterface;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+use Chill\MainBundle\Entity\Center;
+use Chill\PersonBundle\Entity\PersonRepository;
+use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
+use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
+use Symfony\Component\Security\Core\Exception\AccessDeniedException;
+use Symfony\Component\Security\Core\Role\Role;
+use Chill\MainBundle\Entity\GroupCenter;
+use Chill\PersonBundle\Entity\Person;
+
+/**
+ * This type allow to pick a person.
+ *
+ * The form is embedded in a select2 input.
+ *
+ * The people may be filtered :
+ *
+ * - with the `centers` option, only the people associated with the given center(s)
+ * are seen. May be an instance of `Chill\MainBundle\Entity\Center`, or an array of
+ * `Chill\MainBundle\Entity\Center`. By default, all the reachable centers as selected.
+ * - with the `role` option, only the people belonging to the reachable center for the
+ * given role are displayed.
+ *
+ *
+ * @author Julien Fastré
+ */
+class PickPersonType extends AbstractType
+{
+ /**
+ * @var PersonRepository
+ */
+ protected $personRepository;
+
+ /**
+ *
+ * @var \Chill\MainBundle\Entity\User
+ */
+ protected $user;
+
+ /**
+ *
+ * @var AuthorizationHelper
+ */
+ protected $authorizationHelper;
+
+ public function __construct(
+ PersonRepository $personRepository,
+ TokenStorageInterface $tokenStorage,
+ AuthorizationHelper $authorizationHelper
+ )
+ {
+ $this->personRepository = $personRepository;
+ $this->user = $tokenStorage->getToken()->getUser();
+ $this->authorizationHelper = $authorizationHelper;
+ }
+
+ public function buildForm(FormBuilderInterface $builder, array $options)
+ {
+ $qb = $options['query_builder'];
+
+ if ($options['role'] === NULL) {
+ $centers = array_map(function (GroupCenter $g) {
+
+ return $g->getCenter();
+ }, $this->user->getGroupCenters()->toArray());
+ } else {
+ $centers = $this->authorizationHelper
+ ->getReachableCenters($this->user, $options['role']);
+ }
+
+ if ($options['centers'] === NULL) {
+ // we select all selected centers
+ $selectedCenters = $centers;
+ } else {
+ $selectedCenters = array();
+ $options['centers'] = is_array($options['centers']) ?
+ $options['centers'] : array($options['centers']);
+
+ foreach ($options['centers'] as $c) {
+ // check that every member of the array is a center
+ if (!$c instanceof Center) {
+ throw new \RuntimeException('Every member of the "centers" '
+ . 'option must be an instance of '.Center::class);
+ }
+ if (!in_array($c->getId(), array_map(
+ function(Center $c) { return $c->getId();},
+ $centers))) {
+ throw new AccessDeniedException('The given center is not reachable');
+ }
+ $selectedCenters[] = $c;
+ }
+ }
+
+
+ $qb
+ ->orderBy('p.firstName', 'ASC')
+ ->orderBy('p.lastName', 'ASC')
+ ->where($qb->expr()->in('p.center', ':centers'))
+ ->setParameter('centers', $selectedCenters)
+ ;
+ }
+
+ public function configureOptions(OptionsResolver $resolver)
+ {
+ parent::configureOptions($resolver);
+
+ // add the possibles options for this type
+ $resolver->setDefined('centers')
+ ->addAllowedTypes('centers', array('array', Center::class, 'null'))
+ ->setDefault('centers', null)
+ ->setDefined('role')
+ ->addAllowedTypes('role', array(Role::class, 'null'))
+ ->setDefault('role', null)
+ ;
+
+ // add the default options
+ $resolver->setDefaults(array(
+ 'class' => Person::class,
+ 'choice_label' => function(Person $p) {
+ return $p->getFirstname().' '.$p->getLastname();
+ },
+ 'placeholder' => 'Pick a person',
+ 'choice_attr' => function(Person $p) {
+ return array(
+ 'data-center' => $p->getCenter()->getId()
+ );
+ },
+ 'attr' => array('class' => 'select2 '),
+ 'query_builder' => $this->personRepository->createQueryBuilder('p')
+ ));
+ }
+
+ public function getParent()
+ {
+ return \Symfony\Bridge\Doctrine\Form\Type\EntityType::class;
+ }
+
+}
diff --git a/Resources/config/services.yml b/Resources/config/services.yml
index b0cf94e1d..3bb58b536 100644
--- a/Resources/config/services.yml
+++ b/Resources/config/services.yml
@@ -69,4 +69,18 @@ services:
- "@chill.main.form.data_transformer.center_transformer"
tags:
- { name: form.type, alias: chill_personbundle_person_creation }
-
\ No newline at end of file
+
+ chill.person.form.type.pick_person:
+ class: Chill\PersonBundle\Form\Type\PickPersonType
+ arguments:
+ - "@chill.person.repository.person"
+ - "@security.token_storage"
+ - "@chill.main.security.authorization.helper"
+ tags:
+ - { name: form.type }
+
+ chill.person.repository.person:
+ class: Chill\PersonBundle\Entity\PersonRepository
+ factory: ['@doctrine.orm.entity_manager', getRepository]
+ arguments:
+ - 'Chill\PersonBundle\Entity\Person'
\ No newline at end of file
diff --git a/Resources/translations/messages.fr.yml b/Resources/translations/messages.fr.yml
index 19b1be9ef..8836cb641 100644
--- a/Resources/translations/messages.fr.yml
+++ b/Resources/translations/messages.fr.yml
@@ -71,6 +71,9 @@ Reset: 'Remise à zéro'
'Update details for %name%': 'Modifier détails de %name%'
Accompanying period list: Périodes d'accompagnement
+# pickAPersonType
+Pick a person: Choisir une personne
+
#address
Since %date%: Depuis le %date%
No address given: Pas d'adresse renseignée
diff --git a/Tests/Form/Type/PickPersonTypeTest.php b/Tests/Form/Type/PickPersonTypeTest.php
new file mode 100644
index 000000000..0baf96bfc
--- /dev/null
+++ b/Tests/Form/Type/PickPersonTypeTest.php
@@ -0,0 +1,181 @@
+
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+namespace Chill\PersonBundle\Tests\Form\Type;
+
+use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
+use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
+use Chill\PersonBundle\Form\Type\PickPersonType;
+
+/**
+ *
+ *
+ * @author Julien Fastré
+ */
+class PickPersonTypeTest extends KernelTestCase
+{
+ /**
+ *
+ * @var \Chill\MainBundle\Entity\User
+ */
+ protected $user;
+
+ /**
+ *
+ * @var \Symfony\Component\DependencyInjection\ContainerInterface
+ */
+ protected $container;
+
+ /**
+ *
+ * @var \Symfony\Component\Form\FormFactoryInterface
+ */
+ protected $formFactory;
+
+ public function setUp()
+ {
+ self::bootKernel();
+
+ $this->container = self::$kernel->getContainer();
+
+ $this->user = $this->container->get('doctrine.orm.entity_manager')
+ ->getRepository('ChillMainBundle:User')
+ ->findOneBy(array('username' => 'multi_center'));
+
+ $this->formFactory = $this->container->get('form.factory');
+
+ $token = (new UsernamePasswordToken($this->user, 'password', 'firewall'));
+ $this->container->get('security.token_storage')
+ ->setToken($token);
+ }
+
+ public function testWithoutOption()
+ {
+ $form = $this->formFactory
+ ->createBuilder(PickPersonType::class, null, array())
+ ->getForm();
+
+ $this->assertInstanceOf(\Symfony\Component\Form\FormInterface::class,
+ $form);
+
+ // transform into a view to have data-center attr
+ $view = $form->createView();
+
+ $centerIds = array();
+
+ /* @var $centerIds \Symfony\Component\Form\ChoiceList\View\ChoiceView */
+ foreach($view->vars['choices'] as $choice) {
+ $centerIds[] = $choice->attr['data-center'];
+ }
+
+ $this->assertEquals(2, count(array_unique($centerIds)),
+ "test that the form contains people from 2 centers");
+ }
+
+ /**
+ * Test the form with an option 'centers' with an unique center
+ * entity (not in an array)
+ */
+ public function testWithOptionCenter()
+ {
+ $center = $this->container->get('doctrine.orm.entity_manager')
+ ->getRepository('ChillMainBundle:Center')
+ ->findOneBy(array('name' => 'Center A'))
+ ;
+
+ $form = $this->formFactory
+ ->createBuilder(PickPersonType::class, null, array(
+ 'centers' => $center
+ ))
+ ->getForm();
+
+ // transform into a view to have data-center attr
+ $view = $form->createView();
+
+ /* @var $centerIds \Symfony\Component\Form\ChoiceList\View\ChoiceView */
+ foreach($view->vars['choices'] as $choice) {
+ $centerIds[] = $choice->attr['data-center'];
+ }
+
+ $this->assertEquals(1, count(array_unique($centerIds)),
+ "test that the form contains people from only one centers");
+
+ $this->assertEquals($center->getId(), array_unique($centerIds)[0]);
+
+ }
+
+ /**
+ * Test the form with multiple centers
+ */
+ public function testWithOptionCenters()
+ {
+ $centers = $this->container->get('doctrine.orm.entity_manager')
+ ->getRepository('ChillMainBundle:Center')
+ ->findAll()
+ ;
+
+ $form = $this->formFactory
+ ->createBuilder(PickPersonType::class, null, array(
+ 'centers' => $centers
+ ))
+ ->getForm();
+
+ // transform into a view to have data-center attr
+ $view = $form->createView();
+
+ /* @var $centerIds \Symfony\Component\Form\ChoiceList\View\ChoiceView */
+ foreach($view->vars['choices'] as $choice) {
+ $centerIds[] = $choice->attr['data-center'];
+ }
+
+ $this->assertEquals(2, count(array_unique($centerIds)),
+ "test that the form contains people from only one centers");
+
+ }
+
+ /**
+ * test with an invalid center type in the option 'centers' (in an array)
+ *
+ * @expectedException \RuntimeException
+ */
+ public function testWithInvalidOptionCenters()
+ {
+
+ $form = $this->formFactory
+ ->createBuilder(PickPersonType::class, null, array(
+ 'centers' => array('string')
+ ))
+ ->getForm();
+ }
+
+ public function testWithOptionRoleInvalid()
+ {
+ $form = $this->formFactory
+ ->createBuilder(PickPersonType::class, null, array(
+ 'role' => new \Symfony\Component\Security\Core\Role\Role('INVALID')
+ ))
+ ->getForm();
+
+ // transform into a view to have data-center attr
+ $view = $form->createView();
+
+ $this->assertEquals(0, count($view->vars['choices']));
+ }
+
+}