This commit is contained in:
Marc Ducobu 2016-03-24 10:47:03 +01:00
commit ab609a4ddb
15 changed files with 552 additions and 5 deletions

3
.gitignore vendored
View File

@ -17,5 +17,8 @@ app/config/parameters.ini
app/config/parameters.yml app/config/parameters.yml
Tests/Fixtures/App/config/parameters.yml Tests/Fixtures/App/config/parameters.yml
# fixtures
Resources/test/*
#composer #composer
composer.lock composer.lock

View File

@ -4,8 +4,13 @@ namespace Chill\EventBundle\Controller;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Chill\PersonBundle\Form\Type\PickPersonType;
use Chill\EventBundle\Entity\Event; use Chill\EventBundle\Entity\Event;
use Chill\EventBundle\Form\EventType; use Chill\EventBundle\Form\EventType;
use Symfony\Component\Security\Core\Role\Role;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
/** /**
@ -103,11 +108,52 @@ class EventController extends Controller
$this->denyAccessUnlessGranted('CHILL_EVENT_SEE_DETAILS', $entity, $this->denyAccessUnlessGranted('CHILL_EVENT_SEE_DETAILS', $entity,
"You are not allowed to see details on this event"); "You are not allowed to see details on this event");
$addParticipationByPersonForm = $this->createAddParticipationByPersonForm($entity);
return $this->render('ChillEventBundle:Event:show.html.twig', array( return $this->render('ChillEventBundle:Event:show.html.twig', array(
'event' => $entity 'event' => $entity,
'form_add_participation_by_person' => $addParticipationByPersonForm->createView()
)); ));
} }
/**
* create a form to add a participation with a person
*
* @return \Symfony\Component\Form\FormInterface
*/
protected function createAddParticipationByPersonForm(Event $event)
{
/* @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('person_id', PickPersonType::class, array(
'role' => new Role('CHILL_EVENT_CREATE'),
'centers' => $event->getCenter()
));
$builder->add('event_id', HiddenType::class, array(
'data' => $event->getId()
));
$builder->add('submit', SubmitType::class,
array(
'label' => 'Add a participation'
));
return $builder->getForm();
}
/** /**
* Displays a form to edit an existing Event entity. * Displays a form to edit an existing Event entity.

View File

@ -0,0 +1,151 @@
<?php
/*
* Copyright (C) 2016 Champs-Libres <info@champs-libres.coop>
*
* 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 <http://www.gnu.org/licenses/>.
*/
namespace Chill\EventBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Chill\EventBundle\Entity\Participation;
use Chill\EventBundle\Form\ParticipationType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Chill\EventBundle\Security\Authorization\ParticipationVoter;
/**
*
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class ParticipationController extends Controller
{
public function newAction(Request $request)
{
$participation = $this->handleRequest($request, new Participation());
$this->denyAccessUnlessGranted(ParticipationVoter::CREATE,
$participation, 'The user is not allowed to create this participation');
$form = $this->createCreateForm($participation);
return $this->render('ChillEventBundle:Participation:new.html.twig', array(
'form' => $form->createView(),
'participation' => $participation
));
}
public function createAction(Request $request)
{
$participation = $this->handleRequest($request, new Participation());
$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'
));
return $this->redirectToRoute('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
* @param Participation $participation
* @return Participation
* @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)
{
$em = $this->getDoctrine()->getManager();
$event_id = $request->query->getInt('event_id', null);
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);
}
$person_id = $request->query->getInt('person_id', null);
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);
}
return $participation;
}
/**
*
* @param Participation $participation
* @return \Symfony\Component\Form\FormInterface
*/
public function createCreateForm(Participation $participation)
{
$form = $this->createForm(ParticipationType::class, $participation, array(
'event_type' => $participation->getEvent()->getType(),
'action' => $this->generateUrl('chill_event_participation_create', array(
'event_id' => $participation->getEvent()->getId(),
'person_id' => $participation->getPerson()->getId()
))
));
$form->add('submit', SubmitType::class, array(
'label' => 'Create'
));
return $form;
}
}

View File

@ -56,19 +56,31 @@ class LoadRolesACL extends AbstractFixture implements OrderedFixtureInterface
break; break;
} }
printf("Adding CHILL_EVENT_UPDATE & CHILL_EVENT_CREATE to %s " printf("Adding CHILL_EVENT_UPDATE & CHILL_EVENT_CREATE "
. "CHILL_EVENT_PARTICIPATION_UPDATE & CHILL_EVENT_PARTICIPATION_CREATE "
. "to %s "
. "permission group, scope '%s' \n", . "permission group, scope '%s' \n",
$permissionsGroup->getName(), $scope->getName()['en']); $permissionsGroup->getName(), $scope->getName()['en']);
$roleScopeUpdate = (new RoleScope()) $roleScopeUpdate = (new RoleScope())
->setRole('CHILL_EVENT_UPDATE') ->setRole('CHILL_EVENT_UPDATE')
->setScope($scope); ->setScope($scope);
$roleScopeUpdate2 = (new RoleScope())
->setRole('CHILL_EVENT_PARTICIPATION_UPDATE')
->setScope($scope);
$permissionsGroup->addRoleScope($roleScopeUpdate); $permissionsGroup->addRoleScope($roleScopeUpdate);
$permissionsGroup->addRoleScope($roleScopeUpdate2);
$roleScopeCreate = (new RoleScope()) $roleScopeCreate = (new RoleScope())
->setRole('CHILL_EVENT_CREATE') ->setRole('CHILL_EVENT_CREATE')
->setScope($scope); ->setScope($scope);
$roleScopeCreate2 = (new RoleScope())
->setRole('CHILL_EVENT_PARTICIPATION_CREATE')
->setScope($scope);
$permissionsGroup->addRoleScope($roleScopeCreate); $permissionsGroup->addRoleScope($roleScopeCreate);
$permissionsGroup->addRoleScope($roleScopeCreate2);
$manager->persist($roleScopeUpdate); $manager->persist($roleScopeUpdate);
$manager->persist($roleScopeUpdate2);
$manager->persist($roleScopeCreate); $manager->persist($roleScopeCreate);
$manager->persist($roleScopeCreate2);
} }
} }

View File

@ -2,10 +2,14 @@
namespace Chill\EventBundle\Entity; namespace Chill\EventBundle\Entity;
use Chill\MainBundle\Entity\HasScopeInterface;
use Chill\MainBundle\Entity\HasCenterInterface;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
/** /**
* Participation * Participation
*/ */
class Participation class Participation implements HasCenterInterface, HasScopeInterface
{ {
/** /**
* @var integer * @var integer
@ -183,4 +187,50 @@ class Participation
{ {
return $this->status; return $this->status;
} }
public function getCenter()
{
if ($this->getEvent() === NULL) {
throw new \RuntimeException('The event is not linked with this instance. '
. 'You should initialize the event with a valid center before.');
}
return $this->getEvent()->getCenter();
}
public function getScope()
{
if ($this->getEvent() === NULL) {
throw new \RuntimeException('The event is not linked with this instance. '
. 'You should initialize the event with a valid center before.');
}
return $this->getEvent()->getCircle();
}
/**
* Check that :
*
* - the role can be associated with this event type
* - the status can be associated with this event type
*
* @param ExecutionContextInterface $context
*/
public function isConsistent(ExecutionContextInterface $context)
{
if ($this->getRole()->getType()->getId() !==
$this->getEvent()->getType()->getId()) {
$context->buildViolation('The role is not allowed with this event type')
->atPath('role')
->addViolation();
}
if ($this->getStatus()->getType()->getId() !==
$this->getEvent()->getType()->getId()) {
$context->buildViolation('The status is not allowed with this event type')
->atPath('status')
->addViolation();
}
}
} }

117
Form/ParticipationType.php Normal file
View File

@ -0,0 +1,117 @@
<?php
/*
* Copyright (C) 2016 Champs-Libres <info@champs-libres.coop>
*
* 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 <http://www.gnu.org/licenses/>.
*/
namespace Chill\EventBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Chill\EventBundle\Entity\EventType;
use Chill\EventBundle\Entity\Status;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Doctrine\ORM\EntityRepository;
use Chill\EventBundle\Entity\Role;
use Chill\MainBundle\Templating\TranslatableStringHelper;
/**
* A type to create a participation
*
* If the `event` option is defined, the role will be restricted
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class ParticipationType extends AbstractType
{
/**
*
* @var TranslatableStringHelper
*/
protected $translatableStringHelper;
public function __construct(TranslatableStringHelper $translatableStringHelper)
{
$this->translatableStringHelper = $translatableStringHelper;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
// local copy of variable for Closure
$translatableStringHelper = $this->translatableStringHelper;
// add role
$builder->add('role', EntityType::class, array(
'class' => Role::class,
'query_builder' => function (EntityRepository $er) use ($options) {
$qb = $er->createQueryBuilder('r');
if ($options['event_type'] instanceof EventType) {
$qb->where($qb->expr()->eq('r.type', ':event_type'))
->setParameter('event_type', $options['event_type']);
}
$qb->andWhere($qb->expr()->eq('r.active', ':active'))
->setParameter('active', true);
return $qb;
},
'choice_attr' => function(Role $r) {
return array(
'data-event-type' => $r->getType()->getId()
);
},
'choice_label' => function(Role $r) use ($translatableStringHelper) {
return $translatableStringHelper->localize($r->getLabel());
}
));
// add a status
$builder->add('status', EntityType::class, array(
'class' => Status::class,
'choice_attr' => function(Status $s) {
return array(
'data-event-type' => $s->getType()->getId()
);
},
'query_builder' => function (EntityRepository $er) use ($options) {
$qb = $er->createQueryBuilder('s');
if ($options['event_type'] instanceof EventType) {
$qb->where($qb->expr()->eq('s.type', ':event_type'))
->setParameter('event_type', $options['event_type']);
}
$qb->andWhere($qb->expr()->eq('s.active', ':active'))
->setParameter('active', true);
return $qb;
},
'choice_label' => function(Status $s) use ($translatableStringHelper) {
return $translatableStringHelper->localize($s->getLabel());
}
));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefined('event_type')
->setAllowedTypes('event_type', array('null', EventType::class))
->setDefault('event_type', 'null');
}
}

View File

@ -13,3 +13,7 @@ chill_event_admin_role:
chill_event_admin_event_type: chill_event_admin_event_type:
resource: "@ChillEventBundle/Resources/config/routing/eventtype.yml" resource: "@ChillEventBundle/Resources/config/routing/eventtype.yml"
prefix: /{_locale}/admin/event/event_type prefix: /{_locale}/admin/event/event_type
chill_event_participation:
resource: "@ChillEventBundle/Resources/config/routing/participation.yml"
prefix: /{_locale}/event/participation

View File

@ -0,0 +1,7 @@
chill_event_participation_new:
path: /new
defaults: { _controller: ChillEventBundle:Participation:new }
chill_event_participation_create:
path: /create
defaults: { _controller: ChillEventBundle:Participation:create }

View File

@ -6,3 +6,11 @@ services:
tags: tags:
- { name: chill.role } - { name: chill.role }
- { name: security.voter } - { name: security.voter }
chill_event.event_participation:
class: Chill\EventBundle\Security\Authorization\ParticipationVoter
arguments:
- "@chill.main.security.authorization.helper"
tags:
- { name: chill.role }
- { name: security.voter }

View File

@ -14,3 +14,10 @@ services:
- "@chill.main.helper.translatable_string" - "@chill.main.helper.translatable_string"
tags: tags:
- { name: form.type } - { name: form.type }
chill.event.form.participation_type:
class: Chill\EventBundle\Form\ParticipationType
arguments:
- "@chill.main.helper.translatable_string"
tags:
- { name: form.type }

View File

@ -0,0 +1,10 @@
Chill\EventBundle\Event\Participation:
properties:
event:
- NotNull: ~
status:
- NotNull: ~
person:
- NotNull: ~
constraints:
- Callback: [isConsistent]

View File

@ -12,8 +12,13 @@ Last update: Dernière mise à jour
#CRUD #CRUD
Details of an event: Détails d'un événement Details of an event: Détails d'un événement
Edit all the participations: Modifier toutes les participations Edit all the participations: Modifier toutes les participations
Add a participation: Ajouter un participant
Participation creation: Ajouter une participation
Associated person: Personne associée
Associated event: Événement associé
Back to the event: Retour à l'événement
The participation was created: La participation a été créée
#search #search
Event search: Recherche d'événements Event search: Recherche d'événements

View File

@ -36,7 +36,7 @@
</li> </li>
</ul> </ul>
<h2>{{ 'Participation'|trans }}</h2> <h2>{{ 'Participations'|trans }}</h2>
{% set count = event.participations|length %} {% set count = event.participations|length %}
<p>{% transchoice count %}%count% participations to this event{% endtranschoice %}</p> <p>{% transchoice count %}%count% participations to this event{% endtranschoice %}</p>
@ -74,6 +74,14 @@
<ul class="record_actions"> <ul class="record_actions">
<li><a href="#" class="sc-button btn-edit">{{ 'Edit all the participations'|trans }}</a></li> <li><a href="#" class="sc-button btn-edit">{{ 'Edit all the participations'|trans }}</a></li>
<li><div style="padding-left: 3em;">
{{ form_start(form_add_participation_by_person) }}
{{ form_widget(form_add_participation_by_person.person_id, { 'attr' : { 'style' : 'width: 20em; display:inline-block; ' } } ) }}
{{ form_widget(form_add_participation_by_person.submit, { 'attr' : { 'class' : 'sc-button btn-create' } } ) }}
{{ form_rest(form_add_participation_by_person) }}
{{ form_end(form_add_participation_by_person) }}
</div>
</li>
</ul> </ul>
{% endif %} {% endif %}

View File

@ -0,0 +1,37 @@
{% extends 'ChillEventBundle::layout.html.twig' %}
{% import 'ChillPersonBundle:Person:macro.html.twig' as person_macro %}
{% block event_content -%}
<h1>{{ 'Participation creation'|trans }}</h1>
<table>
<tbody>
<tr>
<th>{{ 'Associated person'|trans }}</th>
<td>{{ person_macro.render(participation.person) }}</td>
</tr>
<tr>
<th>{{ 'Associated event'|trans }} </th>
<td>{{ participation.event.label }}</td>
</tr>
</tbody>
</table>
{{ form_start(form) }}
{{ form_row(form.role) }}
{{ form_row(form.status) }}
<ul class="record_actions">
<li>
<a href="{{ path('event_show', { 'event_id' : participation.event.id } ) }}" class="sc-button btn-cancel">
{{ 'Back to the event'|trans }}
</a>
</li>
<li>
{{ form_widget(form.submit, { 'attr' : { 'class' : 'sc-button btn-create' } } ) }}
</li>
</ul>
{{ form_end(form) }}
{% endblock %}

View File

@ -0,0 +1,82 @@
<?php
/*
* Copyright (C) 2016 Champs-Libres <info@champs-libres.coop>
*
* 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 <http://www.gnu.org/licenses/>.
*/
namespace Chill\EventBundle\Security\Authorization;
use Chill\MainBundle\Security\ProvideRoleInterface;
use Chill\MainBundle\Security\Authorization\AbstractChillVoter;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\EventBundle\Entity\Participation;
use Chill\MainBundle\Entity\User;
/**
*
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class ParticipationVoter extends AbstractChillVoter implements ProvideRoleInterface
{
/**
*
* @var AuthorizationHelper
*/
protected $authorizationHelper;
const CREATE = 'CHILL_EVENT_PARTICIPATION_CREATE';
const UPDATE = 'CHILL_EVENT_PARTICIPATION_UPDATE';
public function __construct(AuthorizationHelper $helper)
{
$this->authorizationHelper = $helper;
}
protected function getSupportedAttributes()
{
return array(
self::CREATE, self::UPDATE
);
}
protected function getSupportedClasses()
{
return array(
Participation::class
);
}
protected function isGranted($attribute, $participation, $user = null)
{
if (!$user instanceof User) {
return false;
}
return $this->authorizationHelper->userHasAccess($user, $participation, $attribute);
}
public function getRoles()
{
return $this->getSupportedAttributes();
}
public function getRolesWithoutScope()
{
return null;
}
}