* * 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\EventBundle\Controller; use ArrayIterator; use Chill\EventBundle\Entity\Event; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; 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; /** * Class ParticipationController * * @package Chill\EventBundle\Controller * @author Julien Fastré */ class ParticipationController extends AbstractController { /** * Show a form to add a participation * * This function parse the person_id / persons_ids query argument * and decide if it should process a single or multiple participation. Depending * on this, the appropriate layout and form. * * @param Request $request * @return \Symfony\Component\HttpFoundation\RedirectResponse|Response */ public function newAction(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->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"); } /** * * Test that the query parameters are valid : * * - an `event_id` is existing ; * - `person_id` and `persons_ids` are **not** both present ; * - `persons_id` is correct (contains only numbers and a ','. * * @param Request $request * @throws \RuntimeException if an error is detected */ protected function testRequest(Request $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 ($multiple === true) { $persons_ids = $request->query->get('persons_ids'); if (!preg_match('/^([0-9]{1,},{0,1}){1,}[0-9]{0,}$/', $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"); } } /** * Show a form with single participation. * * @param Request $request * @return Response */ protected function newSingle(Request $request) { $returnPath = $request->query->get('return_path') ? $request->query->get('return_path') : null; $participation = $this->handleRequest($request, new Participation(), false); $this->denyAccessUnlessGranted(ParticipationVoter::CREATE, $participation, 'The user is not allowed to create this participation'); $form = $this->createCreateForm($participation, $returnPath); return $this->render('ChillEventBundle:Participation:new.html.twig', array( 'form' => $form->createView(), 'participation' => $participation, 'ignored_participations' => array() // this is required, see self::newMultiple )); } /** * Show a form with multiple participation. * * If a person is already participating on the event (if a participation with * the same person is associated with the event), the participation is ignored. * * If all but one participation is ignored, the page show the same response * than the newSingle function. * * If all participations must be ignored, an error is shown and the method redirects * to the event 'show' view with an appropriate flash message. * * @param Request $request * @return \Symfony\Component\HttpFoundation\RedirectResponse|Response */ protected function newMultiple(Request $request) { $participations = $this->handleRequest($request, new Participation(), true); foreach ($participations as $i => $participation) { // check for authorization $this->denyAccessUnlessGranted(ParticipationVoter::CREATE, $participation, 'The user is not allowed to create this participation'); // create a collection of person's id participating to the event /* @var $peopleParticipating \Doctrine\Common\Collections\ArrayCollection */ $peopleParticipating = isset($peopleParticipating) ? $peopleParticipating : $participation->getEvent()->getParticipations()->map( function(Participation $p) { return $p->getPerson()->getId(); } ); // check that the user is not already in the event if ($peopleParticipating->contains($participation->getPerson()->getId())) { $ignoredParticipations[] = $participation ->getEvent()->getParticipations()->filter( function (Participation $p) use ($participation) { return $p->getPerson()->getId() === $participation->getPerson()->getId(); } )->first(); } else { $newParticipations[] = $participation; } } // this is where the function redirect depending on valid participation if (!isset($newParticipations)) { // if we do not have nay participants, redirect to event view $this->addFlash('error', $this->get('translator')->trans( 'None of the requested people may participate ' . 'the event: they are maybe already participating.')); return $this->redirectToRoute('chill_event__event_show', array( 'event_id' => $request->query->getInt('event_id', 0) )); } elseif (count($newParticipations) > 1) { // if we have multiple participations, show a form with multiple participations $form = $this->createCreateFormMultiple($newParticipations); return $this->render('ChillEventBundle:Participation:new-multiple.html.twig', array( 'form' => $form->createView(), 'participations' => $newParticipations, 'ignored_participations' => isset($ignoredParticipations) ? $ignoredParticipations : array() )); } else { // if we have only one participation, show the same form than for single participation $form = $this->createCreateForm($participation); return $this->render('ChillEventBundle:Participation:new.html.twig', array( 'form' => $form->createView(), 'participation' => $participation, 'ignored_participations' => isset($ignoredParticipations) ? $ignoredParticipations : array() )); } } /** * @param Request $request * @return \Symfony\Component\HttpFoundation\RedirectResponse|Response */ 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"); } /** * @param Request $request * @return \Symfony\Component\HttpFoundation\RedirectResponse|Response */ public function createSingle(Request $request) { $participation = $this->handleRequest($request, new Participation(), false); $this->denyAccessUnlessGranted(ParticipationVoter::CREATE, $participation, 'The user is not allowed to create this participation'); $form = $this->createCreateForm($participation); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { $em = $this->getDoctrine()->getManager(); $em->persist($participation); $em->flush(); $this->addFlash('success', $this->get('translator')->trans( 'The participation was created' )); if ($request->query->get('return_path')) { return $this->redirect($request->query->get('return_path')); } else { return $this->redirectToRoute('chill_event__event_show', array( 'event_id' => $participation->getEvent()->getId() )); } } return $this->render('ChillEventBundle:Participation:new.html.twig', array( 'form' => $form->createView(), 'participation' => $participation )); } /** * @param Request $request * @return \Symfony\Component\HttpFoundation\RedirectResponse|Response */ public function createMultiple(Request $request) { $participations = $this->handleRequest($request, new Participation(), true); 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 * @param boolean $multiple (default false) * @return Participation|Participations[] return one single participation if $multiple == false * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException if the event/person is not found * @throws \Symfony\Component\Security\Core\Exception\AccessDeniedException if the user does not have access to event/person */ protected function handleRequest( Request $request, Participation $participation, $multiple = false) { $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', 0); // sf4 check: // prevent error: `Argument 2 passed to ::getInt() must be of the type int, null given` if ($event_id !== NULL) { $event = $em->getRepository('ChillEventBundle:Event') ->find($event_id); if ($event === NULL) { throw $this->createNotFoundException('The event with id '.$event_id.' is not found'); } $this->denyAccessUnlessGranted('CHILL_EVENT_SEE', $event, 'The user is not allowed to see the event'); $participation->setEvent($event); } // this script should be able to handle multiple, so we translate // single person_id in an array $persons_ids = $request->query->has('person_id') ? [$request->query->getInt('person_id', 0)] // sf4 check: // prevent error: `Argument 2 passed to ::getInt() must be of the type int, null given` : explode(',', $request->query->get('persons_ids')); $participations = array(); foreach($persons_ids as $person_id) { // 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); } $participations[] = $participation; } return $multiple ? $participations : $participations[0]; } /** * @param Participation $participation * @param null $return_path * @return \Symfony\Component\Form\FormInterface */ public function createCreateForm(Participation $participation, $return_path = null) { $form = $this->createForm(ParticipationType::class, $participation, array( 'event_type' => $participation->getEvent()->getType(), 'action' => $this->generateUrl('chill_event_participation_create', array( 'return_path' => $return_path, 'event_id' => $participation->getEvent()->getId(), 'person_id' => $participation->getPerson()->getId() )) )); $form->add('submit', SubmitType::class, array( 'label' => 'Create' )); return $form; } /** * @param array $participations * @return \Symfony\Component\Form\FormInterface */ 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' => current($participations)->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' => current($participations)->getEvent()->getType() ), ) ); $form->add('submit', SubmitType::class, array( 'label' => 'Create' )); return $form; } /** * show an edit form for the participation with the given id. * * @param int $participation_id * @return \Symfony\Component\HttpFoundation\Response * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException if the participation is not found * @throws \Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException if the user is not allowed to edit the participation */ public function editAction($participation_id) { /* @var $participation Participation */ $participation = $this->getDoctrine()->getManager() ->getRepository('ChillEventBundle:Participation') ->find($participation_id); if ($participation === NULL) { throw $this->createNotFoundException('The participation is not found'); } $this->denyAccessUnlessGranted(ParticipationVoter::UPDATE, $participation, 'You are not allowed to edit this participation'); $form = $this->createEditForm($participation); return $this->render('ChillEventBundle:Participation:edit.html.twig', array( 'form' => $form->createView(), 'participation' => $participation )); } /** * @param $participation_id * @param Request $request * @return \Symfony\Component\HttpFoundation\RedirectResponse|Response */ public function updateAction($participation_id, Request $request) { /* @var $participation Participation */ $participation = $this->getDoctrine()->getManager() ->getRepository('ChillEventBundle:Participation') ->find($participation_id); if ($participation === NULL) { throw $this->createNotFoundException('The participation is not found'); } $this->denyAccessUnlessGranted(ParticipationVoter::UPDATE, $participation, 'You are not allowed to edit this participation'); $form = $this->createEditForm($participation); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { $em = $this->getDoctrine()->getManager(); $em->flush(); $this->addFlash('success', $this->get('translator')->trans( 'The participation was updated' )); return $this->redirectToRoute('chill_event__event_show', array( 'event_id' => $participation->getEvent()->getId() )); } return $this->render('ChillEventBundle:Participation:edit.html.twig', array( 'form' => $form->createView(), 'participation' => $participation )); } /** * * @param Participation $participation * @return \Symfony\Component\Form\FormInterface */ public function createEditForm(Participation $participation) { $form = $this->createForm(ParticipationType::class, $participation, array( 'event_type' => $participation->getEvent()->getType(), 'action' => $this->generateUrl('chill_event_participation_update', array( 'participation_id' => $participation->getId() )) )); $form->add('submit', SubmitType::class, array( 'label' => 'Edit' )); return $form; } /** * show a form to edit multiple participation for the same event. * * @param int $event_id * @return \Symfony\Component\HttpFoundation\RedirectResponse|Response */ public function editMultipleAction($event_id) { $event = $this->getDoctrine()->getRepository('ChillEventBundle:Event') ->find($event_id); if ($event === null) { throw $this->createNotFoundException("The event with id $event_id is not found"); } // check for ACL, on Event level and on Participation Level $this->denyAccessUnlessGranted('CHILL_EVENT_SEE', $event, "You are not allowed " . "to see this event"); foreach ($event->getParticipations() as $participation) { $this->denyAccessUnlessGranted(ParticipationVoter::UPDATE, $participation, "You are not allowed to update participation with id ".$participation->getId()); } switch ($event->getParticipations()->count()) { case 0: // if there aren't any participation, redirect to the 'show' view with an add flash $this->addFlash('warning', $this->get('translator') ->trans( "There are no participation to edit for this event")); return $this->redirectToRoute('chill_event__event_show', array('event_id' => $event->getId())); case 1: // redirect to the form for a single participation return $this->redirectToRoute('chill_event_participation_edit', array( 'participation_id' => $event->getParticipations()->current()->getId() )); } $form = $this->createEditFormMultiple($event->getParticipations(), $event); return $this->render('ChillEventBundle:Participation:edit-multiple.html.twig', array( 'event' => $event, 'participations' => $event->getParticipations(), 'form' => $form->createView() )); } public function updateMultipleAction($event_id, Request $request) { /* @var $event \Chill\EventBundle\Entity\Event */ $event = $this->getDoctrine()->getRepository('ChillEventBundle:Event') ->find($event_id); if ($event === null) { throw $this->createNotFoundException("The event with id $event_id is not found"); } $this->denyAccessUnlessGranted('CHILL_EVENT_SEE', $event, "You are not allowed " . "to see this event"); foreach ($event->getParticipations() as $participation) { $this->denyAccessUnlessGranted(ParticipationVoter::UPDATE, $participation, "You are not allowed to update participation with id ".$participation->getId()); } $form = $this->createEditFormMultiple($event->getParticipations(), $event); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { $this->getDoctrine()->getManager()->flush(); $this->addFlash('success', $this->get('translator')->trans("The participations " . "have been successfully updated.")); return $this->redirectToRoute('chill_event__event_show', array('event_id' => $event->getId())); } return $this->render('ChillEventBundle:Participation:edit-multiple.html.twig', array( 'event' => $event, 'participations' => $event->getParticipations(), 'form' => $form->createView() )); } /** * @param ArrayIterator $participations * @param Event $event * @return \Symfony\Component\Form\FormInterface */ protected function createEditFormMultiple(ArrayIterator $participations, Event $event) { $form = $this->createForm(\Symfony\Component\Form\Extension\Core\Type\FormType::class, array('participations' => $participations), array( 'method' => 'POST', 'action' => $this->generateUrl('chill_event_participation_update_multiple', array( 'event_id' => $event->getId() )) )); $form->add('participations', CollectionType::class, array( 'entry_type' => ParticipationType::class, 'entry_options' => array( 'event_type' => $event->getType() ), ) ); $form->add('submit', SubmitType::class, array( 'label' => 'Update' )); return $form; } /** * @param integer $participation_id * @param Request $request * @return \Symfony\Component\HttpFoundation\RedirectResponse|Response */ public function deleteAction($participation_id, Request $request) { $em = $this->getDoctrine()->getManager(); $participation = $em->getRepository('ChillEventBundle:Participation')->findOneBy([ 'id' => $participation_id ]); if (! $participation) { throw $this->createNotFoundException('Unable to find participation.'); } /** @var Event $event */ $event = $participation->getEvent(); $form = $this->createDeleteForm($participation_id); if ($request->getMethod() === Request::METHOD_DELETE) { $form->handleRequest($request); if ($form->isValid()) { $em->remove($participation); $em->flush(); $this->addFlash('success', $this->get('translator') ->trans("The participation has been sucessfully removed") ); return $this->redirectToRoute('chill_event__event_show', [ 'event_id' => $event->getId() ]); } } return $this->render('ChillEventBundle:Participation:confirm_delete.html.twig', [ 'event_id' => $event->getId(), 'delete_form' => $form->createView() ]); } /** * @param $participation_id * @return \Symfony\Component\Form\FormInterface */ private function createDeleteForm($participation_id) { return $this->createFormBuilder() ->setAction($this->generateUrl('chill_event_participation_delete', [ 'participation_id' => $participation_id ])) ->setMethod('DELETE') ->add('submit', SubmitType::class, ['label' => 'Delete']) ->getForm() ; } }