From 9459d7a287e961d47159f5e17cfcb4b09b8e2400 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Sat, 9 Apr 2016 23:45:07 +0200 Subject: [PATCH] Allow to add multiple participation The participationController accept a new parameter : `persons_ids`, which must receive a comma-separated list of person ids. A participation will be create for all those peoples. The `new` and `create` actions does not allow to receive both `person_id` and `persons_ids`. Tests are added. ref #6 --- Controller/ParticipationController.php | 224 +++++++++++-- Entity/Participation.php | 55 +++- .../Participation/new-multiple.html.twig | 47 ++- Resources/views/Participation/new.html.twig | 3 + Tests/Controller/EventControllerTest.php | 5 + Tests/Controller/EventTypeControllerTest.php | 4 + .../ParticipationControllerTest.php | 311 ++++++++++++++++++ Tests/Controller/RoleControllerTest.php | 4 + Tests/Controller/StatusControllerTest.php | 4 + 9 files changed, 626 insertions(+), 31 deletions(-) create mode 100644 Tests/Controller/ParticipationControllerTest.php diff --git a/Controller/ParticipationController.php b/Controller/ParticipationController.php index a04f05ed0..007a7825d 100644 --- a/Controller/ParticipationController.php +++ b/Controller/ParticipationController.php @@ -26,6 +26,7 @@ use Chill\EventBundle\Entity\Participation; use Chill\EventBundle\Form\ParticipationType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Chill\EventBundle\Security\Authorization\ParticipationVoter; +use Symfony\Component\Form\Extension\Core\Type\CollectionType; /** * @@ -44,23 +45,65 @@ class ParticipationController extends Controller */ public function newAction(Request $request) { - $single = $request->query->getInt('person_id', null); - $multiple = $request->query->get('persons_ids', null); - - if ($single !== NULL AND $multiple !== NULL) { - // we are not allowed to have both person_id and persons_ids + // test the request is correct + try { + $this->testRequest($request); + } catch (\RuntimeException $ex) { + $this->get('logger')->warning($ex->getMessage()); + return (new Response()) ->setStatusCode(Response::HTTP_BAD_REQUEST) - ->setContent("You are not allow to provide both 'person_id' and " + ->setContent($ex->getMessage()); + } + + // forward to other action + $single = $request->query->has('person_id'); + $multiple = $request->query->has('persons_ids'); + + if ($single === true) { + return $this->newSingle($request); + } + + if ($multiple === true) { + + return $this->newMultiple($request); + } + + // at this point, we miss the required fields. Throw an error + return (new Response()) + ->setStatusCode(Response::HTTP_BAD_REQUEST) + ->setContent("You must provide either 'person_id' or " + . "'persons_ids' argument in query"); + } + + protected function testRequest($request) + { + $single = $request->query->has('person_id'); + $multiple = $request->query->has('persons_ids'); + + if ($single === true AND $multiple === true) { + // we are not allowed to have both person_id and persons_ids + throw new \RuntimeException("You are not allow to provide both 'person_id' and " . "'persons_ids' simulaneously"); } - if ($single !== NULL) { - return $this->newSingleAction($request); + if ($multiple === true) { + $persons_ids = $request->query->get('persons_ids'); + + if (!preg_match('/^([0-9]{1,},){1,}[0-9]{1,}$/', $persons_ids)) { + throw new \RuntimeException("The persons_ids value should " + . "contains int separated by ','"); + } } + + // check for event_id - this could be removed later + if ($request->query->has('event_id') === FALSE) { + throw new \RuntimeException("You must provide an event_id"); + } + } - protected function newSingleAction(Request $request) + protected function newSingle(Request $request) { $participation = $this->handleRequest($request, new Participation()); @@ -75,7 +118,57 @@ class ParticipationController extends Controller )); } + protected function newMultiple(Request $request) + { + $participations = $this->handleRequest($request, new Participation()); + + foreach ($participations as $participation) { + $this->denyAccessUnlessGranted(ParticipationVoter::CREATE, + $participation, 'The user is not allowed to create this participation'); + } + + $form = $this->createCreateFormMultiple($participations); + + return $this->render('ChillEventBundle:Participation:new-multiple.html.twig', array( + 'form' => $form->createView(), + 'participations' => $participations + )); + } + public function createAction(Request $request) + { + // test the request is correct + try { + $this->testRequest($request); + } catch (\RuntimeException $ex) { + $this->get('logger')->warning($ex->getMessage()); + + return (new Response()) + ->setStatusCode(Response::HTTP_BAD_REQUEST) + ->setContent($ex->getMessage()); + } + + // forward to other action + $single = $request->query->has('person_id'); + $multiple = $request->query->has('persons_ids'); + + if ($single === true) { + return $this->createSingle($request); + } + + if ($multiple === true) { + + return $this->createMultiple($request); + } + + // at this point, we miss the required fields. Throw an error + return (new Response()) + ->setStatusCode(Response::HTTP_BAD_REQUEST) + ->setContent("You must provide either 'person_id' or " + . "'persons_ids' argument in query"); + } + + public function createSingle(Request $request) { $participation = $this->handleRequest($request, new Participation()); @@ -106,7 +199,49 @@ class ParticipationController extends Controller )); } + public function createMultiple(Request $request) + { + $participations = $this->handleRequest($request, new Participation()); + + foreach($participations as $participation) { + $this->denyAccessUnlessGranted(ParticipationVoter::CREATE, + $participation, 'The user is not allowed to create this participation'); + } + + $form = $this->createCreateFormMultiple($participations); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $em = $this->getDoctrine()->getManager(); + $data = $form->getData(); + + foreach($data['participations'] as $participation) { + $em->persist($participation); + } + + $em->flush(); + + $this->addFlash('success', $this->get('translator')->trans( + 'The participations were created' + )); + + return $this->redirectToRoute('chill_event__event_show', array( + 'event_id' => $participations[0]->getEvent()->getId() + )); + } + + return $this->render('ChillEventBundle:Participation:new.html.twig', array( + 'form' => $form->createView(), + 'participation' => $participation + )); + } + /** + * + * Handle the request to adapt $participation. + * + * If the request is multiple, the $participation object is cloned. + * Limitations: the $participation should not be persisted. * * @param Request $request * @param Participation $participation @@ -117,6 +252,10 @@ class ParticipationController extends Controller protected function handleRequest(Request $request, Participation $participation) { $em = $this->getDoctrine()->getManager(); + if ($em->contains($participation)) { + throw new \LogicException("The participation object should not be managed by " + . "the object manager using the method ".__METHOD__); + } $event_id = $request->query->getInt('event_id', null); @@ -134,23 +273,37 @@ class ParticipationController extends Controller $participation->setEvent($event); } - $person_id = $request->query->getInt('person_id', null); + // this script should be able to handle multiple, so we translate + // single person_id in an array + $persons_ids = $request->query->has('person_id') ? + array($request->query->getInt('person_id', null)): + explode(',', $request->query->get('persons_ids')) + ; + $participations = array(); - if ($person_id !== NULL) { - $person = $em->getRepository('ChillPersonBundle:Person') - ->find($person_id); + foreach($persons_ids as $person_id) { - if ($person === NULL) { - throw $this->createNotFoundException('The person with id '.$person_id.' is not found'); + // clone if we have to reuse the $participation + $participation = count($persons_ids) > 1 ? clone $participation : $participation; + + if ($person_id !== NULL) { + $person = $em->getRepository('ChillPersonBundle:Person') + ->find($person_id); + + if ($person === NULL) { + throw $this->createNotFoundException('The person with id '.$person_id.' is not found'); + } + + $this->denyAccessUnlessGranted('CHILL_PERSON_SEE', $person, + 'The user is not allowed to see the person'); + + $participation->setPerson($person); } - $this->denyAccessUnlessGranted('CHILL_PERSON_SEE', $person, - 'The user is not allowed to see the person'); - - $participation->setPerson($person); + $participations[] = $participation; } - return $participation; + return count($participations) > 1 ? $participations : $participations[0]; } /** @@ -175,6 +328,37 @@ class ParticipationController extends Controller return $form; } + /** + * + * @param array $participations + * @return type + */ + public function createCreateFormMultiple(array $participations) + { + $form = $this->createForm(\Symfony\Component\Form\Extension\Core\Type\FormType::class, + array('participations' => $participations), array( + 'action' => $this->generateUrl('chill_event_participation_create', array( + 'event_id' => $participations[0]->getEvent()->getId(), + 'persons_ids' => implode(',', array_map( + function(Participation $p) { return $p->getPerson()->getId(); }, + $participations)) + ) + ))); + $form->add('participations', CollectionType::class, array( + 'entry_type' => ParticipationType::class, + 'entry_options' => array( + 'event_type' => $participations[0]->getEvent()->getType() + ), + ) + ); + + $form->add('submit', SubmitType::class, array( + 'label' => 'Create' + )); + + return $form; + } + /** * show an edit form for the participation with the given id. * diff --git a/Entity/Participation.php b/Entity/Participation.php index ef33c6b99..07967edca 100644 --- a/Entity/Participation.php +++ b/Entity/Participation.php @@ -9,7 +9,7 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; /** * Participation */ -class Participation implements HasCenterInterface, HasScopeInterface +class Participation implements HasCenterInterface, HasScopeInterface, \ArrayAccess { /** * @var integer @@ -218,6 +218,11 @@ class Participation implements HasCenterInterface, HasScopeInterface */ public function isConsistent(ExecutionContextInterface $context) { + + if ($this->getEvent() === NULL || $this->getRole() === NULL || $this->getStatus() === NULL) { + return; + } + if ($this->getRole()->getType()->getId() !== $this->getEvent()->getType()->getId()) { $context->buildViolation('The role is not allowed with this event type') @@ -233,4 +238,52 @@ class Participation implements HasCenterInterface, HasScopeInterface } } + public function offsetExists($offset) + { + return in_array($offset, array( + 'person', 'role', 'status', 'event' + )); + } + + public function offsetGet($offset) + { + switch ($offset) { + case 'person': + return $this->getPerson(); + break; + case 'role': + return $this->getRole(); + break; + case 'status': + return $this->getStatus(); + break; + case 'event': + return $this->getEvent(); + break; + } + } + + public function offsetSet($offset, $value) + { + switch($offset) { + case 'person': + return $this->setPerson($value); + break; + case 'role': + return $this->setRole($value); + break; + case 'status': + return $this->setStatus($value); + break; + case 'event': + return $this->setEvent($value); + break; + } + } + + public function offsetUnset($offset) + { + $this->offsetSet($offset, null); + } + } diff --git a/Resources/views/Participation/new-multiple.html.twig b/Resources/views/Participation/new-multiple.html.twig index 9b7a55a7f..634ffa78f 100644 --- a/Resources/views/Participation/new-multiple.html.twig +++ b/Resources/views/Participation/new-multiple.html.twig @@ -3,30 +3,57 @@ {% import 'ChillPersonBundle:Person:macro.html.twig' as person_macro %} {% block title 'Participation creation'|trans %} + + {% form_theme form _self %} + + {% block _collection_row %} + + + {{ form_widget(form) }} + + + {# {{ form_row(participationField.status) }} #} + + + {% endblock %} {% block event_content -%}

{{ 'Participation creation'|trans }}

- - - - - +
{{ 'Associated person'|trans }}{{ person_macro.render(participation.person) }}
{{ 'Associated event'|trans }} {{ participation.event.name }}{{ participations[0].event.name }}
- - {{ form_start(form) }} - {{ form_row(form.role) }} - {{ form_row(form.status) }} + + + {{ form_start(form) }} + + + + + + + + + + {% for participationField in form.participations %} + + + + + + {% endfor %} + +
{{ 'Person'|trans }}{{ 'Role'|trans }}{{ 'Status'|trans }}
{{ person_macro.render(participationField.vars.value.person) }}{{ form_widget(participationField.role) }}{{ form_widget(participationField.status) }}
+