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'])); + } + +}