From ee8f303d258039b92dd47daf7c1dc37a02757fdb Mon Sep 17 00:00:00 2001 From: Tchama Date: Thu, 2 May 2019 11:35:15 +0200 Subject: [PATCH] subscribe a person since listByPerson context (issue #19) --- Controller/EventController.php | 51 ++++- Form/ChoiceLoader/EventChoiceLoader.php | 138 ++++++++++++ Form/Type/PickSubscriptionType.php | 211 +++++++++++++++++++ Repository/EventRepository.php | 35 +++ Resources/config/doctrine/Event.orm.yml | 1 + Resources/config/routing/event.yml | 1 + Resources/config/services/forms.yml | 10 + Resources/config/services/repositories.yml | 5 +- Resources/translations/messages.fr.yml | 3 + Resources/views/Event/listByPerson.html.twig | 11 +- Search/EventSearch.php | 23 +- 11 files changed, 478 insertions(+), 11 deletions(-) create mode 100644 Form/ChoiceLoader/EventChoiceLoader.php create mode 100644 Form/Type/PickSubscriptionType.php create mode 100644 Repository/EventRepository.php diff --git a/Controller/EventController.php b/Controller/EventController.php index d10b561b2..ab7431b72 100644 --- a/Controller/EventController.php +++ b/Controller/EventController.php @@ -23,8 +23,10 @@ namespace Chill\EventBundle\Controller; use Chill\EventBundle\Entity\Participation; +use Chill\EventBundle\Form\Type\PickSubscriptionType; use Chill\EventBundle\Security\Authorization\EventVoter; use Chill\MainBundle\Security\Authorization\AuthorizationHelper; +use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Privacy\PrivacyEvent; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\Request; @@ -343,6 +345,7 @@ class EventController extends Controller */ public function listByPersonAction($person_id) { + $em = $this->getDoctrine()->getManager(); $person = $em->getRepository('ChillPersonBundle:Person')->find($person_id); @@ -393,17 +396,59 @@ class EventController extends Controller ->getResult() ; - $privacyEvent = new PrivacyEvent($person, array( 'element_class' => Participation::class, 'action' => 'list' )); $this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $privacyEvent); - + + $addParticipationByEventForm = $this->createAddParticipationByEventForm($person); + return $this->render('ChillEventBundle:Event:listByPerson.html.twig', array( 'participations' => $participations, 'person' => $person, - 'paginator' => $paginator + 'paginator' => $paginator, + 'form_add_participation_by_event' => $addParticipationByEventForm->createView() )); } + + /** + * create a form to add a participation with an event + * + * @param Person $person + * @return \Symfony\Component\Form\FormInterface + */ + protected function createAddParticipationByEventForm(Person $person) + { + /* @var $builder \Symfony\Component\Form\FormBuilderInterface */ + $builder = $this + ->get('form.factory') + ->createNamedBuilder( + null, + FormType::class, + null, + array( + 'method' => 'GET', + 'action' => $this->generateUrl('chill_event_participation_new'), + 'csrf_protection' => false + )) + ; + + $builder->add('event_id', PickSubscriptionType::class, array( + 'role' => new Role('CHILL_EVENT_CREATE'), + 'centers' => $person->getCenter() + )); + + $builder->add('person_id', HiddenType::class, array( + 'data' => $person->getId() + )); + + $builder->add('submit', SubmitType::class, + array( + 'label' => 'Subscribe an event' + )); + + return $builder->getForm(); + } + } diff --git a/Form/ChoiceLoader/EventChoiceLoader.php b/Form/ChoiceLoader/EventChoiceLoader.php new file mode 100644 index 000000000..c96e541f7 --- /dev/null +++ b/Form/ChoiceLoader/EventChoiceLoader.php @@ -0,0 +1,138 @@ + + * + * 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\Form\ChoiceLoader; + +use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface; +use Symfony\Component\Form\ChoiceList\ChoiceListInterface; +use Doctrine\ORM\EntityRepository; +use Chill\EventBundle\Entity\Event; + +/*** + * Class EventChoiceLoader + * + * @package Chill\EventBundle\Form\ChoiceLoader + * @author Mathieu Jaumotte jaum_mathieu@collectifs.net + */ +class EventChoiceLoader implements ChoiceLoaderInterface +{ + + /** + * @var EntityRepository + */ + protected $eventRepository; + + /** + * @var array + */ + protected $lazyLoadedEvents = []; + + /** + * @var array + */ + protected $centers = []; + + /** + * EventChoiceLoader constructor. + * + * @param EntityRepository $eventRepository + * @param array|null $centers + */ + public function __construct( + EntityRepository $eventRepository, + array $centers = null + ) { + $this->eventRepository = $eventRepository; + if (NULL !== $centers) { + $this->centers = $centers; + } + } + + /** + * @return bool + */ + protected function hasCenterFilter() + { + return count($this->centers) > 0; + } + + /** + * @param null $value + * @return ChoiceListInterface + */ + public function loadChoiceList($value = null): ChoiceListInterface + { + $list = new \Symfony\Component\Form\ChoiceList\ArrayChoiceList( + $this->lazyLoadedEvents, + function(Event $p) use ($value) { + return \call_user_func($value, $p); + }); + + return $list; + } + + /** + * @param array $values + * @param null $value + * @return array + */ + public function loadChoicesForValues(array $values, $value = null) + { + $choices = []; + + foreach($values as $value) { + if (empty($value)) { + continue; + } + + $event = $this->eventRepository->find($value); + + if ($this->hasCenterFilter() && + !\in_array($event->getCenter(), $this->centers)) { + throw new \RuntimeException("chosen an event not in correct center"); + } + + $choices[] = $event; + } + + return $choices; + } + + /** + * @param array $choices + * @param null $value + * @return array|string[] + */ + public function loadValuesForChoices(array $choices, $value = null) + { + $values = []; + + foreach ($choices as $choice) { + if (NULL === $choice) { + $values[] = null; + continue; + } + + $id = \call_user_func($value, $choice); + $values[] = $id; + $this->lazyLoadedEvents[$id] = $choice; + } + return $values; + } + +} diff --git a/Form/Type/PickSubscriptionType.php b/Form/Type/PickSubscriptionType.php new file mode 100644 index 000000000..c782330db --- /dev/null +++ b/Form/Type/PickSubscriptionType.php @@ -0,0 +1,211 @@ +, + * + * 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\Form\Type; + +use Chill\EventBundle\Entity\Event; +use Chill\EventBundle\Form\ChoiceLoader\EventChoiceLoader; +use Chill\EventBundle\Repository\EventRepository; +use Chill\EventBundle\Search\EventSearch; +use Chill\MainBundle\Entity\Center; +use Chill\MainBundle\Entity\GroupCenter; +use Chill\MainBundle\Entity\User; +use Chill\MainBundle\Security\Authorization\AuthorizationHelper; +use Symfony\Bridge\Doctrine\Form\Type\EntityType; +use Symfony\Component\Finder\Exception\AccessDeniedException; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Role\Role; +use Symfony\Component\Translation\TranslatorInterface; + +/** + * Class PickSubscriptionType + * + * @package Chill\EventBundle\Form\Type + * @author Mathieu Jaumotte jaum_mathieu@collectifs.net + */ +class PickSubscriptionType extends AbstractType +{ + + /** + * @var EventRepository + */ + protected $eventRepository; + + /** + * @var User + */ + protected $user; + + /** + * @var AuthorizationHelper + */ + protected $authorizationHelper; + + /** + * @var UrlGeneratorInterface + */ + protected $urlGenerator; + + /** + * @var TranslatorInterface + */ + protected $translator; + + /** + * PickSubscriptionType constructor. + * + * @param EventRepository $eventRepository + * @param TokenStorageInterface $tokenStorage + * @param AuthorizationHelper $authorizationHelper + * @param UrlGeneratorInterface $urlGenerator + * @param TranslatorInterface $translator + */ + public function __construct( + EventRepository $eventRepository, + TokenStorageInterface $tokenStorage, + AuthorizationHelper $authorizationHelper, + UrlGeneratorInterface $urlGenerator, + TranslatorInterface $translator + ) { + $this->eventRepository = $eventRepository; + $this->user = $tokenStorage->getToken()->getUser(); + $this->authorizationHelper = $authorizationHelper; + $this->urlGenerator = $urlGenerator; + $this->translator = $translator; + } + + /** + * @param OptionsResolver $resolver + */ + 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) + ; + $resolver + ->setDefined('role') + ->addAllowedTypes('role', array(Role::class, 'null')) + ->setDefault('role', null) + ; + + // add the default options + $resolver->setDefaults(array( + 'class' => Event::class, + 'choice_label' => function(Event $e) { + return $e->getDate()->format('d-m-Y'). ' ' .$e->getName(); + // TODO ajouter si possible le type de l'événement ! + }, + 'placeholder' => 'Pick an event', + 'attr' => array('class' => 'select2 '), + 'choice_attr' => function(Event $e) { + return array('data-center' => $e->getCenter()->getId()); + }, + 'choiceloader' => function(Options $options) { + $centers = $this->filterCenters($options); + return new EventChoiceLoader($this->eventRepository, $centers); + } + )); + } + + /** + * @return null|string + */ + public function getParent() + { + return EntityType::class; + } + + /** + * @param \Symfony\Component\Form\FormView $view + * @param \Symfony\Component\Form\FormInterface $form + * @param array $options + */ + public function buildView(\Symfony\Component\Form\FormView $view, \Symfony\Component\Form\FormInterface $form, array $options) + { + $view->vars['attr']['data-person-picker'] = true; + $view->vars['attr']['data-select-interactive-loading'] = true; + $view->vars['attr']['data-search-url'] = $this->urlGenerator + ->generate('chill_main_search', [ 'name' => EventSearch::NAME, '_format' => 'json' ]); + $view->vars['attr']['data-placeholder'] = $this->translator->trans($options['placeholder']); + $view->vars['attr']['data-no-results-label'] = $this->translator->trans('select2.no_results'); + $view->vars['attr']['data-error-load-label'] = $this->translator->trans('select2.error_loading'); + $view->vars['attr']['data-searching-label'] = $this->translator->trans('select2.searching'); + } + + /** + * @param Options $options + * @return array + */ + protected function filterCenters(Options $options) + { + + 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(); + $optionsCenters = is_array($options['centers']) ? + $options['centers'] : array($options['centers']); + + foreach ($optionsCenters 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; + } + } + return $selectedCenters; + } + +} \ No newline at end of file diff --git a/Repository/EventRepository.php b/Repository/EventRepository.php new file mode 100644 index 000000000..a18e664ba --- /dev/null +++ b/Repository/EventRepository.php @@ -0,0 +1,35 @@ +, + * + * 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\Repository; + +use Doctrine\ORM\EntityRepository; + +/** + * Class EventRepository + * + * @package Chill\EventBundle\Repository + * @author Mathieu Jaumotte jaum_mathieu@collectifs.net + */ +class EventRepository extends EntityRepository +{ + +} diff --git a/Resources/config/doctrine/Event.orm.yml b/Resources/config/doctrine/Event.orm.yml index 7dabeb880..bb0237c3f 100644 --- a/Resources/config/doctrine/Event.orm.yml +++ b/Resources/config/doctrine/Event.orm.yml @@ -1,6 +1,7 @@ Chill\EventBundle\Entity\Event: type: entity table: chill_event_event + repositoryClass: Chill\EventBundle\Repository\EventRepository id: id: type: integer diff --git a/Resources/config/routing/event.yml b/Resources/config/routing/event.yml index 20580c37a..4271c6d49 100644 --- a/Resources/config/routing/event.yml +++ b/Resources/config/routing/event.yml @@ -39,3 +39,4 @@ chill_event__event_update: chill_event__list_by_person: path: /{person_id}/list defaults: { _controller: "ChillEventBundle:Event:listByPerson" } + methods: [ GET ] diff --git a/Resources/config/services/forms.yml b/Resources/config/services/forms.yml index 90981d31b..1ad542dd1 100644 --- a/Resources/config/services/forms.yml +++ b/Resources/config/services/forms.yml @@ -46,3 +46,13 @@ services: - "@chill.main.helper.translatable_string" tags: - { name: form.type } + + Chill\EventBundle\Form\Type\PickSubscriptionType: + arguments: + $eventRepository: "@chill_event.repository.event" + $tokenStorage: "@security.token_storage" + $authorizationHelper: "@chill.main.security.authorization.helper" + $urlGenerator: '@Symfony\Component\Routing\Generator\UrlGeneratorInterface' + $translator: '@Symfony\Component\Translation\TranslatorInterface' + tags: + - { name: form.type } diff --git a/Resources/config/services/repositories.yml b/Resources/config/services/repositories.yml index d27130bcd..ca050ed4b 100644 --- a/Resources/config/services/repositories.yml +++ b/Resources/config/services/repositories.yml @@ -1,6 +1,7 @@ services: + chill_event.repository.event: - class: Doctrine\ORM\EntityRepository + class: Chill\EventBundle\Repository\EventRepository factory: ['@doctrine.orm.entity_manager', getRepository] arguments: - 'Chill\EventBundle\Entity\Event' @@ -15,4 +16,4 @@ services: class: Doctrine\ORM\EntityRepository factory: ['@doctrine.orm.entity_manager', getRepository] arguments: - - 'Chill\EventBundle\Entity\Status' \ No newline at end of file + - 'Chill\EventBundle\Entity\Status' diff --git a/Resources/translations/messages.fr.yml b/Resources/translations/messages.fr.yml index 68860c593..2bfc74ac9 100644 --- a/Resources/translations/messages.fr.yml +++ b/Resources/translations/messages.fr.yml @@ -66,3 +66,6 @@ past: passé futur: futur 'subscribed by %user% to event "%event%" (%event_type%)': inscrit par %user% à l'événement "%event%" (%event_type%) Show the event: Voir l'événement + +Subscribe an event: Ajouter un événement +Pick an event: Choisir un événement \ No newline at end of file diff --git a/Resources/views/Event/listByPerson.html.twig b/Resources/views/Event/listByPerson.html.twig index 9d9fd7079..b8ae0b326 100644 --- a/Resources/views/Event/listByPerson.html.twig +++ b/Resources/views/Event/listByPerson.html.twig @@ -95,6 +95,7 @@ {% endfor %} + @@ -102,8 +103,12 @@ {{ chill_pagination(paginator) }} {% endif %} -{# -{{ dump() }} -#} +
+ {{ form_start(form_add_participation_by_event) }} + {{ form_widget(form_add_participation_by_event.event_id, { 'attr' : { 'style' : 'width: 25em; display:inline-block; ' } } ) }} + {{ form_widget(form_add_participation_by_event.submit, { 'attr' : { 'class' : 'sc-button bt-create' } } ) }} + {{ form_rest(form_add_participation_by_event) }} + {{ form_end(form_add_participation_by_event) }} +
{% endblock %} \ No newline at end of file diff --git a/Search/EventSearch.php b/Search/EventSearch.php index 4fc5fc19a..bd87b5ad5 100644 --- a/Search/EventSearch.php +++ b/Search/EventSearch.php @@ -3,6 +3,7 @@ namespace Chill\EventBundle\Search; +use Chill\EventBundle\Entity\Event; use Chill\MainBundle\Search\AbstractSearch; use Doctrine\ORM\EntityRepository; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; @@ -101,7 +102,8 @@ class EventSearch extends AbstractSearch $total = $this->count($terms); $paginator = $this->paginationFactory->create($total); - return $this->templating->render('ChillEventBundle:Event:list.html.twig', + if ($format === 'html') { + return $this->templating->render('ChillEventBundle:Event:list.html.twig', array( 'events' => $this->search($terms, $start, $limit, $options), 'pattern' => $this->recomposePattern($terms, $this->getAvailableTerms(), $terms['_domain']), @@ -111,6 +113,23 @@ class EventSearch extends AbstractSearch 'paginator' => $paginator, 'search_name' => self::NAME )); + } + else if ($format === 'json') { + $results = []; + $search = $this->search($terms, $start, $limit, $options); + foreach ($search as $item) { + $results[] = [ + 'id' => $item->getId(), + 'text' => $item->getDate()->format('d-m-Y, H:i'). ' → ' .$item->getName() + ]; + } + return [ + 'results' => $results, + 'pagination' => [ + 'more' => $paginator->hasNextPage() + ] + ]; + } } protected function getAvailableTerms() @@ -207,8 +226,6 @@ class EventSearch extends AbstractSearch $qb->andWhere($where); } - - return $qb; } }