Merge branch '149-150-events-improve' into 'master'

Modernisation fonctionnement module Evénement

Closes #149, #150, and #225

See merge request Chill-Projet/chill-bundles!621
This commit is contained in:
Julien Fastré 2024-02-08 20:19:30 +00:00
commit b3d1320c94
47 changed files with 1483 additions and 444 deletions

View File

@ -0,0 +1,5 @@
kind: Feature
body: Modernize the event bundle, with some new fields and multiple improvements
time: 2024-02-08T21:01:38.124701724+01:00
custom:
Issue: ""

View File

@ -17,10 +17,11 @@ use Chill\EventBundle\Form\EventType;
use Chill\EventBundle\Form\Type\PickEventType; use Chill\EventBundle\Form\Type\PickEventType;
use Chill\EventBundle\Security\Authorization\EventVoter; use Chill\EventBundle\Security\Authorization\EventVoter;
use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Pagination\PaginatorFactory; use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Form\Type\PickPersonType; use Chill\PersonBundle\Form\Type\PickPersonDynamicType;
use Chill\PersonBundle\Privacy\PrivacyEvent; use Chill\PersonBundle\Privacy\PrivacyEvent;
use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Csv; use PhpOffice\PhpSpreadsheet\Writer\Csv;
@ -37,53 +38,26 @@ use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormFactoryInterface; use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Security\Core\Security;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
/** /**
* Class EventController. * Class EventController.
*/ */
class EventController extends AbstractController final class EventController extends AbstractController
{ {
/**
* @var AuthorizationHelper
*/
protected $authorizationHelper;
/**
* @var EventDispatcherInterface
*/
protected $eventDispatcher;
/**
* @var FormFactoryInterface
*/
protected $formFactoryInterface;
/**
* @var PaginatorFactory
*/
protected $paginator;
/**
* @var TranslatorInterface
*/
protected $translator;
/** /**
* EventController constructor. * EventController constructor.
*/ */
public function __construct( public function __construct(
EventDispatcherInterface $eventDispatcher, private readonly EventDispatcherInterface $eventDispatcher,
AuthorizationHelper $authorizationHelper, private readonly AuthorizationHelperInterface $authorizationHelper,
FormFactoryInterface $formFactoryInterface, private readonly FormFactoryInterface $formFactoryInterface,
TranslatorInterface $translator, private readonly TranslatorInterface $translator,
PaginatorFactory $paginator private readonly PaginatorFactory $paginator,
private readonly Security $security,
) { ) {
$this->eventDispatcher = $eventDispatcher;
$this->authorizationHelper = $authorizationHelper;
$this->formFactoryInterface = $formFactoryInterface;
$this->translator = $translator;
$this->paginator = $paginator;
} }
/** /**
@ -181,7 +155,7 @@ class EventController extends AbstractController
$this->denyAccessUnlessGranted('CHILL_PERSON_SEE', $person); $this->denyAccessUnlessGranted('CHILL_PERSON_SEE', $person);
$reachablesCircles = $this->authorizationHelper->getReachableCircles( $reachablesCircles = $this->authorizationHelper->getReachableScopes(
$this->getUser(), $this->getUser(),
EventVoter::SEE, EventVoter::SEE,
$person->getCenter() $person->getCenter()
@ -233,6 +207,12 @@ class EventController extends AbstractController
*/ */
public function newAction(?Center $center, Request $request) public function newAction(?Center $center, Request $request)
{ {
$user = $this->security->getUser();
if (!$user instanceof User) {
throw new AccessDeniedHttpException('not a regular user. Maybe an administrator ?');
}
if (null === $center) { if (null === $center) {
$center_id = $request->query->get('center_id'); $center_id = $request->query->get('center_id');
$center = $this->getDoctrine()->getRepository(Center::class)->find($center_id); $center = $this->getDoctrine()->getRepository(Center::class)->find($center_id);
@ -240,6 +220,7 @@ class EventController extends AbstractController
$entity = new Event(); $entity = new Event();
$entity->setCenter($center); $entity->setCenter($center);
$entity->setLocation($user->getCurrentLocation());
$form = $this->createCreateForm($entity); $form = $this->createCreateForm($entity);
$form->handleRequest($request); $form->handleRequest($request);
@ -282,7 +263,7 @@ class EventController extends AbstractController
} }
$form = $this->formFactoryInterface $form = $this->formFactoryInterface
->createNamedBuilder(null, FormType::class, null, [ ->createNamedBuilder('', FormType::class, null, [
'csrf_protection' => false, 'csrf_protection' => false,
]) ])
->setMethod('GET') ->setMethod('GET')
@ -323,7 +304,7 @@ class EventController extends AbstractController
} }
$this->denyAccessUnlessGranted( $this->denyAccessUnlessGranted(
'CHILL_EVENT_SEE_DETAILS', EventVoter::SEE_DETAILS,
$event, $event,
'You are not allowed to see details on this event' 'You are not allowed to see details on this event'
); );
@ -367,7 +348,7 @@ class EventController extends AbstractController
$this->addFlash('success', $this->translator $this->addFlash('success', $this->translator
->trans('The event was updated')); ->trans('The event was updated'));
return $this->redirectToRoute('chill_event__event_edit', ['event_id' => $event_id]); return $this->redirectToRoute('chill_event__event_show', ['event_id' => $event_id]);
} }
return $this->render('@ChillEvent/Event/edit.html.twig', [ return $this->render('@ChillEvent/Event/edit.html.twig', [
@ -385,7 +366,7 @@ class EventController extends AbstractController
{ {
/** @var \Symfony\Component\Form\FormBuilderInterface $builder */ /** @var \Symfony\Component\Form\FormBuilderInterface $builder */
$builder = $this $builder = $this
->get('form.factory') ->formFactoryInterface
->createNamedBuilder( ->createNamedBuilder(
null, null,
FormType::class, FormType::class,
@ -430,11 +411,9 @@ class EventController extends AbstractController
*/ */
protected function createAddParticipationByPersonForm(Event $event) protected function createAddParticipationByPersonForm(Event $event)
{ {
/** @var \Symfony\Component\Form\FormBuilderInterface $builder */ $builder = $this->formFactoryInterface
$builder = $this
->get('form.factory')
->createNamedBuilder( ->createNamedBuilder(
null, '',
FormType::class, FormType::class,
null, null,
[ [
@ -444,23 +423,17 @@ class EventController extends AbstractController
] ]
); );
$builder->add('person_id', PickPersonType::class, [ $builder->add('person_id', PickPersonDynamicType::class, [
'role' => 'CHILL_EVENT_CREATE', 'as_id' => true,
'centers' => $event->getCenter(), 'multiple' => false,
'submit_on_adding_new_entity' => true,
'label' => 'Add a participation',
]); ]);
$builder->add('event_id', HiddenType::class, [ $builder->add('event_id', HiddenType::class, [
'data' => $event->getId(), 'data' => $event->getId(),
]); ]);
$builder->add(
'submit',
SubmitType::class,
[
'label' => 'Add a participation',
]
);
return $builder->getForm(); return $builder->getForm();
} }
@ -469,7 +442,7 @@ class EventController extends AbstractController
*/ */
protected function createExportByFormatForm() protected function createExportByFormatForm()
{ {
$builder = $this->createFormBuilder() $builder = $this->createFormBuilder(['format' => 'xlsx'])
->add('format', ChoiceType::class, [ ->add('format', ChoiceType::class, [
'choices' => [ 'choices' => [
'xlsx' => 'xlsx', 'xlsx' => 'xlsx',

View File

@ -0,0 +1,118 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\EventBundle\Controller;
use Chill\EventBundle\Entity\Event;
use Chill\EventBundle\Entity\EventType;
use Chill\EventBundle\Repository\EventACLAwareRepositoryInterface;
use Chill\EventBundle\Repository\EventTypeRepository;
use Chill\MainBundle\Pagination\PaginatorFactoryInterface;
use Chill\MainBundle\Templating\Listing\FilterOrderHelper;
use Chill\MainBundle\Templating\Listing\FilterOrderHelperFactory;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Form\Type\PickPersonDynamicType;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Twig\Environment;
final readonly class EventListController
{
public function __construct(
private Environment $environment,
private EventACLAwareRepositoryInterface $eventACLAwareRepository,
private EventTypeRepository $eventTypeRepository,
private FilterOrderHelperFactory $filterOrderHelperFactory,
private FormFactoryInterface $formFactory,
private PaginatorFactoryInterface $paginatorFactory,
private TranslatableStringHelperInterface $translatableStringHelper,
private UrlGeneratorInterface $urlGenerator,
) {
}
/**
* @Route("{_locale}/event/event/list", name="chill_event_event_list")
*/
public function __invoke(): Response
{
$filter = $this->buildFilterOrder();
$filterData = [
'q' => (string) $filter->getQueryString(),
'dates' => $filter->getDateRangeData('dates'),
'event_types' => $filter->getEntityChoiceData('event_types'),
];
$total = $this->eventACLAwareRepository->countAllViewable($filterData);
$pagination = $this->paginatorFactory->create($total);
$events = $this->eventACLAwareRepository->findAllViewable($filterData, $pagination->getCurrentPageFirstItemNumber(), $pagination->getItemsPerPage());
$eventForms = [];
foreach ($events as $event) {
$eventForms[$event->getId()] = $this->createAddParticipationByPersonForm($event)->createView();
}
return new Response($this->environment->render(
'@ChillEvent/Event/page_list.html.twig',
[
'events' => $events,
'pagination' => $pagination,
'eventForms' => $eventForms,
'filter' => $filter,
]
));
}
private function buildFilterOrder(): FilterOrderHelper
{
$types = $this->eventTypeRepository->findAllActive();
$builder = $this->filterOrderHelperFactory->create(__METHOD__);
$builder
->addDateRange('dates', 'event.filter.event_dates')
->addSearchBox(['name'])
->addEntityChoice('event_types', 'event.filter.event_types', EventType::class, $types, [
'choice_label' => fn (EventType $e) => $this->translatableStringHelper->localize($e->getName()),
]);
return $builder->build();
}
private function createAddParticipationByPersonForm(Event $event): FormInterface
{
$builder = $this->formFactory
->createNamedBuilder(
'',
FormType::class,
null,
[
'method' => 'GET',
'action' => $this->urlGenerator->generate('chill_event_participation_new'),
'csrf_protection' => false,
]
);
$builder->add('person_id', PickPersonDynamicType::class, [
'as_id' => true,
'multiple' => false,
'submit_on_adding_new_entity' => true,
'label' => 'Add a participation',
]);
$builder->add('event_id', HiddenType::class, [
'data' => $event->getId(),
]);
return $builder->getForm();
}
}

View File

@ -14,7 +14,10 @@ namespace Chill\EventBundle\Controller;
use Chill\EventBundle\Entity\Event; use Chill\EventBundle\Entity\Event;
use Chill\EventBundle\Entity\Participation; use Chill\EventBundle\Entity\Participation;
use Chill\EventBundle\Form\ParticipationType; use Chill\EventBundle\Form\ParticipationType;
use Chill\EventBundle\Repository\EventRepository;
use Chill\EventBundle\Security\Authorization\ParticipationVoter; use Chill\EventBundle\Security\Authorization\ParticipationVoter;
use Chill\PersonBundle\Repository\PersonRepository;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Collection;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
@ -28,13 +31,17 @@ use Symfony\Contracts\Translation\TranslatorInterface;
/** /**
* Class ParticipationController. * Class ParticipationController.
*/ */
class ParticipationController extends AbstractController final class ParticipationController extends AbstractController
{ {
/** /**
* ParticipationController constructor. * ParticipationController constructor.
*/ */
public function __construct(private readonly LoggerInterface $logger, private readonly TranslatorInterface $translator) public function __construct(
{ private readonly LoggerInterface $logger,
private readonly TranslatorInterface $translator,
private readonly EventRepository $eventRepository,
private readonly PersonRepository $personRepository,
) {
} }
/** /**
@ -230,6 +237,7 @@ class ParticipationController extends AbstractController
return $this->render('@ChillEvent/Participation/new.html.twig', [ return $this->render('@ChillEvent/Participation/new.html.twig', [
'form' => $form->createView(), 'form' => $form->createView(),
'participation' => $participation, 'participation' => $participation,
'ignored_participations' => [],
]); ]);
} }
@ -539,7 +547,7 @@ class ParticipationController extends AbstractController
* If the request is multiple, the $participation object is cloned. * If the request is multiple, the $participation object is cloned.
* Limitations: the $participation should not be persisted. * Limitations: the $participation should not be persisted.
* *
* @return Participation|Participation[] return one single participation if $multiple == false * @return Participation|list<Participation> return one single participation if $multiple == false
* *
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException if the event/person is not found * @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 * @throws \Symfony\Component\Security\Core\Exception\AccessDeniedException if the user does not have access to event/person
@ -556,30 +564,25 @@ class ParticipationController extends AbstractController
} }
$event_id = $request->query->getInt('event_id', 0); // sf4 check: $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 (null !== $event_id) { $event = $this->eventRepository->find($event_id);
$event = $em->getRepository(Event::class)
->find($event_id);
if (null === $event) { if (null === $event) {
throw $this->createNotFoundException('The event with id '.$event_id.' is not found'); 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->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 // this script should be able to handle multiple, so we translate
// single person_id in an array // single person_id in an array
$persons_ids = $request->query->has('person_id') ? $persons_ids = $request->query->has('person_id') ?
[$request->query->getInt('person_id', 0)] // sf4 check: [$request->query->get('person_id', 0)]
// prevent error: `Argument 2 passed to ::getInt() must be of the type int, null given`
: explode(',', (string) $request->query->get('persons_ids')); : explode(',', (string) $request->query->get('persons_ids'));
$participations = []; $participations = [];
@ -588,15 +591,14 @@ class ParticipationController extends AbstractController
$participation = \count($persons_ids) > 1 ? clone $participation : $participation; $participation = \count($persons_ids) > 1 ? clone $participation : $participation;
if (null !== $person_id) { if (null !== $person_id) {
$person = $em->getRepository(\Chill\PersonBundle\Entity\Person::class) $person = $this->personRepository->find($person_id);
->find($person_id);
if (null === $person) { if (null === $person) {
throw $this->createNotFoundException('The person with id '.$person_id.' is not found'); throw $this->createNotFoundException('The person with id '.$person_id.' is not found');
} }
$this->denyAccessUnlessGranted( $this->denyAccessUnlessGranted(
'CHILL_PERSON_SEE', PersonVoter::SEE,
$person, $person,
'The user is not allowed to see the person' 'The user is not allowed to see the person'
); );

View File

@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\EventBundle\DependencyInjection; namespace Chill\EventBundle\DependencyInjection;
use Chill\EventBundle\Security\Authorization\EventVoter; use Chill\EventBundle\Security\Authorization\EventVoter;
use Chill\EventBundle\Security\Authorization\ParticipationVoter;
use Symfony\Component\Config\FileLocator; use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
@ -33,10 +34,8 @@ class ChillEventExtension extends Extension implements PrependExtensionInterface
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../config')); $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../config'));
$loader->load('services.yaml'); $loader->load('services.yaml');
$loader->load('services/authorization.yaml'); $loader->load('services/authorization.yaml');
$loader->load('services/controller.yaml');
$loader->load('services/fixtures.yaml'); $loader->load('services/fixtures.yaml');
$loader->load('services/forms.yaml'); $loader->load('services/forms.yaml');
$loader->load('services/menu.yaml');
$loader->load('services/repositories.yaml'); $loader->load('services/repositories.yaml');
$loader->load('services/search.yaml'); $loader->load('services/search.yaml');
$loader->load('services/timeline.yaml'); $loader->load('services/timeline.yaml');
@ -61,6 +60,8 @@ class ChillEventExtension extends Extension implements PrependExtensionInterface
EventVoter::SEE_DETAILS => [EventVoter::SEE], EventVoter::SEE_DETAILS => [EventVoter::SEE],
EventVoter::UPDATE => [EventVoter::SEE_DETAILS], EventVoter::UPDATE => [EventVoter::SEE_DETAILS],
EventVoter::CREATE => [EventVoter::SEE_DETAILS], EventVoter::CREATE => [EventVoter::SEE_DETAILS],
ParticipationVoter::SEE_DETAILS => [ParticipationVoter::SEE],
ParticipationVoter::UPDATE => [ParticipationVoter::SEE_DETAILS],
], ],
]); ]);
} }

View File

@ -11,15 +11,23 @@ declare(strict_types=1);
namespace Chill\EventBundle\Entity; namespace Chill\EventBundle\Entity;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\MainBundle\Doctrine\Model\TrackCreationInterface;
use Chill\MainBundle\Doctrine\Model\TrackCreationTrait;
use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface;
use Chill\MainBundle\Doctrine\Model\TrackUpdateTrait;
use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable;
use Chill\MainBundle\Entity\HasCenterInterface; use Chill\MainBundle\Entity\HasCenterInterface;
use Chill\MainBundle\Entity\HasScopeInterface; use Chill\MainBundle\Entity\HasScopeInterface;
use Chill\MainBundle\Entity\Location;
use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use DateTime; use DateTime;
use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/** /**
* Class Event. * Class Event.
@ -30,10 +38,15 @@ use Doctrine\ORM\Mapping as ORM;
* *
* @ORM\HasLifecycleCallbacks * @ORM\HasLifecycleCallbacks
*/ */
class Event implements HasCenterInterface, HasScopeInterface class Event implements HasCenterInterface, HasScopeInterface, TrackCreationInterface, TrackUpdateInterface
{ {
use TrackCreationTrait;
use TrackUpdateTrait;
/** /**
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Center") * @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Center")A
*
* @Assert\NotNull()
*/ */
private ?Center $center = null; private ?Center $center = null;
@ -63,6 +76,8 @@ class Event implements HasCenterInterface, HasScopeInterface
/** /**
* @ORM\Column(type="string", length=150) * @ORM\Column(type="string", length=150)
*
* @Assert\NotBlank()
*/ */
private ?string $name = null; private ?string $name = null;
@ -77,15 +92,45 @@ class Event implements HasCenterInterface, HasScopeInterface
/** /**
* @ORM\ManyToOne(targetEntity="Chill\EventBundle\Entity\EventType") * @ORM\ManyToOne(targetEntity="Chill\EventBundle\Entity\EventType")
*
* @Assert\NotNull()
*/ */
private ?EventType $type = null; private ?EventType $type = null;
/**
* @ORM\Embedded(class=CommentEmbeddable::class, columnPrefix="comment_")
*/
private CommentEmbeddable $comment;
/**
* @ORM\ManyToOne(targetEntity=Location::class)
*
* @ORM\JoinColumn(nullable=true)
*/
private ?Location $location = null;
/**
* @var Collection<StoredObject>
*
* @ORM\ManyToMany(targetEntity=StoredObject::class, cascade={"persist","refresh"})
*
* @ORM\JoinTable("chill_event_event_documents")
*/
private Collection $documents;
/**
* @ORM\Column(type="decimal", precision=10, scale=4, nullable=true, options={"default": null})
*/
private string $organizationCost = '0.0';
/** /**
* Event constructor. * Event constructor.
*/ */
public function __construct() public function __construct()
{ {
$this->participations = new ArrayCollection(); $this->participations = new ArrayCollection();
$this->documents = new ArrayCollection();
$this->comment = new CommentEmbeddable();
} }
/** /**
@ -100,6 +145,22 @@ class Event implements HasCenterInterface, HasScopeInterface
return $this; return $this;
} }
public function addDocument(StoredObject $storedObject): self
{
if ($this->documents->contains($storedObject)) {
$this->documents[] = $storedObject;
}
return $this;
}
public function removeDocument(StoredObject $storedObject): self
{
$this->documents->removeElement($storedObject);
return $this;
}
/** /**
* @return Center * @return Center
*/ */
@ -259,4 +320,44 @@ class Event implements HasCenterInterface, HasScopeInterface
return $this; return $this;
} }
public function getComment(): CommentEmbeddable
{
return $this->comment;
}
public function setComment(CommentEmbeddable $comment): void
{
$this->comment = $comment;
}
public function getLocation(): ?Location
{
return $this->location;
}
public function setLocation(?Location $location): void
{
$this->location = $location;
}
public function getDocuments(): Collection
{
return $this->documents;
}
public function setDocuments(Collection $documents): void
{
$this->documents = $documents;
}
public function getOrganizationCost(): string
{
return $this->organizationCost;
}
public function setOrganizationCost(string $organizationCost): void
{
$this->organizationCost = $organizationCost;
}
} }

View File

@ -11,13 +11,17 @@ declare(strict_types=1);
namespace Chill\EventBundle\Entity; namespace Chill\EventBundle\Entity;
use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Doctrine\Model\TrackCreationInterface;
use Chill\MainBundle\Doctrine\Model\TrackCreationTrait;
use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface;
use Chill\MainBundle\Doctrine\Model\TrackUpdateTrait;
use Chill\MainBundle\Entity\HasCenterInterface; use Chill\MainBundle\Entity\HasCenterInterface;
use Chill\MainBundle\Entity\HasScopeInterface; use Chill\MainBundle\Entity\HasScopeInterface;
use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Entity\Scope;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
use DateTime;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Context\ExecutionContextInterface; use Symfony\Component\Validator\Context\ExecutionContextInterface;
/** /**
@ -26,12 +30,20 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface;
* @ORM\Entity( * @ORM\Entity(
* repositoryClass="Chill\EventBundle\Repository\ParticipationRepository") * repositoryClass="Chill\EventBundle\Repository\ParticipationRepository")
* *
* @ORM\Table(name="chill_event_participation") * @ORM\Table(name="chill_event_participation", uniqueConstraints={
*
* @ORM\UniqueConstraint(name="chill_event_participation_event_person_unique_idx", columns={"event_id", "person_id"})
* })
* *
* @ORM\HasLifecycleCallbacks * @ORM\HasLifecycleCallbacks
*
* @UniqueEntity({"event", "person"}, message="event.validation.person_already_participate_to_event")
*/ */
class Participation implements \ArrayAccess, HasCenterInterface, HasScopeInterface class Participation implements \ArrayAccess, HasCenterInterface, HasScopeInterface, TrackUpdateInterface, TrackCreationInterface
{ {
use TrackCreationTrait;
use TrackUpdateTrait;
/** /**
* @ORM\ManyToOne( * @ORM\ManyToOne(
* targetEntity="Chill\EventBundle\Entity\Event", * targetEntity="Chill\EventBundle\Entity\Event",
@ -48,13 +60,10 @@ class Participation implements \ArrayAccess, HasCenterInterface, HasScopeInterfa
*/ */
private ?int $id = null; private ?int $id = null;
/**
* @ORM\Column(type="datetime")
*/
private ?\DateTime $lastUpdate = null;
/** /**
* @ORM\ManyToOne(targetEntity="Chill\PersonBundle\Entity\Person") * @ORM\ManyToOne(targetEntity="Chill\PersonBundle\Entity\Person")
*
* @Assert\NotNull()
*/ */
private ?Person $person = null; private ?Person $person = null;
@ -65,12 +74,11 @@ class Participation implements \ArrayAccess, HasCenterInterface, HasScopeInterfa
/** /**
* @ORM\ManyToOne(targetEntity="Chill\EventBundle\Entity\Status") * @ORM\ManyToOne(targetEntity="Chill\EventBundle\Entity\Status")
*
* @Assert\NotNull()
*/ */
private ?Status $status = null; private ?Status $status = null;
/**
* @return Center
*/
public function getCenter() public function getCenter()
{ {
if (null === $this->getEvent()) { if (null === $this->getEvent()) {
@ -90,10 +98,8 @@ class Participation implements \ArrayAccess, HasCenterInterface, HasScopeInterfa
/** /**
* Get id. * Get id.
*
* @return int
*/ */
public function getId() public function getId(): int
{ {
return $this->id; return $this->id;
} }
@ -101,11 +107,11 @@ class Participation implements \ArrayAccess, HasCenterInterface, HasScopeInterfa
/** /**
* Get lastUpdate. * Get lastUpdate.
* *
* @return \DateTime * @return \DateTimeInterface|null
*/ */
public function getLastUpdate() public function getLastUpdate()
{ {
return $this->lastUpdate; return $this->getUpdatedAt();
} }
/** /**
@ -235,10 +241,6 @@ class Participation implements \ArrayAccess, HasCenterInterface, HasScopeInterfa
*/ */
public function setEvent(?Event $event = null) public function setEvent(?Event $event = null)
{ {
if ($this->event !== $event) {
$this->update();
}
$this->event = $event; $this->event = $event;
return $this; return $this;
@ -251,10 +253,6 @@ class Participation implements \ArrayAccess, HasCenterInterface, HasScopeInterfa
*/ */
public function setPerson(?Person $person = null) public function setPerson(?Person $person = null)
{ {
if ($person !== $this->person) {
$this->update();
}
$this->person = $person; $this->person = $person;
return $this; return $this;
@ -267,9 +265,6 @@ class Participation implements \ArrayAccess, HasCenterInterface, HasScopeInterfa
*/ */
public function setRole(?Role $role = null) public function setRole(?Role $role = null)
{ {
if ($role !== $this->role) {
$this->update();
}
$this->role = $role; $this->role = $role;
return $this; return $this;
@ -282,10 +277,6 @@ class Participation implements \ArrayAccess, HasCenterInterface, HasScopeInterfa
*/ */
public function setStatus(?Status $status = null) public function setStatus(?Status $status = null)
{ {
if ($this->status !== $status) {
$this->update();
}
$this->status = $status; $this->status = $status;
return $this; return $this;
@ -295,11 +286,11 @@ class Participation implements \ArrayAccess, HasCenterInterface, HasScopeInterfa
* Set lastUpdate. * Set lastUpdate.
* *
* @return Participation * @return Participation
*
* @deprecated
*/ */
protected function update() protected function update()
{ {
$this->lastUpdate = new \DateTime('now');
return $this; return $this;
} }
} }

View File

@ -11,12 +11,18 @@ declare(strict_types=1);
namespace Chill\EventBundle\Form; namespace Chill\EventBundle\Form;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\Form\StoredObjectType;
use Chill\EventBundle\Form\Type\PickEventTypeType; use Chill\EventBundle\Form\Type\PickEventTypeType;
use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Form\Type\ChillCollectionType;
use Chill\MainBundle\Form\Type\ChillDateTimeType; use Chill\MainBundle\Form\Type\ChillDateTimeType;
use Chill\MainBundle\Form\Type\CommentType;
use Chill\MainBundle\Form\Type\PickUserLocationType;
use Chill\MainBundle\Form\Type\ScopePickerType; use Chill\MainBundle\Form\Type\ScopePickerType;
use Chill\MainBundle\Form\Type\UserPickerType; use Chill\MainBundle\Form\Type\UserPickerType;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\MoneyType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\OptionsResolver\OptionsResolver;
@ -47,6 +53,28 @@ class EventType extends AbstractType
'class' => '', 'class' => '',
], ],
'required' => false, 'required' => false,
])
->add('location', PickUserLocationType::class, [
'label' => 'event.fields.location',
])
->add('comment', CommentType::class, [
'label' => 'Comment',
'required' => false,
])
->add('documents', ChillCollectionType::class, [
'entry_type' => StoredObjectType::class,
'entry_options' => [
'has_title' => true,
],
'allow_add' => true,
'allow_delete' => true,
'delete_empty' => fn (StoredObject $storedObject): bool => '' === $storedObject->getFilename(),
'button_remove_label' => 'event.form.remove_document',
'button_add_label' => 'event.form.add_document',
])
->add('organizationCost', MoneyType::class, [
'label' => 'event.fields.organizationCost',
'help' => 'event.form.organisationCost_help',
]); ]);
} }

View File

@ -114,7 +114,7 @@ final class PickEventType extends AbstractType
} else { } else {
$centers = $this->authorizationHelper->getReachableCenters( $centers = $this->authorizationHelper->getReachableCenters(
$user, $user,
(string) $options['role']->getRole() $options['role']
); );
} }

View File

@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\EventBundle\Menu;
use Chill\EventBundle\Security\Authorization\EventVoter;
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
use Knp\Menu\MenuItem;
use Symfony\Component\Security\Core\Security;
use Symfony\Contracts\Translation\TranslatorInterface;
final readonly class SectionMenuBuilder implements LocalMenuBuilderInterface
{
public function __construct(
private Security $security,
private TranslatorInterface $translator,
) {
}
public function buildMenu($menuId, MenuItem $menu, array $parameters)
{
if ($this->security->isGranted(EventVoter::SEE)) {
$menu->addChild(
$this->translator->trans('Events'),
[
'route' => 'chill_event_event_list',
]
)->setExtras([
'order' => 250,
]);
}
}
public static function getMenuIds(): array
{
return ['section'];
}
}

View File

@ -0,0 +1,142 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\EventBundle\Repository;
use Chill\EventBundle\Entity\Event;
use Chill\EventBundle\Entity\Participation;
use Chill\EventBundle\Security\Authorization\EventVoter;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Security\Authorization\AuthorizationHelperForCurrentUserInterface;
use Chill\PersonBundle\Entity\Person;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\NonUniqueResultException;
use Doctrine\ORM\NoResultException;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Security\Core\Security;
final readonly class EventACLAwareRepository implements EventACLAwareRepositoryInterface
{
public function __construct(
private AuthorizationHelperForCurrentUserInterface $authorizationHelperForCurrentUser,
private EntityManagerInterface $entityManager,
private Security $security,
) {
}
/**
* @throws NonUniqueResultException
* @throws NoResultException
*/
public function countAllViewable(array $filters): int
{
if (!$this->security->getUser() instanceof User) {
return 0;
}
$qb = $this->buildQueryByAllViewable($filters);
$this->addFilters($filters, $qb);
$qb->select('COUNT(event.id)');
return $qb->getQuery()->getSingleScalarResult();
}
public function findAllViewable(array $filters, int $offset = 0, int $limit = 50): array
{
if (!$this->security->getUser() instanceof User) {
return [];
}
$qb = $this->buildQueryByAllViewable($filters)->select('event');
$this->addFilters($filters, $qb);
$qb->setFirstResult($offset)->setMaxResults($limit);
$qb->addOrderBy('event.date', 'DESC');
return $qb->getQuery()->getResult();
}
private function addFilters(array $filters, QueryBuilder $qb): void
{
if (($filters['q'] ?? '') !== '') {
$qb->andWhere('event.name LIKE :content');
$qb->setParameter('content', '%'.$filters['q'].'%');
}
if (array_key_exists('dates', $filters)) {
$dates = $filters['dates'];
if (null !== ($dates['from'] ?? null)) {
$qb->andWhere('event.date >= :date_from');
$qb->setParameter('date_from', $dates['from']);
}
if (null !== ($dates['to'] ?? null)) {
$qb->andWhere('event.date <= :date_to');
$qb->setParameter('date_to', $dates['to']);
}
}
if (0 < count($filters['event_types'] ?? [])) {
$qb->andWhere('event.type IN (:event_types)');
$qb->setParameter('event_types', $filters['event_types']);
}
}
public function buildQueryByAllViewable(array $filters): QueryBuilder
{
$qb = $this->entityManager->createQueryBuilder();
$qb->from(Event::class, 'event');
$aclConditions = $qb->expr()->orX();
$i = 0;
foreach ($this->authorizationHelperForCurrentUser->getReachableCenters(EventVoter::SEE) as $center) {
foreach ($this->authorizationHelperForCurrentUser->getReachableScopes(EventVoter::SEE, $center) as $scopes) {
$aclConditions->add(
$qb->expr()->andX(
'event.circle IN (:scopes_'.$i.')',
$qb->expr()->orX(
'event.center = :center_'.$i,
$qb->expr()->exists(
'SELECT 1 FROM '.Participation::class.' participation_'.$i.' JOIN participation_'.$i.'.event event_'.$i.
' JOIN '.Person\PersonCenterHistory::class.' person_center_history_'.$i.
' WITH IDENTITY(person_center_history_'.$i.'.person) = IDENTITY(participation_'.$i.'.person) '.
' AND event_'.$i.'.date <= person_center_history_'.$i.'.startDate AND (person_center_history_'.$i.'.endDate IS NULL OR person_center_history_'.$i.'.endDate > event_'.$i.'.date) '.
' WHERE participation_'.$i.'.event = event'
)
)
)
);
$qb->setParameter('scopes_'.$i, $scopes);
$qb->setParameter('center_'.$i, $center);
++$i;
}
}
if (0 === $i) {
$aclConditions->add('FALSE = TRUE');
}
$qb
->andWhere(
$qb->expr()->orX(
'event.createdBy = :user',
$aclConditions
)
);
$qb->setParameter('user', $this->security->getUser());
return $qb;
}
}

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\EventBundle\Repository;
use Chill\EventBundle\Entity\Event;
use Chill\EventBundle\Entity\EventType;
interface EventACLAwareRepositoryInterface
{
/**
* @param array{q?: string, dates?: array{from?: \DateTimeImmutable|null, to?: \DateTimeImmutable|null}, event_types?: list<EventType>} $filters
*/
public function countAllViewable(array $filters): int;
/**
* @param array{q?: string, dates?: array{from?: \DateTimeImmutable|null, to?: \DateTimeImmutable|null}, event_types?: list<EventType>} $filters
*
* @return list<Event>
*/
public function findAllViewable(array $filters, int $offset = 0, int $limit = 50): array;
}

View File

@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\EventBundle\Repository;
use Chill\EventBundle\Entity\EventType;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* @extends ServiceEntityRepository<EventType>
*/
final class EventTypeRepository extends ServiceEntityRepository
{
public function __construct(
ManagerRegistry $registry,
private readonly EntityManagerInterface $entityManager,
private readonly TranslatorInterface $translator
) {
parent::__construct($registry, EventType::class);
}
/**
* @return list<EventType>
*/
public function findAllActive(): array
{
$dql = 'SELECT et FROM '.EventType::class.' et WHERE et.active = TRUE ORDER BY JSON_EXTRACT(et.name, :lang)';
return $this->entityManager->createQuery($dql)
->setParameter('lang', $this->translator->getLocale())
->getResult();
}
}

View File

@ -14,15 +14,16 @@
{{ form_row(edit_form.type, { 'label': 'Event type' }) }} {{ form_row(edit_form.type, { 'label': 'Event type' }) }}
{{ form_row(edit_form.moderator) }} {{ form_row(edit_form.moderator) }}
{{ form_row(edit_form.location) }}
{{ form_row(edit_form.organizationCost) }}
<ul class="record_actions"> {{ form_row(edit_form.comment) }}
{{ form_row(edit_form.documents) }}
<ul class="record_actions sticky-form-buttons">
<li class="cancel"> <li class="cancel">
<a href="{{ chill_return_path_or('chill_event_event_list') }}" class="btn btn-cancel">
{% set returnPath = app.request.get('return_path') %} {{ 'List of events'|trans|chill_return_path_label }}
{% set returnLabel = app.request.get('return_label') %}
<a href="{{ returnPath |default( path('chill_event_list_most_recent') ) }}" class="btn btn-cancel">
{{ returnLabel |default('Back to the most recent events'|trans) }}
</a> </a>
</li> </li>
<li> <li>

View File

@ -24,85 +24,89 @@
{% block content %} {% block content %}
<h2>{{ 'Events participation' |trans }}</h2> <h2>{{ 'Events participation' |trans }}</h2>
<table class="table table-striped table-bordered border-dark align-middle mt-3 events"> {% if participations|length == 0 %}
<thead> <p class="chill-no-data-statement">{{ 'Any participation for this person'|trans }}</p>
<tr> {% else %}
<th class="chill-green">{{ 'Date'|trans }}</th> <table class="table table-striped table-bordered border-dark align-middle mt-3 events">
<th class="chill-red">{{ 'Name'|trans }}</th> <thead>
<th class="chill-orange">{{ 'Event type'|trans }}</th> <tr>
<th class="chill-red">{{ 'Role'|trans }}</th> <th class="chill-green">{{ 'Date'|trans }}</th>
<th class="chill-green">{{ 'Status'|trans }}</th> <th class="chill-red">{{ 'Name'|trans }}</th>
<th> </th> <th class="chill-orange">{{ 'Event type'|trans }}</th>
</tr> <th class="chill-red">{{ 'Role'|trans }}</th>
</thead> <th class="chill-green">{{ 'Status'|trans }}</th>
<tbody> <th> </th>
{% for participation in participations %} </tr>
<tr> </thead>
<td>{{ participation.event.date|format_date('short') }}</td> <tbody>
<td>{{ participation.event.name }}</td> {% for participation in participations %}
<td>{{ participation.event.type.name|localize_translatable_string }}</td> <tr>
<td>{{ participation.role.name|localize_translatable_string }}</td> <td>{{ participation.event.date|format_date('short') }}</td>
<td>{{ participation.status.name|localize_translatable_string }}</td> <td>{{ participation.event.name }}</td>
<td> <td>{{ participation.event.type.name|localize_translatable_string }}</td>
<div class="btn-group" role="group" aria-label="Button group actions"> <td>{{ participation.role.name|localize_translatable_string }}</td>
<td>{{ participation.status.name|localize_translatable_string }}</td>
<td>
<div class="btn-group" role="group" aria-label="Button group actions">
{% set currentPath = path(app.request.attributes.get('_route'), app.request.attributes.get('_route_params')) %} {% set currentPath = path(app.request.attributes.get('_route'), app.request.attributes.get('_route_params')) %}
{% set returnLabel = 'Back to %person% events'|trans({ '%person%' : currentPerson } ) %} {% set returnLabel = 'Back to %person% events'|trans({ '%person%' : currentPerson } ) %}
{% if is_granted('CHILL_EVENT_SEE_DETAILS', participation.event) %} {% if is_granted('CHILL_EVENT_SEE_DETAILS', participation.event) %}
<a href="{{ path('chill_event__event_show', { 'event_id' : participation.event.id, 'return_path' : currentPath, 'return_label' : returnLabel } ) }}" <a href="{{ path('chill_event__event_show', { 'event_id' : participation.event.id, 'return_path' : currentPath, 'return_label' : returnLabel } ) }}"
class="btn btn-primary btn-sm" title="{{ 'See details of the event'|trans }}"> class="btn btn-primary btn-sm" title="{{ 'See details of the event'|trans }}">
<i class="fa fa-fw fa-eye"></i> <i class="fa fa-fw fa-eye"></i>
</a> </a>
{% endif %} {% endif %}
{% if is_granted('CHILL_EVENT_UPDATE', participation.event) {% if is_granted('CHILL_EVENT_UPDATE', participation.event)
and is_granted('CHILL_EVENT_PARTICIPATION_UPDATE', participation) %} and is_granted('CHILL_EVENT_PARTICIPATION_UPDATE', participation) %}
<div class="btn-group" role="group"> <div class="btn-group" role="group">
<button class="btn btn-sm btn-warning dropdown-toggle" type="button" id="dropdownEdit" data-bs-toggle="dropdown" aria-expanded="false"> <button class="btn btn-sm btn-warning dropdown-toggle" type="button" id="dropdownEdit" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fa fa-pencil"></i> <i class="fa fa-pencil"></i>
</button> </button>
<ul class="dropdown-menu" aria-labelledby="dropdownEdit"> <ul class="dropdown-menu" aria-labelledby="dropdownEdit">
<li> <li>
<a href="{{ path('chill_event__event_edit', { 'event_id' : participation.event.id, 'return_path' : currentPath, 'return_label' : returnLabel }) }}"
class="dropdown-item">
{{ 'Edit the event'|trans }}
</a>
</li>
<li>
<a href="{{ path('chill_event_participation_edit', { 'participation_id' : participation.id, 'return_path' : currentPath, 'return_label' : returnLabel }) }}"
class="dropdown-item">
{{ 'Edit the participation'|trans }}
</a>
</li>
</ul>
</div>
{% else %}
{% if is_granted('CHILL_EVENT_UPDATE', participation.event) %}
<a href="{{ path('chill_event__event_edit', { 'event_id' : participation.event.id, 'return_path' : currentPath, 'return_label' : returnLabel }) }}" <a href="{{ path('chill_event__event_edit', { 'event_id' : participation.event.id, 'return_path' : currentPath, 'return_label' : returnLabel }) }}"
class="dropdown-item"> class="btn btn-warning btn-sm">
{{ 'Edit the event'|trans }} {{ 'Edit the event'|trans }}
</a> </a>
</li> {% endif %}
<li> {% if is_granted('CHILL_EVENT_PARTICIPATION_UPDATE', participation) %}
<a href="{{ path('chill_event_participation_edit', { 'participation_id' : participation.id, 'return_path' : currentPath, 'return_label' : returnLabel }) }}" <a href="{{ path('chill_event_participation_edit', { 'participation_id' : participation.id, 'return_path' : currentPath, 'return_label' : returnLabel }) }}"
class="dropdown-item"> class="btn btn-warning btn-sm">
{{ 'Edit the participation'|trans }} {{ 'Edit the participation'|trans }}
</a> </a>
</li> {% endif %}
</ul>
</div>
{% else %}
{% if is_granted('CHILL_EVENT_UPDATE', participation.event) %} {% endif %}
<a href="{{ path('chill_event__event_edit', { 'event_id' : participation.event.id, 'return_path' : currentPath, 'return_label' : returnLabel }) }}"
class="btn btn-warning btn-sm">
{{ 'Edit the event'|trans }}
</a>
{% endif %}
{% if is_granted('CHILL_EVENT_PARTICIPATION_UPDATE', participation) %}
<a href="{{ path('chill_event_participation_edit', { 'participation_id' : participation.id, 'return_path' : currentPath, 'return_label' : returnLabel }) }}"
class="btn btn-warning btn-sm">
{{ 'Edit the participation'|trans }}
</a>
{% endif %}
{% endif %}
</div> </div>
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
{% endif %}
{% if participations|length < paginator.getTotalItems %} {% if participations|length < paginator.getTotalItems %}
{{ chill_pagination(paginator) }} {{ chill_pagination(paginator) }}

View File

@ -1,5 +1,13 @@
{% extends '@ChillEvent/layout.html.twig' %} {% extends '@ChillEvent/layout.html.twig' %}
{% block js %}
{{ encore_entry_script_tags('mod_async_upload') }}
{% endblock %}
{% block css %}
{{ encore_entry_link_tags('mod_async_upload') }}
{% endblock %}
{% block title 'Event creation'|trans %} {% block title 'Event creation'|trans %}
{% block event_content -%} {% block event_content -%}
@ -14,8 +22,13 @@
{{ form_row(form.type, { 'label': 'Event type' }) }} {{ form_row(form.type, { 'label': 'Event type' }) }}
{{ form_row(form.moderator) }} {{ form_row(form.moderator) }}
{{ form_row(form.location) }}
{{ form_row(form.organizationCost) }}
<ul class="record_actions"> {{ form_row(form.comment) }}
{{ form_row(form.documents) }}
<ul class="record_actions sticky-form-buttons">
<li class="cancel"> <li class="cancel">
<a href="{{ path('chill_event_list_most_recent') }}" class="btn btn-cancel"> <a href="{{ path('chill_event_list_most_recent') }}" class="btn btn-cancel">
{{ 'Back to the most recent events'|trans }} {{ 'Back to the most recent events'|trans }}
@ -25,7 +38,7 @@
{{ form_widget(form.submit, { 'attr' : { 'class' : 'btn btn-create' } }) }} {{ form_widget(form.submit, { 'attr' : { 'class' : 'btn btn-create' } }) }}
</li> </li>
</ul> </ul>
{{ form_end(form) }} {{ form_end(form) }}
</div> </div>
{% endblock %} {% endblock %}

View File

@ -0,0 +1,92 @@
{% extends '@ChillEvent/layout.html.twig' %}
{% block title 'Events'|trans %}
{% block js %}
{{ parent() }}
{{ encore_entry_script_tags('mod_pickentity_type') }}
{% endblock %}
{% block css %}
{{ parent() }}
{{ encore_entry_link_tags('mod_pickentity_type') }}
{% endblock %}
{% block content %}
<h1>{{ block('title') }}</h1>
{{ filter|chill_render_filter_order_helper }}
{# {% if is_granted('CHILL_EVENT_CREATE') %} #}
<ul class="record_actions">
<li><a class="btn btn-create" href="{{ chill_path_add_return_path('chill_event__event_new_pickcenter') }}">{{ 'Add an event'|trans }}</a></li>
</ul>
{# {% endif %} #}
{% if events|length > 0 %}
<div class="flex-table">
{% for e in events %}
<div class="item-bloc">
<div class="item-row">
<div class="item-col">
<div class="denomination h2">
{{ e.name }}
</div>
<p>{{ e.type.name|localize_translatable_string }}</p>
{% if e.moderator is not null %}
<p>{{ 'Moderator'|trans }}: {{ e.moderator|chill_entity_render_box }}</p>
{% endif %}
</div>
<div class="item-col">
<div class="container" style="text-align: right;">
<p>{{ e.date|format_datetime('medium', 'medium') }}</p>
<p>{{ 'count participations to this event'|trans({'count': e.participations|length}) }}</p>
</div>
</div>
</div>
{% if e.participations|length > 0 %}
<div class="item-row separator">
<strong>{{ 'Participations'|trans }}&nbsp;: </strong>
{% for part in e.participations|slice(0, 20) %}
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
targetEntity: { name: 'person', id: part.person.id },
action: 'show',
displayBadge: true,
buttonText: part.person|chill_entity_render_string,
isDead: part.person.deathdate is not null
} %}
{% endfor %}
{% if e.participations|length > 20 %}
{{ 'events.and_other_count_participants'|trans({'count': e.participations|length - 20}) }}
{% endif %}
</div>
{% endif %}
<div class="item-row">
<div class="item-col">
{{ form_start(eventForms[e.id]) }}
{{ form_widget(eventForms[e.id].person_id) }}
{{ form_end(eventForms[e.id]) }}
</div>
</div>
<div class="item-row separator">
<div class="item-col item-meta">
</div>
<div class="item-col">
<ul class="record_actions">
{% if is_granted('CHILL_EVENT_UPDATE', e) %}
<li><a href="{{ chill_path_add_return_path('chill_event__event_delete', {'event_id': e.id}) }}" class="btn btn-delete"></a></li>
{% endif %}
{% if is_granted('CHILL_EVENT_UPDATE', e) %}
<li><a href="{{ chill_path_add_return_path('chill_event__event_edit', {'event_id': e.id}) }}" class="btn btn-edit"></a></li>
{% endif %}
<li><a href="{{ chill_path_add_return_path('chill_event__event_show', {'event_id': e.id}) }}" class="btn btn-show"></a></li>
</ul>
</div>
</div>
</div>
{% endfor %}
</div>
{% endif %}
{{ chill_pagination(pagination) }}
{% endblock %}

View File

@ -4,12 +4,28 @@
{% import '@ChillPerson/Person/macro.html.twig' as person_macro %} {% import '@ChillPerson/Person/macro.html.twig' as person_macro %}
{% block js %}
{{ parent() }}
{{ encore_entry_script_tags('mod_pickentity_type') }}
{{ encore_entry_script_tags('mod_document_action_buttons_group') }}
{% endblock %}
{% block css %}
{{ parent() }}
{{ encore_entry_link_tags('mod_pickentity_type') }}
{{ encore_entry_link_tags('mod_document_action_buttons_group') }}
{% endblock %}
{% block event_content -%} {% block event_content -%}
<div class="col-10"> <div class="col-10">
<h1>{{ 'Details of an event'|trans }}</h1> <h1>{{ 'Details of an event'|trans }}</h1>
<table class="table table-bordered border-dark align-middle"> <table class="table table-bordered border-dark align-middle">
<tbody> <tbody>
<tr>
<th>{{ 'Circle'|trans }}</th>
<td>{{ event.circle.name|localize_translatable_string }}</td>
</tr>
<tr> <tr>
<th>{{ 'Name'|trans }}</th> <th>{{ 'Name'|trans }}</th>
<td>{{ event.name }}</td> <td>{{ event.name }}</td>
@ -22,42 +38,62 @@
<th>{{ 'Event type'|trans }}</th> <th>{{ 'Event type'|trans }}</th>
<td>{{ event.type.name|localize_translatable_string }}</td> <td>{{ event.type.name|localize_translatable_string }}</td>
</tr> </tr>
<tr>
<th>{{ 'Circle'|trans }}</th>
<td>{{ event.circle.name|localize_translatable_string }}</td>
</tr>
<tr> <tr>
<th>{{ 'Moderator'|trans }}</th> <th>{{ 'Moderator'|trans }}</th>
<td>{{ event.moderator|trans|default('-') }}</td> <td>{{ event.moderator|trans|default('-') }}</td>
</tr> </tr>
<tr>
<th>{{ 'event.fields.organizationCost'|trans }}</th>
<td>{{ event.organizationCost|format_currency('EUR') }}</td>
</tr>
<tr>
<th>{{ 'event.fields.location'|trans }}</th>
<td>
{% if event.location is not null %}
{{ event.location.name }}
{% if event.location.address is not same as(null) %}{{ event.location.address|chill_entity_render_box({'multiline': false, 'with_picto': (event.location.name is empty), 'details_button': true}) }}{% endif %}
{% else %}
<span class="chill-no-data-statement">{{ 'Any location for this event'|trans }}</span>
{% endif %}
</td>
</tr>
</tbody> </tbody>
</table> </table>
{% if event.documents|length > 0 %}
<div>
<p><strong>{{ 'event.fields.documents'|trans }}</strong></p>
<ul>
{% for d in event.documents %}
<li class="document-list-item">{{ d.title|chill_print_or_message('document.Any title') }} {{ d|chill_document_button_group(d.title, is_granted('CHILL_EVENT_SEE_DETAILS', event), {small: false}) }}</li>
{% endfor %}
</ul>
</div>
{% endif %}
{% if not event.comment.empty %}
<div>
{{ event.comment|chill_entity_render_box({
'disable_markdown': false,
'metadata': true,
}) }}
</div>
{% endif %}
<ul class="record_actions"> <ul class="record_actions">
{% set returnPath = app.request.get('return_path') %} {% set returnPath = app.request.get('return_path') %}
{% set returnLabel = app.request.get('return_label') %} {% set returnLabel = app.request.get('return_label') %}
{% if returnPath and returnLabel %} <li class="cancel">
<li class="cancel"> <a href="{{ chill_return_path_or('chill_event_event_list') }}" class="btn btn-cancel">{{ 'Back to the list'|trans|chill_return_path_label }}</a>
<a href="{{ returnPath }}" class="btn btn-cancel">{{ returnLabel }}</a> </li>
</li> <li>
<li> <a href="{{ chill_path_add_return_path('chill_event__event_edit', {'event_id': event.id }, false, 'See'|trans) }}" class="btn btn-edit">
<a href="{{ path('chill_event__event_edit', { {{ 'Edit'|trans }}
'event_id': event.id, </a>
'return_path': app.request.getRequestUri, </li>
'return_label': 'Back to details of the event'|trans
}) }}" class="btn btn-edit">{{ 'Edit'|trans }}
</a>
</li>
{% else %}
<li>
<a href="{{ path('chill_event__event_edit', {'event_id': event.id }) }}" class="btn btn-edit">
{{ 'Edit'|trans }}
</a>
</li>
{% endif %}
<li> <li>
<a href="{{ path('chill_event__event_delete', {'event_id' : event.id } ) }}" <a href="{{ path('chill_event__event_delete', {'event_id' : event.id } ) }}"
class="btn btn-delete">{{ 'Delete event'|trans }}</a> class="btn btn-delete">{{ 'Delete event'|trans }}</a>
@ -83,7 +119,15 @@
<tbody> <tbody>
{% for participation in event.participations %} {% for participation in event.participations %}
<tr> <tr>
<td>{{ person_macro.render(participation.person) }}</td> <td>
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
targetEntity: { name: 'person', id: participation.person.id },
action: 'show',
displayBadge: true,
buttonText: participation.person|chill_entity_render_string,
isDead: participation.person.deathdate is not null
} %}
</td>
<td>{{ participation.role.name|localize_translatable_string }}</td> <td>{{ participation.role.name|localize_translatable_string }}</td>
<td>{{ participation.status.name|localize_translatable_string }}</td> <td>{{ participation.status.name|localize_translatable_string }}</td>
<td>{{ participation.lastUpdate|ago }} {# sf4 check: filter 'time_diff' is abandoned, <td>{{ participation.lastUpdate|ago }} {# sf4 check: filter 'time_diff' is abandoned,
@ -94,7 +138,7 @@
<ul class="record_actions"> <ul class="record_actions">
{% if is_granted('CHILL_EVENT_PARTICIPATION_UPDATE', participation) %} {% if is_granted('CHILL_EVENT_PARTICIPATION_UPDATE', participation) %}
<li> <li>
<a href="{{ path('chill_event_participation_edit', { 'participation_id' : participation.id } ) }}" <a href="{{ chill_path_add_return_path('chill_event_participation_edit', { 'participation_id' : participation.id }, false, 'See'|trans ) }}"
class="btn btn-edit" title="{{ 'Edit'|trans }}"></a> class="btn btn-edit" title="{{ 'Edit'|trans }}"></a>
</li> </li>
<li> <li>
@ -126,11 +170,8 @@
'class' : 'custom-select', 'class' : 'custom-select',
'style': 'min-width: 15em; max-width: 18em; display: inline-block;' 'style': 'min-width: 15em; max-width: 18em; display: inline-block;'
}} ) }} }} ) }}
<div class="input-group-append">
{{ form_widget(form_add_participation_by_person.submit, { 'attr' : { 'class' : 'btn btn-create' } } ) }}
</div>
</div> </div>
{{ form_rest(form_add_participation_by_person) }} <input type="hidden" name="returnPath" value="{{ app.request.requestUri }}" />
{{ form_end(form_add_participation_by_person) }} {{ form_end(form_add_participation_by_person) }}
</div> </div>

View File

@ -32,7 +32,7 @@
<ul class="record_actions"> <ul class="record_actions sticky-form-buttons">
<li class="cancel"> <li class="cancel">
<a href="{{ path('chill_event__event_show', { 'event_id' : participation.event.id } ) }}" class="btn btn-cancel"> <a href="{{ path('chill_event__event_show', { 'event_id' : participation.event.id } ) }}" class="btn btn-cancel">
{{ 'Back to the event'|trans }} {{ 'Back to the event'|trans }}

View File

@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\EventBundle\Tests\Controller;
use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Test\PrepareClientTrait;
use Prophecy\PhpUnit\ProphecyTrait;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Twig\Environment;
/**
* @internal
*
* @coversNothing
*/
class EventListControllerTest extends WebTestCase
{
use ProphecyTrait;
use PrepareClientTrait;
private readonly PaginatorFactory $paginatorFactory;
private readonly Environment $environment;
protected function setUp(): void
{
}
public function testList(): void
{
$client = $this->getClientAuthenticated();
$client->request('GET', '/fr/event/event/list');
self::assertResponseIsSuccessful();
}
}

View File

@ -11,6 +11,11 @@ declare(strict_types=1);
namespace Chill\EventBundle\Tests\Controller; namespace Chill\EventBundle\Tests\Controller;
use Chill\EventBundle\Entity\Event;
use Chill\EventBundle\Repository\EventRepository;
use Chill\MainBundle\Test\PrepareClientTrait;
use Chill\PersonBundle\DataFixtures\Helper\PersonRandomHelper;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use function count; use function count;
@ -23,15 +28,12 @@ use function count;
*/ */
final class ParticipationControllerTest extends WebTestCase final class ParticipationControllerTest extends WebTestCase
{ {
/** use PersonRandomHelper;
* @var \Symfony\Component\BrowserKit\AbstractBrowser use PrepareClientTrait;
*/
protected $client;
/** private EntityManagerInterface $em;
* @var \Doctrine\ORM\EntityManagerInterface
*/ private EventRepository $eventRepository;
protected $em;
/** /**
* Keep a cache for each person id given by the function getRandomPerson. * Keep a cache for each person id given by the function getRandomPerson.
@ -44,23 +46,21 @@ final class ParticipationControllerTest extends WebTestCase
*/ */
private array $personsIdsCache = []; private array $personsIdsCache = [];
protected function setUp(): void protected function prepareDI(): void
{ {
self::bootKernel(); $this->em = self::$container->get(EntityManagerInterface::class);
$this->eventRepository = self::$container->get(EventRepository::class);
$this->client = self::createClient([], [
'PHP_AUTH_USER' => 'center a_social',
'PHP_AUTH_PW' => 'password',
'HTTP_ACCEPT_LANGUAGE' => 'fr_FR',
]);
$container = self::$kernel->getContainer();
$this->em = $container->get('doctrine.orm.entity_manager');
$this->personsIdsCache = []; $this->personsIdsCache = [];
} }
protected function tearDown(): void
{
parent::tearDown();
self::ensureKernelShutdown();
}
/** /**
* This method test participation creation with wrong parameters. * This method test participation creation with wrong parameters.
* *
@ -68,11 +68,13 @@ final class ParticipationControllerTest extends WebTestCase
*/ */
public function testCreateActionWrongParameters() public function testCreateActionWrongParameters()
{ {
$client = $this->getClientAuthenticated();
$this->prepareDI();
$event = $this->getRandomEvent(); $event = $this->getRandomEvent();
$person = $this->getRandomPerson(); $person = $this->getRandomPerson($this->em);
// missing person_id or persons_ids // missing person_id or persons_ids
$this->client->request( $client->request(
'GET', 'GET',
'/fr/event/participation/create', '/fr/event/participation/create',
[ [
@ -81,33 +83,33 @@ final class ParticipationControllerTest extends WebTestCase
); );
$this->assertEquals( $this->assertEquals(
400, 400,
$this->client->getResponse()->getStatusCode(), $client->getResponse()->getStatusCode(),
'Test that /fr/event/participation/create fail if ' 'Test that /fr/event/participation/create fail if '
.'both person_id and persons_ids are missing' .'both person_id and persons_ids are missing'
); );
// having both person_id and persons_ids // having both person_id and persons_ids
$this->client->request( $client->request(
'GET', 'GET',
'/fr/event/participation/create', '/fr/event/participation/create',
[ [
'event_id' => $event->getId(), 'event_id' => $event->getId(),
'persons_ids' => implode(',', [ 'persons_ids' => implode(',', [
$this->getRandomPerson()->getId(), $this->getRandomPerson($this->em)->getId(),
$this->getRandomPerson()->getId(), $this->getRandomPerson($this->em)->getId(),
]), ]),
'person_id' => $person->getId(), 'person_id' => $person->getId(),
] ]
); );
$this->assertEquals( $this->assertEquals(
400, 400,
$this->client->getResponse()->getStatusCode(), $client->getResponse()->getStatusCode(),
'test that /fr/event/participation/create fail if both person_id and ' 'test that /fr/event/participation/create fail if both person_id and '
.'persons_ids are set' .'persons_ids are set'
); );
// missing event_id // missing event_id
$this->client->request( $client->request(
'GET', 'GET',
'/fr/event/participation/create', '/fr/event/participation/create',
[ [
@ -116,12 +118,12 @@ final class ParticipationControllerTest extends WebTestCase
); );
$this->assertEquals( $this->assertEquals(
400, 400,
$this->client->getResponse()->getStatusCode(), $client->getResponse()->getStatusCode(),
'Test that /fr/event/participation/create fails if event_id is missing' 'Test that /fr/event/participation/create fails if event_id is missing'
); );
// persons_ids with wrong content // persons_ids with wrong content
$this->client->request( $client->request(
'GET', 'GET',
'/fr/event/participation/create', '/fr/event/participation/create',
[ [
@ -131,42 +133,47 @@ final class ParticipationControllerTest extends WebTestCase
); );
$this->assertEquals( $this->assertEquals(
400, 400,
$this->client->getResponse()->getStatusCode(), $client->getResponse()->getStatusCode(),
'Test that /fr/event/participation/create fails if persons_ids has wrong content' 'Test that /fr/event/participation/create fails if persons_ids has wrong content'
); );
} }
public function testEditMultipleAction() public function testEditMultipleAction()
{ {
/** @var \Chill\EventBundle\Entity\Event $event */ $client = $this->getClientAuthenticated();
$this->prepareDI();
/** @var Event $event */
$event = $this->getRandomEventWithMultipleParticipations(); $event = $this->getRandomEventWithMultipleParticipations();
$crawler = $this->client->request('GET', '/fr/event/participation/'.$event->getId(). $crawler = $client->request('GET', '/fr/event/participation/'.$event->getId().
'/edit_multiple'); '/edit_multiple');
$this->assertEquals(200, $this->client->getResponse()->getStatusCode()); $this->assertEquals(200, $client->getResponse()->getStatusCode());
$button = $crawler->selectButton('Mettre à jour'); $button = $crawler->selectButton('Mettre à jour');
$this->assertEquals(1, $button->count(), "test the form with button 'mettre à jour' exists "); $this->assertEquals(1, $button->count(), "test the form with button 'mettre à jour' exists ");
$this->client->submit($button->form(), [ $client->submit($button->form(), [
'form[participations][0][role]' => $event->getType()->getRoles()->first()->getId(), 'form[participations][0][role]' => $event->getType()->getRoles()->first()->getId(),
'form[participations][0][status]' => $event->getType()->getStatuses()->first()->getId(), 'form[participations][0][status]' => $event->getType()->getStatuses()->first()->getId(),
'form[participations][1][role]' => $event->getType()->getRoles()->last()->getId(), 'form[participations][1][role]' => $event->getType()->getRoles()->last()->getId(),
'form[participations][1][status]' => $event->getType()->getStatuses()->last()->getId(), 'form[participations][1][status]' => $event->getType()->getStatuses()->last()->getId(),
]); ]);
$this->assertTrue($this->client->getResponse() $this->assertTrue($client->getResponse()
->isRedirect('/fr/event/event/'.$event->getId().'/show')); ->isRedirect('/fr/event/event/'.$event->getId().'/show'));
} }
public function testNewActionWrongParameters() public function testNewActionWrongParameters()
{ {
$client = $this->getClientAuthenticated();
$this->prepareDI();
$event = $this->getRandomEvent(); $event = $this->getRandomEvent();
$person = $this->getRandomPerson(); $person = $this->getRandomPerson($this->em);
// missing person_id or persons_ids // missing person_id or persons_ids
$this->client->request( $client->request(
'GET', 'GET',
'/fr/event/participation/new', '/fr/event/participation/new',
[ [
@ -175,33 +182,33 @@ final class ParticipationControllerTest extends WebTestCase
); );
$this->assertEquals( $this->assertEquals(
400, 400,
$this->client->getResponse()->getStatusCode(), $client->getResponse()->getStatusCode(),
'Test that /fr/event/participation/new fail if ' 'Test that /fr/event/participation/new fail if '
.'both person_id and persons_ids are missing' .'both person_id and persons_ids are missing'
); );
// having both person_id and persons_ids // having both person_id and persons_ids
$this->client->request( $client->request(
'GET', 'GET',
'/fr/event/participation/new', '/fr/event/participation/new',
[ [
'event_id' => $event->getId(), 'event_id' => $event->getId(),
'persons_ids' => implode(',', [ 'persons_ids' => implode(',', [
$this->getRandomPerson()->getId(), $this->getRandomPerson($this->em)->getId(),
$this->getRandomPerson()->getId(), $this->getRandomPerson($this->em)->getId(),
]), ]),
'person_id' => $person->getId(), 'person_id' => $person->getId(),
] ]
); );
$this->assertEquals( $this->assertEquals(
400, 400,
$this->client->getResponse()->getStatusCode(), $client->getResponse()->getStatusCode(),
'test that /fr/event/participation/new fail if both person_id and ' 'test that /fr/event/participation/new fail if both person_id and '
.'persons_ids are set' .'persons_ids are set'
); );
// missing event_id // missing event_id
$this->client->request( $client->request(
'GET', 'GET',
'/fr/event/participation/new', '/fr/event/participation/new',
[ [
@ -210,12 +217,12 @@ final class ParticipationControllerTest extends WebTestCase
); );
$this->assertEquals( $this->assertEquals(
400, 400,
$this->client->getResponse()->getStatusCode(), $client->getResponse()->getStatusCode(),
'Test that /fr/event/participation/new fails if event_id is missing' 'Test that /fr/event/participation/new fails if event_id is missing'
); );
// persons_ids with wrong content // persons_ids with wrong content
$this->client->request( $client->request(
'GET', 'GET',
'/fr/event/participation/new', '/fr/event/participation/new',
[ [
@ -225,13 +232,15 @@ final class ParticipationControllerTest extends WebTestCase
); );
$this->assertEquals( $this->assertEquals(
400, 400,
$this->client->getResponse()->getStatusCode(), $client->getResponse()->getStatusCode(),
'Test that /fr/event/participation/new fails if persons_ids has wrong content' 'Test that /fr/event/participation/new fails if persons_ids has wrong content'
); );
} }
public function testNewMultipleAction() public function testNewMultipleAction()
{ {
$client = $this->getClientAuthenticated();
$this->prepareDI();
$event = $this->getRandomEvent(); $event = $this->getRandomEvent();
// record the number of participation for the event (used later in this test) // record the number of participation for the event (used later in this test)
$nbParticipations = $event->getParticipations()->count(); $nbParticipations = $event->getParticipations()->count();
@ -244,10 +253,10 @@ final class ParticipationControllerTest extends WebTestCase
->toArray() ->toArray()
); );
// get some random people // get some random people
$person1 = $this->getRandomPerson(); $person1 = $this->getRandomPerson($this->em);
$person2 = $this->getRandomPerson(); $person2 = $this->getRandomPerson($this->em);
$crawler = $this->client->request( $crawler = $client->request(
'GET', 'GET',
'/fr/event/participation/new', '/fr/event/participation/new',
[ [
@ -258,7 +267,7 @@ final class ParticipationControllerTest extends WebTestCase
$this->assertEquals( $this->assertEquals(
200, 200,
$this->client->getResponse()->getStatusCode(), $client->getResponse()->getStatusCode(),
'test that /fr/event/participation/new is successful' 'test that /fr/event/participation/new is successful'
); );
@ -266,7 +275,7 @@ final class ParticipationControllerTest extends WebTestCase
$this->assertNotNull($button, "test the form with button 'Créer' exists"); $this->assertNotNull($button, "test the form with button 'Créer' exists");
$this->client->submit($button->form(), [ $client->submit($button->form(), [
'form' => [ 'form' => [
'participations' => [ 'participations' => [
0 => [ 0 => [
@ -281,8 +290,8 @@ final class ParticipationControllerTest extends WebTestCase
], ],
]); ]);
$this->assertTrue($this->client->getResponse()->isRedirect()); $this->assertTrue($client->getResponse()->isRedirect());
$crawler = $this->client->followRedirect(); $crawler = $client->followRedirect();
$span1 = $crawler->filter('table td span.entity-person a:contains("' $span1 = $crawler->filter('table td span.entity-person a:contains("'
.$person1->getFirstName().'"):contains("'.$person1->getLastname().'")'); .$person1->getFirstName().'"):contains("'.$person1->getLastname().'")');
@ -292,7 +301,7 @@ final class ParticipationControllerTest extends WebTestCase
$this->assertGreaterThan(0, \count($span2)); $this->assertGreaterThan(0, \count($span2));
// as the container has reloaded, reload the event // as the container has reloaded, reload the event
$event = $this->em->getRepository(\Chill\EventBundle\Entity\Event::class)->find($event->getId()); $event = $this->em->getRepository(Event::class)->find($event->getId());
$this->em->refresh($event); $this->em->refresh($event);
$this->assertEquals($nbParticipations + 2, $event->getParticipations()->count()); $this->assertEquals($nbParticipations + 2, $event->getParticipations()->count());
@ -300,13 +309,15 @@ final class ParticipationControllerTest extends WebTestCase
public function testNewMultipleWithAllPeopleParticipating() public function testNewMultipleWithAllPeopleParticipating()
{ {
$client = $this->getClientAuthenticated();
$this->prepareDI();
$event = $this->getRandomEventWithMultipleParticipations(); $event = $this->getRandomEventWithMultipleParticipations();
$persons_id = implode(',', $event->getParticipations()->map( $persons_id = implode(',', $event->getParticipations()->map(
static fn ($p) => $p->getPerson()->getId() static fn ($p) => $p->getPerson()->getId()
)->toArray()); )->toArray());
$crawler = $this->client->request( $crawler = $client->request(
'GET', 'GET',
'/fr/event/participation/new', '/fr/event/participation/new',
[ [
@ -317,13 +328,15 @@ final class ParticipationControllerTest extends WebTestCase
$this->assertEquals( $this->assertEquals(
302, 302,
$this->client->getResponse()->getStatusCode(), $client->getResponse()->getStatusCode(),
'test that /fr/event/participation/new is redirecting' 'test that /fr/event/participation/new is redirecting'
); );
} }
public function testNewMultipleWithSomePeopleParticipating() public function testNewMultipleWithSomePeopleParticipating()
{ {
$client = $this->getClientAuthenticated();
$this->prepareDI();
$event = $this->getRandomEventWithMultipleParticipations(); $event = $this->getRandomEventWithMultipleParticipations();
// record the number of participation for the event (used later in this test) // record the number of participation for the event (used later in this test)
$nbParticipations = $event->getParticipations()->count(); $nbParticipations = $event->getParticipations()->count();
@ -335,12 +348,12 @@ final class ParticipationControllerTest extends WebTestCase
$this->personsIdsCache = array_merge($this->personsIdsCache, $persons_id); $this->personsIdsCache = array_merge($this->personsIdsCache, $persons_id);
// get a random person // get a random person
$newPerson = $this->getRandomPerson(); $newPerson = $this->getRandomPerson($this->em);
// build the `persons_ids` parameter // build the `persons_ids` parameter
$persons_ids_string = implode(',', [...$persons_id, $newPerson->getId()]); $persons_ids_string = implode(',', [...$persons_id, $newPerson->getId()]);
$crawler = $this->client->request( $crawler = $client->request(
'GET', 'GET',
'/fr/event/participation/new', '/fr/event/participation/new',
[ [
@ -351,7 +364,7 @@ final class ParticipationControllerTest extends WebTestCase
$this->assertEquals( $this->assertEquals(
200, 200,
$this->client->getResponse()->getStatusCode(), $client->getResponse()->getStatusCode(),
'test that /fr/event/participation/new is successful' 'test that /fr/event/participation/new is successful'
); );
@ -377,15 +390,15 @@ final class ParticipationControllerTest extends WebTestCase
$this->assertNotNull($button, "test the form with button 'Créer' exists"); $this->assertNotNull($button, "test the form with button 'Créer' exists");
// submit the form // submit the form
$this->client->submit($button->form(), [ $client->submit($button->form(), [
'participation[role]' => $event->getType()->getRoles()->first()->getId(), 'participation[role]' => $event->getType()->getRoles()->first()->getId(),
'participation[status]' => $event->getType()->getStatuses()->first()->getId(), 'participation[status]' => $event->getType()->getStatuses()->first()->getId(),
]); ]);
$this->assertTrue($this->client->getResponse()->isRedirect()); $this->assertTrue($client->getResponse()->isRedirect());
// reload the event and test there is a new participation // reload the event and test there is a new participation
$event = $this->em->getRepository(\Chill\EventBundle\Entity\Event::class) $event = $this->em->getRepository(Event::class)
->find($event->getId()); ->find($event->getId());
$this->em->refresh($event); $this->em->refresh($event);
@ -398,12 +411,14 @@ final class ParticipationControllerTest extends WebTestCase
public function testNewSingleAction() public function testNewSingleAction()
{ {
$client = $this->getClientAuthenticated();
$this->prepareDI();
$event = $this->getRandomEvent(); $event = $this->getRandomEvent();
// record the number of participation for the event // record the number of participation for the event
$nbParticipations = $event->getParticipations()->count(); $nbParticipations = $event->getParticipations()->count();
$person = $this->getRandomPerson(); $person = $this->getRandomPerson($this->em);
$crawler = $this->client->request( $crawler = $client->request(
'GET', 'GET',
'/fr/event/participation/new', '/fr/event/participation/new',
[ [
@ -414,7 +429,7 @@ final class ParticipationControllerTest extends WebTestCase
$this->assertEquals( $this->assertEquals(
200, 200,
$this->client->getResponse()->getStatusCode(), $client->getResponse()->getStatusCode(),
'test that /fr/event/participation/new is successful' 'test that /fr/event/participation/new is successful'
); );
@ -422,13 +437,13 @@ final class ParticipationControllerTest extends WebTestCase
$this->assertNotNull($button, "test the form with button 'Créer' exists"); $this->assertNotNull($button, "test the form with button 'Créer' exists");
$this->client->submit($button->form(), [ $client->submit($button->form(), [
'participation[role]' => $event->getType()->getRoles()->first()->getId(), 'participation[role]' => $event->getType()->getRoles()->first()->getId(),
'participation[status]' => $event->getType()->getStatuses()->first()->getId(), 'participation[status]' => $event->getType()->getStatuses()->first()->getId(),
]); ]);
$this->assertTrue($this->client->getResponse()->isRedirect()); $this->assertTrue($client->getResponse()->isRedirect());
$crawler = $this->client->followRedirect(); $crawler = $client->followRedirect();
$span = $crawler->filter('table td span.entity-person a:contains("' $span = $crawler->filter('table td span.entity-person a:contains("'
.$person->getFirstName().'"):contains("'.$person->getLastname().'")'); .$person->getFirstName().'"):contains("'.$person->getLastname().'")');
@ -436,29 +451,23 @@ final class ParticipationControllerTest extends WebTestCase
$this->assertGreaterThan(0, \count($span)); $this->assertGreaterThan(0, \count($span));
// as the container has reloaded, reload the event // as the container has reloaded, reload the event
$event = $this->em->getRepository(\Chill\EventBundle\Entity\Event::class)->find($event->getId()); $event = $this->em->getRepository(Event::class)->find($event->getId());
$this->em->refresh($event); $this->em->refresh($event);
$this->assertEquals($nbParticipations + 1, $event->getParticipations()->count()); $this->assertEquals($nbParticipations + 1, $event->getParticipations()->count());
} }
/** private function getRandomEvent(string $centerName = 'Center A', string $circleName = 'social'): Event
* @return \Chill\EventBundle\Entity\Event
*/
protected function getRandomEvent(mixed $centerName = 'Center A', mixed $circleName = 'social')
{ {
$center = $this->em->getRepository(\Chill\MainBundle\Entity\Center::class) $dql = 'FROM '.Event::class.' e JOIN e.center center JOIN e.circle scope WHERE center.name LIKE :cname AND JSON_EXTRACT(scope.name, \'fr\') LIKE :sname';
->findByName($centerName);
$circles = $this->em->getRepository(\Chill\MainBundle\Entity\Scope::class) $ids = $this->em->createQuery(
->findAll(); 'SELECT DISTINCT e.id '.$dql
array_filter($circles, static fn ($circle) => \in_array($circleName, $circle->getName(), true)); )
$circle = $circles[0]; ->setParameters(['cname' => $centerName, 'sname' => $circleName])
->getResult();
$events = $this->em->getRepository(\Chill\EventBundle\Entity\Event::class) return $this->eventRepository->find($ids[array_rand($ids)]['id']);
->findBy(['center' => $center, 'circle' => $circle]);
return $events[array_rand($events)];
} }
/** /**
@ -467,7 +476,7 @@ final class ParticipationControllerTest extends WebTestCase
* @param string $centerName * @param string $centerName
* @param type $circleName * @param type $circleName
* *
* @return \Chill\EventBundle\Entity\Event * @return Event
*/ */
protected function getRandomEventWithMultipleParticipations( protected function getRandomEventWithMultipleParticipations(
$centerName = 'Center A', $centerName = 'Center A',
@ -479,35 +488,4 @@ final class ParticipationControllerTest extends WebTestCase
$event : $event :
$this->getRandomEventWithMultipleParticipations($centerName, $circleName); $this->getRandomEventWithMultipleParticipations($centerName, $circleName);
} }
/**
* Returns a person randomly.
*
* This function does not give the same person twice
* for each test.
*
* You may ask to ignore some people by adding their id to the property
* `$this->personsIdsCache`
*
* @param string $centerName
*
* @return \Chill\PersonBundle\Entity\Person
*/
protected function getRandomPerson($centerName = 'Center A')
{
$center = $this->em->getRepository(\Chill\MainBundle\Entity\Center::class)
->findByName($centerName);
$persons = $this->em->getRepository(\Chill\PersonBundle\Entity\Person::class)
->findBy(['center' => $center]);
$person = $persons[array_rand($persons)];
if (\in_array($person->getId(), $this->personsIdsCache, true)) {
return $this->getRandomPerson($centerName); // we try another time
}
$this->personsIdsCache[] = $person->getId();
return $person;
}
} }

View File

@ -0,0 +1,97 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\EventBundle\Tests\Repository;
use Chill\EventBundle\Repository\EventACLAwareRepository;
use Chill\EventBundle\Security\Authorization\EventVoter;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Security\Authorization\AuthorizationHelperForCurrentUserInterface;
use Doctrine\ORM\EntityManagerInterface;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Security\Core\Security;
/**
* @internal
*
* @coversNothing
*/
class EventACLAwareRepositoryTest extends KernelTestCase
{
use ProphecyTrait;
protected function setUp(): void
{
self::bootKernel();
}
/**
* @dataProvider generateFilters
*
* @throws \Doctrine\ORM\NoResultException
* @throws \Doctrine\ORM\NonUniqueResultException
*/
public function testCountAllViewable(array $filters): void
{
$repository = $this->buildEventACLAwareRepository();
$this->assertGreaterThanOrEqual(0, $repository->countAllViewable($filters));
}
/**
* @dataProvider generateFilters
*/
public function testFindAllViewable(array $filters): void
{
$repository = $this->buildEventACLAwareRepository();
$this->assertIsArray($repository->findAllViewable($filters));
}
public function generateFilters(): iterable
{
yield [[]];
}
public function buildEventACLAwareRepository(): EventACLAwareRepository
{
$em = self::$container->get(EntityManagerInterface::class);
$user = $em->createQuery('SELECT u FROM '.User::class.' u')
->setMaxResults(1)
->getSingleResult()
;
$scopes = $em->createQuery('SELECT s FROM '.Scope::class.' s')
->setMaxResults(3)
->getResult();
$centers = $em->createQuery('SELECT c FROM '.Center::class.' c')
->setMaxResults(3)
->getResult();
$security = $this->prophesize(Security::class);
$security->getUser()->willReturn($user);
$authorizationHelper = $this->prophesize(AuthorizationHelperForCurrentUserInterface::class);
$authorizationHelper->getReachableCenters(EventVoter::SEE)->willReturn($centers);
$authorizationHelper->getReachableScopes(EventVoter::SEE, Argument::type(Center::class))->willReturn($scopes);
return new EventACLAwareRepository(
$authorizationHelper->reveal(),
$em,
$security->reveal()
);
}
}

View File

@ -1,16 +0,0 @@
services:
Chill\EventBundle\Controller\EventController:
arguments:
$eventDispatcher: '@Symfony\Contracts\EventDispatcher\EventDispatcherInterface'
$authorizationHelper: '@Chill\MainBundle\Security\Authorization\AuthorizationHelper'
$formFactoryInterface: '@Symfony\Component\Form\FormFactoryInterface'
$translator: '@Symfony\Contracts\Translation\TranslatorInterface'
$paginator: '@chill_main.paginator_factory'
public: true
tags: ['controller.service_arguments']
Chill\EventBundle\Controller\ParticipationController:
arguments:
$logger: '@Psr\Log\LoggerInterface'
tags: ['controller.service_arguments']

View File

@ -1,7 +0,0 @@
services:
Chill\EventBundle\Menu\PersonMenuBuilder:
arguments:
$authorizationChecker: '@Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface'
$translator: '@Symfony\Contracts\Translation\TranslatorInterface'
tags:
- { name: 'chill.menu_builder' }

View File

@ -1,26 +0,0 @@
Chill\EventBundle\Entity\Participation:
properties:
event:
- NotNull: ~
status:
- NotNull: ~
person:
- NotNull: ~
constraints:
- Callback: isConsistent
Chill\EventBundle\Entity\Event:
properties:
name:
- Length:
min: 3
max: 75
minMessage: The event name must have at least {{ limit }} characters.
maxMessage: The event name must have maximum {{ limit }} characters.
type:
- NotNull: ~
circle:
- NotNull: ~
center:
- NotNull: ~

View File

@ -19,11 +19,13 @@ use Doctrine\Migrations\AbstractMigration;
*/ */
class Version20160318111334 extends AbstractMigration class Version20160318111334 extends AbstractMigration
{ {
public function getDescription(): string
{
return 'initialize the bundle chill event';
}
public function down(Schema $schema): void public function down(Schema $schema): void
{ {
// this down() migration is auto-generated, please modify it to your needs
$this->abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.');
$this->addSql('ALTER TABLE chill_event_role DROP CONSTRAINT FK_AA714E54C54C8C93'); $this->addSql('ALTER TABLE chill_event_role DROP CONSTRAINT FK_AA714E54C54C8C93');
$this->addSql('ALTER TABLE chill_event_status DROP CONSTRAINT FK_A6CC85D0C54C8C93'); $this->addSql('ALTER TABLE chill_event_status DROP CONSTRAINT FK_A6CC85D0C54C8C93');
$this->addSql('ALTER TABLE chill_event_participation DROP CONSTRAINT FK_4E7768ACD60322AC'); $this->addSql('ALTER TABLE chill_event_participation DROP CONSTRAINT FK_4E7768ACD60322AC');
@ -50,9 +52,6 @@ class Version20160318111334 extends AbstractMigration
public function up(Schema $schema): void public function up(Schema $schema): void
{ {
// this up() migration is auto-generated, please modify it to your needs
$this->abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.');
$this->addSql('CREATE SEQUENCE chill_event_event_type_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); $this->addSql('CREATE SEQUENCE chill_event_event_type_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
$this->addSql('CREATE SEQUENCE chill_event_role_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); $this->addSql('CREATE SEQUENCE chill_event_role_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
$this->addSql('CREATE SEQUENCE chill_event_status_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); $this->addSql('CREATE SEQUENCE chill_event_status_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
@ -123,11 +122,26 @@ class Version20160318111334 extends AbstractMigration
.'FOREIGN KEY (event_id) ' .'FOREIGN KEY (event_id) '
.'REFERENCES chill_event_event (id) ' .'REFERENCES chill_event_event (id) '
.'NOT DEFERRABLE INITIALLY IMMEDIATE'); .'NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE chill_event_participation '
// before adding fk constraint to person, check what is the table name
$results = $this->connection->executeQuery('SELECT EXISTS (SELECT 1 FROM pg_tables WHERE tablename = \'chill_person_person\')');
/** @var bool $isChillPersonPersonTable */
$isChillPersonPersonTable = $results->fetchFirstColumn()[0];
if ($isChillPersonPersonTable) {
$this->addSql('ALTER TABLE chill_event_participation '
.'ADD CONSTRAINT FK_4E7768AC217BBB47 '
.'FOREIGN KEY (person_id) '
.'REFERENCES chill_person_person (id) '
.'NOT DEFERRABLE INITIALLY IMMEDIATE');
} else {
$this->addSql('ALTER TABLE chill_event_participation '
.'ADD CONSTRAINT FK_4E7768AC217BBB47 ' .'ADD CONSTRAINT FK_4E7768AC217BBB47 '
.'FOREIGN KEY (person_id) ' .'FOREIGN KEY (person_id) '
.'REFERENCES Person (id) ' .'REFERENCES Person (id) '
.'NOT DEFERRABLE INITIALLY IMMEDIATE'); .'NOT DEFERRABLE INITIALLY IMMEDIATE');
}
$this->addSql('ALTER TABLE chill_event_participation ' $this->addSql('ALTER TABLE chill_event_participation '
.'ADD CONSTRAINT FK_4E7768ACD60322AC ' .'ADD CONSTRAINT FK_4E7768ACD60322AC '
.'FOREIGN KEY (role_id) ' .'FOREIGN KEY (role_id) '

View File

@ -19,18 +19,19 @@ use Doctrine\Migrations\AbstractMigration;
*/ */
final class Version20190110140538 extends AbstractMigration final class Version20190110140538 extends AbstractMigration
{ {
public function getDescription(): string
{
return 'switch event date to datetime';
}
public function down(Schema $schema): void public function down(Schema $schema): void
{ {
$this->abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.');
$this->addSql('ALTER TABLE chill_event_event ALTER date TYPE DATE'); $this->addSql('ALTER TABLE chill_event_event ALTER date TYPE DATE');
$this->addSql('ALTER TABLE chill_event_event ALTER date DROP DEFAULT'); $this->addSql('ALTER TABLE chill_event_event ALTER date DROP DEFAULT');
} }
public function up(Schema $schema): void public function up(Schema $schema): void
{ {
$this->abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.');
$this->addSql('ALTER TABLE chill_event_event ALTER date TYPE TIMESTAMP(0) WITHOUT TIME ZONE'); $this->addSql('ALTER TABLE chill_event_event ALTER date TYPE TIMESTAMP(0) WITHOUT TIME ZONE');
$this->addSql('ALTER TABLE chill_event_event ALTER date DROP DEFAULT'); $this->addSql('ALTER TABLE chill_event_event ALTER date DROP DEFAULT');
} }

View File

@ -19,11 +19,13 @@ use Doctrine\Migrations\AbstractMigration;
*/ */
final class Version20190115140042 extends AbstractMigration final class Version20190115140042 extends AbstractMigration
{ {
public function getDescription(): string
{
return 'add a moderator field to events';
}
public function down(Schema $schema): void public function down(Schema $schema): void
{ {
// this down() migration is auto-generated, please modify it to your needs
$this->abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.');
$this->addSql('ALTER TABLE chill_event_event DROP CONSTRAINT FK_FA320FC8D0AFA354'); $this->addSql('ALTER TABLE chill_event_event DROP CONSTRAINT FK_FA320FC8D0AFA354');
$this->addSql('DROP INDEX IDX_FA320FC8D0AFA354'); $this->addSql('DROP INDEX IDX_FA320FC8D0AFA354');
$this->addSql('ALTER TABLE chill_event_event DROP moderator_id'); $this->addSql('ALTER TABLE chill_event_event DROP moderator_id');
@ -31,9 +33,6 @@ final class Version20190115140042 extends AbstractMigration
public function up(Schema $schema): void public function up(Schema $schema): void
{ {
// this up() migration is auto-generated, please modify it to your needs
$this->abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.');
$this->addSql('ALTER TABLE chill_event_event ADD moderator_id INT DEFAULT NULL'); $this->addSql('ALTER TABLE chill_event_event ADD moderator_id INT DEFAULT NULL');
$this->addSql('ALTER TABLE chill_event_event ADD CONSTRAINT FK_FA320FC8D0AFA354 FOREIGN KEY (moderator_id) REFERENCES chill_person_person (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); $this->addSql('ALTER TABLE chill_event_event ADD CONSTRAINT FK_FA320FC8D0AFA354 FOREIGN KEY (moderator_id) REFERENCES chill_person_person (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('CREATE INDEX IDX_FA320FC8D0AFA354 ON chill_event_event (moderator_id)'); $this->addSql('CREATE INDEX IDX_FA320FC8D0AFA354 ON chill_event_event (moderator_id)');

View File

@ -19,20 +19,19 @@ use Doctrine\Migrations\AbstractMigration;
*/ */
final class Version20190201143121 extends AbstractMigration final class Version20190201143121 extends AbstractMigration
{ {
public function getDescription(): string
{
return 'fix moderator: relation with user (not person)';
}
public function down(Schema $schema): void public function down(Schema $schema): void
{ {
// this down() migration is auto-generated, please modify it to your needs
$this->abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.');
$this->addSql('ALTER TABLE chill_event_event DROP CONSTRAINT fk_fa320fc8d0afa354'); $this->addSql('ALTER TABLE chill_event_event DROP CONSTRAINT fk_fa320fc8d0afa354');
$this->addSql('ALTER TABLE chill_event_event ADD CONSTRAINT fk_fa320fc8d0afa354 FOREIGN KEY (moderator_id) REFERENCES chill_person_person (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); $this->addSql('ALTER TABLE chill_event_event ADD CONSTRAINT fk_fa320fc8d0afa354 FOREIGN KEY (moderator_id) REFERENCES chill_person_person (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
} }
public function up(Schema $schema): void public function up(Schema $schema): void
{ {
// this up() migration is auto-generated, please modify it to your needs
$this->abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.');
$this->addSql('ALTER TABLE chill_event_event DROP CONSTRAINT FK_FA320FC8D0AFA354'); $this->addSql('ALTER TABLE chill_event_event DROP CONSTRAINT FK_FA320FC8D0AFA354');
$this->addSql('ALTER TABLE chill_event_event ADD CONSTRAINT FK_FA320FC8D0AFA354 FOREIGN KEY (moderator_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); $this->addSql('ALTER TABLE chill_event_event ADD CONSTRAINT FK_FA320FC8D0AFA354 FOREIGN KEY (moderator_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
} }

View File

@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\Migrations\Event;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20231127134244 extends AbstractMigration
{
public function getDescription(): string
{
return 'add creation - update information on event and event participation';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_event_event ADD createdAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
$this->addSql('ALTER TABLE chill_event_event ADD updatedAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
$this->addSql('ALTER TABLE chill_event_event ADD createdBy_id INT DEFAULT NULL');
$this->addSql('ALTER TABLE chill_event_event ADD updatedBy_id INT DEFAULT NULL');
$this->addSql('COMMENT ON COLUMN chill_event_event.createdAt IS \'(DC2Type:datetime_immutable)\'');
$this->addSql('COMMENT ON COLUMN chill_event_event.updatedAt IS \'(DC2Type:datetime_immutable)\'');
$this->addSql('ALTER TABLE chill_event_event ADD CONSTRAINT FK_FA320FC83174800F FOREIGN KEY (createdBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE chill_event_event ADD CONSTRAINT FK_FA320FC865FF1AEC FOREIGN KEY (updatedBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('CREATE INDEX IDX_FA320FC83174800F ON chill_event_event (createdBy_id)');
$this->addSql('CREATE INDEX IDX_FA320FC865FF1AEC ON chill_event_event (updatedBy_id)');
$this->addSql('ALTER TABLE chill_event_participation ADD createdAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
$this->addSql('ALTER TABLE chill_event_participation ADD updatedAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
$this->addSql('ALTER TABLE chill_event_participation ADD createdBy_id INT DEFAULT NULL');
$this->addSql('ALTER TABLE chill_event_participation ADD updatedBy_id INT DEFAULT NULL');
$this->addSql('COMMENT ON COLUMN chill_event_participation.createdAt IS \'(DC2Type:datetime_immutable)\'');
$this->addSql('COMMENT ON COLUMN chill_event_participation.updatedAt IS \'(DC2Type:datetime_immutable)\'');
$this->addSql('ALTER TABLE chill_event_participation ADD CONSTRAINT FK_4E7768AC3174800F FOREIGN KEY (createdBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE chill_event_participation ADD CONSTRAINT FK_4E7768AC65FF1AEC FOREIGN KEY (updatedBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('CREATE INDEX IDX_4E7768AC3174800F ON chill_event_participation (createdBy_id)');
$this->addSql('CREATE INDEX IDX_4E7768AC65FF1AEC ON chill_event_participation (updatedBy_id)');
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_event_event DROP createdAt');
$this->addSql('ALTER TABLE chill_event_event DROP updatedAt');
$this->addSql('ALTER TABLE chill_event_event DROP createdBy_id');
$this->addSql('ALTER TABLE chill_event_event DROP updatedBy_id');
$this->addSql('ALTER TABLE chill_event_participation DROP createdAt');
$this->addSql('ALTER TABLE chill_event_participation DROP updatedAt');
$this->addSql('ALTER TABLE chill_event_participation DROP createdBy_id');
$this->addSql('ALTER TABLE chill_event_participation DROP updatedBy_id');
}
}

View File

@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\Migrations\Event;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20231128114959 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add unique index on participation and drop column participation::lastUpdate';
}
public function up(Schema $schema): void
{
$this->addSql('UPDATE chill_event_participation SET updatedAt=lastupdate WHERE updatedat IS NULL');
$this->addSql('ALTER TABLE chill_event_participation DROP lastupdate');
$this->addSql('WITH ordering AS (SELECT id, event_id, person_id, rank() OVER (PARTITION BY event_id, person_id ORDER BY id DESC) as ranked FROM chill_event_participation),
not_last AS (SELECT * FROM ordering where ranked > 1)
DELETE FROM chill_event_participation WHERE id IN (select id FROM not_last)');
$this->addSql('CREATE UNIQUE INDEX chill_event_participation_event_person_unique_idx ON chill_event_participation (event_id, person_id)');
}
public function down(Schema $schema): void
{
$this->addSql('DROP INDEX chill_event_participation_event_person_unique_idx');
$this->addSql('ALTER TABLE chill_event_participation ADD lastupdate TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL');
$this->addSql('UPDATE chill_event_participation set lastupdate = updatedat');
}
}

View File

@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\Migrations\Event;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20231128122635 extends AbstractMigration
{
public function getDescription(): string
{
return 'Append more fields on event: location, documents, and comment';
}
public function up(Schema $schema): void
{
$this->addSql('CREATE TABLE chill_event_event_documents (event_id INT NOT NULL, storedobject_id INT NOT NULL, PRIMARY KEY(event_id, storedobject_id))');
$this->addSql('CREATE INDEX IDX_5C1B638671F7E88B ON chill_event_event_documents (event_id)');
$this->addSql('CREATE INDEX IDX_5C1B6386EE684399 ON chill_event_event_documents (storedobject_id)');
$this->addSql('ALTER TABLE chill_event_event_documents ADD CONSTRAINT FK_5C1B638671F7E88B FOREIGN KEY (event_id) REFERENCES chill_event_event (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE chill_event_event_documents ADD CONSTRAINT FK_5C1B6386EE684399 FOREIGN KEY (storedobject_id) REFERENCES chill_doc.stored_object (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE chill_event_event ADD location_id INT DEFAULT NULL');
$this->addSql('ALTER TABLE chill_event_event ADD organizationCost NUMERIC(10, 4) DEFAULT 0.0');
$this->addSql('ALTER TABLE chill_event_event ADD comment_comment TEXT DEFAULT NULL');
$this->addSql('ALTER TABLE chill_event_event ADD comment_date TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
$this->addSql('ALTER TABLE chill_event_event ADD comment_userId INT DEFAULT NULL');
$this->addSql('ALTER TABLE chill_event_event ADD CONSTRAINT FK_FA320FC864D218E FOREIGN KEY (location_id) REFERENCES chill_main_location (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('CREATE INDEX IDX_FA320FC864D218E ON chill_event_event (location_id)');
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_event_event_documents DROP CONSTRAINT FK_5C1B638671F7E88B');
$this->addSql('ALTER TABLE chill_event_event_documents DROP CONSTRAINT FK_5C1B6386EE684399');
$this->addSql('DROP TABLE chill_event_event_documents');
$this->addSql('ALTER TABLE chill_event_event DROP location_id');
$this->addSql('ALTER TABLE chill_event_event DROP organizationCost');
$this->addSql('ALTER TABLE chill_event_event DROP comment_comment');
$this->addSql('ALTER TABLE chill_event_event DROP comment_date');
$this->addSql('ALTER TABLE chill_event_event DROP comment_userId');
}
}

View File

@ -11,3 +11,11 @@ count participations to this event: >-
one {Un participant à l'événement} one {Un participant à l'événement}
other {# participants à l'événement} other {# participants à l'événement}
} }
events:
and_other_count_participants: >-
{ count, plural,
=0 {Aucun autre participant}
one {et un autre participant}
other {et # autres participants}
}

View File

@ -26,6 +26,8 @@ Event edit: Modifier un événement
Edit the event: Modifier l'événement Edit the event: Modifier l'événement
The event was updated: L'événement a été modifié The event was updated: L'événement a été modifié
The event was created: L'événement a été créé The event was created: L'événement a été créé
List of events: Liste des événements
Any location for this event: Aucune localisation pour cet événement
#crud participation #crud participation
Edit all the participations: Modifier toutes les participations Edit all the participations: Modifier toutes les participations
@ -50,6 +52,7 @@ Remove participation: Supprimer la participation
Delete event: Supprimer l'événement Delete event: Supprimer l'événement
Are you sure you want to remove that participation ?: Êtes-vous certain de vouloir supprimer cette participation ? Are you sure you want to remove that participation ?: Êtes-vous certain de vouloir supprimer cette participation ?
Are you sure you want to remove that event ?: Êtes-vous certain de vouloir supprimer cet événement, ainsi que toutes les participations associées ? Are you sure you want to remove that event ?: Êtes-vous certain de vouloir supprimer cet événement, ainsi que toutes les participations associées ?
Any participation for this person: Cet usager ne participe à aucun évenements
#search #search
Event search: Recherche d'événements Event search: Recherche d'événements
@ -107,3 +110,17 @@ csv: csv
Create a new role: Créer un nouveau rôle Create a new role: Créer un nouveau rôle
Create a new type: Créer un nouveau type Create a new type: Créer un nouveau type
Create a new status: Créer un nouveau statut Create a new status: Créer un nouveau statut
event:
fields:
organizationCost: Coût d'organisation
location: Localisation
documents: Documents
form:
organisationCost_help: Coût d'organisation pour la structure. Utile pour les statistiques.
add_document: Ajouter un document
remove_document: Supprimer le document
filter:
event_types: Par types d'événement
event_dates: Par date d'événement

View File

@ -0,0 +1,3 @@
event:
validation:
person_already_participate_to_event: L'usager est déjà inscrit à l'événement

View File

@ -42,6 +42,8 @@ class PickUserDynamicType extends AbstractType
$view->vars['types'] = ['user']; $view->vars['types'] = ['user'];
$view->vars['uniqid'] = uniqid('pick_user_dyn'); $view->vars['uniqid'] = uniqid('pick_user_dyn');
$view->vars['suggested'] = []; $view->vars['suggested'] = [];
$view->vars['as_id'] = true === $options['as_id'] ? '1' : '0';
$view->vars['submit_on_adding_new_entity'] = true === $options['submit_on_adding_new_entity'] ? '1' : '0';
foreach ($options['suggested'] as $user) { foreach ($options['suggested'] as $user) {
$view->vars['suggested'][] = $this->normalizer->normalize($user, 'json', ['groups' => 'read']); $view->vars['suggested'][] = $this->normalizer->normalize($user, 'json', ['groups' => 'read']);
@ -54,7 +56,12 @@ class PickUserDynamicType extends AbstractType
->setDefault('multiple', false) ->setDefault('multiple', false)
->setAllowedTypes('multiple', ['bool']) ->setAllowedTypes('multiple', ['bool'])
->setDefault('compound', false) ->setDefault('compound', false)
->setDefault('suggested', []); ->setDefault('suggested', [])
// if set to true, only the id will be set inside the content. The denormalization will not work.
->setDefault('as_id', false)
->setAllowedTypes('as_id', ['bool'])
->setDefault('submit_on_adding_new_entity', false)
->setAllowedTypes('submit_on_adding_new_entity', ['bool']);
} }
public function getBlockPrefix() public function getBlockPrefix()

View File

@ -17,7 +17,7 @@ use Symfony\Component\Routing\RouterInterface;
/** /**
* Create paginator instances. * Create paginator instances.
*/ */
class PaginatorFactory final readonly class PaginatorFactory implements PaginatorFactoryInterface
{ {
final public const DEFAULT_CURRENT_PAGE_KEY = 'page'; final public const DEFAULT_CURRENT_PAGE_KEY = 'page';
@ -25,23 +25,20 @@ class PaginatorFactory
final public const DEFAULT_PAGE_NUMBER = 1; final public const DEFAULT_PAGE_NUMBER = 1;
/**
* @param int $itemPerPage
*/
public function __construct( public function __construct(
/** /**
* the request stack. * the request stack.
*/ */
private readonly RequestStack $requestStack, private RequestStack $requestStack,
/** /**
* the router and generator for url. * the router and generator for url.
*/ */
private readonly RouterInterface $router, private RouterInterface $router,
/** /**
* the default item per page. This may be overriden by * the default item per page. This may be overriden by
* the request or inside the paginator. * the request or inside the paginator.
*/ */
private $itemPerPage = 20 private int $itemPerPage = 20
) { ) {
} }
@ -51,17 +48,14 @@ class PaginatorFactory
* The default route and route parameters are the current ones. If set, * The default route and route parameters are the current ones. If set,
* thos route are overriden. * thos route are overriden.
* *
* @param int $totalItems
* @param string|null $route the specific route to use in pages * @param string|null $route the specific route to use in pages
* @param array|null $routeParameters the specific route parameters to use in pages * @param array|null $routeParameters the specific route parameters to use in pages
*
* @return PaginatorInterface
*/ */
public function create( public function create(
$totalItems, int $totalItems,
?string $route = null, ?string $route = null,
?array $routeParameters = null ?array $routeParameters = null
) { ): PaginatorInterface {
return new Paginator( return new Paginator(
$totalItems, $totalItems,
$this->getCurrentItemsPerPage(), $this->getCurrentItemsPerPage(),
@ -74,7 +68,7 @@ class PaginatorFactory
); );
} }
public function getCurrentItemsPerPage() public function getCurrentItemsPerPage(): int
{ {
return $this->requestStack return $this->requestStack
->getCurrentRequest() ->getCurrentRequest()
@ -82,16 +76,13 @@ class PaginatorFactory
->getInt(self::DEFAULT_ITEM_PER_NUMBER_KEY, $this->itemPerPage); ->getInt(self::DEFAULT_ITEM_PER_NUMBER_KEY, $this->itemPerPage);
} }
public function getCurrentPageFirstItemNumber() public function getCurrentPageFirstItemNumber(): int
{ {
return ($this->getCurrentPageNumber() - 1) * return ($this->getCurrentPageNumber() - 1) *
$this->getCurrentItemsPerPage(); $this->getCurrentItemsPerPage();
} }
/** public function getCurrentPageNumber(): int
* @return int
*/
public function getCurrentPageNumber()
{ {
return $this->requestStack return $this->requestStack
->getCurrentRequest() ->getCurrentRequest()
@ -99,14 +90,14 @@ class PaginatorFactory
->getInt(self::DEFAULT_CURRENT_PAGE_KEY, self::DEFAULT_PAGE_NUMBER); ->getInt(self::DEFAULT_CURRENT_PAGE_KEY, self::DEFAULT_PAGE_NUMBER);
} }
protected function getCurrentRoute() private function getCurrentRoute()
{ {
$request = $this->requestStack->getCurrentRequest(); $request = $this->requestStack->getCurrentRequest();
return $request->get('_route'); return $request->get('_route');
} }
protected function getCurrentRouteParameters() private function getCurrentRouteParameters()
{ {
return array_merge( return array_merge(
$this->router->getContext()->getParameters(), $this->router->getContext()->getParameters(),

View File

@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\Pagination;
/**
* Create paginator instances.
*/
interface PaginatorFactoryInterface
{
/**
* create a paginator instance.
*
* The default route and route parameters are the current ones. If set,
* thos route are overriden.
*
* @param string|null $route the specific route to use in pages
* @param array|null $routeParameters the specific route parameters to use in pages
*/
public function create(int $totalItems, ?string $route = null, ?array $routeParameters = null): PaginatorInterface;
public function getCurrentItemsPerPage(): int;
public function getCurrentPageFirstItemNumber(): int;
public function getCurrentPageNumber(): int;
}

View File

@ -24,7 +24,10 @@ function loadDynamicPicker(element) {
(input.value === '[]' || input.value === '') ? (input.value === '[]' || input.value === '') ?
null : [ JSON.parse(input.value) ] null : [ JSON.parse(input.value) ]
) )
suggested = JSON.parse(el.dataset.suggested) suggested = JSON.parse(el.dataset.suggested),
as_id = parseInt(el.dataset.asId) === 1,
submit_on_adding_new_entity = parseInt(el.dataset.submitOnAddingNewEntity) === 1
label = el.dataset.label;
if (!isMultiple) { if (!isMultiple) {
if (input.value === '[]'){ if (input.value === '[]'){
@ -39,6 +42,7 @@ function loadDynamicPicker(element) {
':picked="picked" ' + ':picked="picked" ' +
':uniqid="uniqid" ' + ':uniqid="uniqid" ' +
':suggested="notPickedSuggested" ' + ':suggested="notPickedSuggested" ' +
':label="label" ' +
'@addNewEntity="addNewEntity" ' + '@addNewEntity="addNewEntity" ' +
'@removeEntity="removeEntity"></pick-entity>', '@removeEntity="removeEntity"></pick-entity>',
components: { components: {
@ -50,7 +54,10 @@ function loadDynamicPicker(element) {
types: JSON.parse(el.dataset.types), types: JSON.parse(el.dataset.types),
picked: picked === null ? [] : picked, picked: picked === null ? [] : picked,
uniqid: el.dataset.uniqid, uniqid: el.dataset.uniqid,
suggested: suggested suggested,
as_id,
submit_on_adding_new_entity,
label,
} }
}, },
computed: { computed: {
@ -69,7 +76,12 @@ function loadDynamicPicker(element) {
return el.type === entity.type && el.id === entity.id; return el.type === entity.type && el.id === entity.id;
})) { })) {
this.picked.push(entity); this.picked.push(entity);
input.value = JSON.stringify(this.picked); if (!as_id) {
input.value = JSON.stringify(this.picked);
} else {
const ids = this.picked.map(el => el.id);
input.value = ids.join(',');
}
console.log(entity) console.log(entity)
} }
} else { } else {
@ -78,9 +90,17 @@ function loadDynamicPicker(element) {
})) { })) {
this.picked.splice(0, this.picked.length); this.picked.splice(0, this.picked.length);
this.picked.push(entity); this.picked.push(entity);
input.value = JSON.stringify(this.picked[0]); if (!as_id) {
input.value = JSON.stringify(this.picked[0]);
} else {
input.value = this.picked.map(el => el.id);
}
} }
} }
if (this.submit_on_adding_new_entity) {
input.form.submit();
}
}, },
removeEntity({entity}) { removeEntity({entity}) {
if (-1 === this.suggested.findIndex(e => e.type === entity.type && e.id === entity.id)) { if (-1 === this.suggested.findIndex(e => e.type === entity.type && e.id === entity.id)) {

View File

@ -56,6 +56,10 @@ export default {
suggested: { suggested: {
type: Array, type: Array,
default: [] default: []
},
label: {
type: String,
required: false,
} }
}, },
emits: ['addNewEntity', 'removeEntity'], emits: ['addNewEntity', 'removeEntity'],
@ -80,6 +84,10 @@ export default {
}; };
}, },
translatedListOfTypes() { translatedListOfTypes() {
if (this.label !== '') {
return this.label;
}
let trans = []; let trans = [];
this.types.forEach(t => { this.types.forEach(t => {
if (this.$props.multiple) { if (this.$props.multiple) {

View File

@ -256,7 +256,10 @@
data-types="{{ form.vars['types']|json_encode }}" data-types="{{ form.vars['types']|json_encode }}"
data-multiple="{{ form.vars['multiple'] }}" data-multiple="{{ form.vars['multiple'] }}"
data-uniqid="{{ form.vars['uniqid'] }}" data-uniqid="{{ form.vars['uniqid'] }}"
data-suggested="{{ form.vars['suggested']|json_encode|escape('html_attr') }}"></div> data-suggested="{{ form.vars['suggested']|json_encode|escape('html_attr') }}"
data-as-id="{{ form.vars['as_id'] }}"
data-submit-on-adding-new-entity="{{ form.vars['submit_on_adding_new_entity'] }}"
data-label="{{ form.vars['label']|trans|escape('html_attr') }}"></div>
{% endblock %} {% endblock %}
{% block pick_postal_code_widget %} {% block pick_postal_code_widget %}

View File

@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\Test;
use Chill\MainBundle\Pagination\Paginator;
use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Pagination\PaginatorFactoryInterface;
use Chill\MainBundle\Pagination\PaginatorInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
class DummyPaginator implements PaginatorFactoryInterface
{
public function __construct(
private readonly UrlGeneratorInterface $urlGenerator,
private readonly string $route,
private readonly array $routeParameters = []
) {
}
public function create(int $totalItems, ?string $route = null, ?array $routeParameters = null): PaginatorInterface
{
return new Paginator(
$totalItems,
$totalItems,
1,
$this->route,
$this->routeParameters,
$this->urlGenerator,
PaginatorFactory::DEFAULT_CURRENT_PAGE_KEY,
PaginatorFactory::DEFAULT_ITEM_PER_NUMBER_KEY
);
}
public function getCurrentItemsPerPage(): int
{
return 20;
}
public function getCurrentPageFirstItemNumber(): int
{
return 1;
}
public function getCurrentPageNumber(): int
{
return 1;
}
}

View File

@ -12,6 +12,7 @@ services:
- "%chill_main.pagination.item_per_page%" - "%chill_main.pagination.item_per_page%"
Chill\MainBundle\Pagination\PaginatorFactory: '@chill_main.paginator_factory' Chill\MainBundle\Pagination\PaginatorFactory: '@chill_main.paginator_factory'
Chill\MainBundle\Pagination\PaginatorFactoryInterface: '@chill_main.paginator_factory'
chill_main.paginator.twig_extensions: chill_main.paginator.twig_extensions:
class: Chill\MainBundle\Pagination\ChillPaginationTwig class: Chill\MainBundle\Pagination\ChillPaginationTwig

View File

@ -41,6 +41,8 @@ class PickPersonDynamicType extends AbstractType
$view->vars['types'] = ['person']; $view->vars['types'] = ['person'];
$view->vars['uniqid'] = uniqid('pick_user_dyn'); $view->vars['uniqid'] = uniqid('pick_user_dyn');
$view->vars['suggested'] = []; $view->vars['suggested'] = [];
$view->vars['as_id'] = true === $options['as_id'] ? '1' : '0';
$view->vars['submit_on_adding_new_entity'] = true === $options['submit_on_adding_new_entity'] ? '1' : '0';
foreach ($options['suggested'] as $person) { foreach ($options['suggested'] as $person) {
$view->vars['suggested'][] = $this->normalizer->normalize($person, 'json', ['groups' => 'read']); $view->vars['suggested'][] = $this->normalizer->normalize($person, 'json', ['groups' => 'read']);
@ -53,7 +55,11 @@ class PickPersonDynamicType extends AbstractType
->setDefault('multiple', false) ->setDefault('multiple', false)
->setAllowedTypes('multiple', ['bool']) ->setAllowedTypes('multiple', ['bool'])
->setDefault('compound', false) ->setDefault('compound', false)
->setDefault('suggested', []); ->setDefault('suggested', [])
->setDefault('as_id', false)
->setAllowedTypes('as_id', ['bool'])
->setDefault('submit_on_adding_new_entity', false)
->setAllowedTypes('submit_on_adding_new_entity', ['bool']);
} }
public function getBlockPrefix() public function getBlockPrefix()

View File

@ -32,7 +32,18 @@ class Version20141129012050 extends AbstractMigration
$this->addSql('CREATE INDEX IDX_C38372B2217BBB47 ON Report (person_id);'); $this->addSql('CREATE INDEX IDX_C38372B2217BBB47 ON Report (person_id);');
$this->addSql('CREATE INDEX IDX_C38372B216D2C9F0 ON Report (cFGroup_id);'); $this->addSql('CREATE INDEX IDX_C38372B216D2C9F0 ON Report (cFGroup_id);');
$this->addSql('ALTER TABLE Report ADD CONSTRAINT FK_C38372B2A76ED395 FOREIGN KEY (user_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE;'); $this->addSql('ALTER TABLE Report ADD CONSTRAINT FK_C38372B2A76ED395 FOREIGN KEY (user_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE;');
$this->addSql('ALTER TABLE Report ADD CONSTRAINT FK_C38372B2217BBB47 FOREIGN KEY (person_id) REFERENCES Person (id) NOT DEFERRABLE INITIALLY IMMEDIATE;');
// before adding fk constraint to person, check what is the table name
$results = $this->connection->executeQuery('SELECT EXISTS (SELECT 1 FROM pg_tables WHERE tablename = \'chill_person_person\')');
/** @var bool $isChillPersonPersonTable */
$isChillPersonPersonTable = $results->fetchFirstColumn()[0];
if ($isChillPersonPersonTable) {
$this->addSql('ALTER TABLE Report ADD CONSTRAINT FK_C38372B2217BBB47 FOREIGN KEY (person_id) REFERENCES chill_person_person (id) NOT DEFERRABLE INITIALLY IMMEDIATE;');
} else {
$this->addSql('ALTER TABLE Report ADD CONSTRAINT FK_C38372B2217BBB47 FOREIGN KEY (person_id) REFERENCES Person (id) NOT DEFERRABLE INITIALLY IMMEDIATE;');
}
$this->addSql('ALTER TABLE Report ADD CONSTRAINT FK_C38372B216D2C9F0 FOREIGN KEY (cFGroup_id) REFERENCES CustomFieldsGroup (id) NOT DEFERRABLE INITIALLY IMMEDIATE;'); $this->addSql('ALTER TABLE Report ADD CONSTRAINT FK_C38372B216D2C9F0 FOREIGN KEY (cFGroup_id) REFERENCES CustomFieldsGroup (id) NOT DEFERRABLE INITIALLY IMMEDIATE;');
} }
} }

View File

@ -41,6 +41,8 @@ class PickThirdpartyDynamicType extends AbstractType
$view->vars['types'] = ['thirdparty']; $view->vars['types'] = ['thirdparty'];
$view->vars['uniqid'] = uniqid('pick_user_dyn'); $view->vars['uniqid'] = uniqid('pick_user_dyn');
$view->vars['suggested'] = []; $view->vars['suggested'] = [];
$view->vars['as_id'] = true === $options['as_id'] ? '1' : '0';
$view->vars['submit_on_adding_new_entity'] = true === $options['submit_on_adding_new_entity'] ? '1' : '0';
foreach ($options['suggested'] as $tp) { foreach ($options['suggested'] as $tp) {
$view->vars['suggested'][] = $this->normalizer->normalize($tp, 'json', ['groups' => 'read']); $view->vars['suggested'][] = $this->normalizer->normalize($tp, 'json', ['groups' => 'read']);
@ -53,7 +55,11 @@ class PickThirdpartyDynamicType extends AbstractType
->setDefault('multiple', false) ->setDefault('multiple', false)
->setAllowedTypes('multiple', ['bool']) ->setAllowedTypes('multiple', ['bool'])
->setDefault('compound', false) ->setDefault('compound', false)
->setDefault('suggested', []); ->setDefault('suggested', [])
->setDefault('as_id', false)
->setAllowedTypes('as_id', ['bool'])
->setDefault('submit_on_adding_new_entity', false)
->setAllowedTypes('submit_on_adding_new_entity', ['bool']);
} }
public function getBlockPrefix() public function getBlockPrefix()

View File

@ -44,5 +44,6 @@ return [
ChampsLibres\WopiBundle\WopiBundle::class => ['all' => true], ChampsLibres\WopiBundle\WopiBundle::class => ['all' => true],
Chill\WopiBundle\ChillWopiBundle::class => ['all' => true], Chill\WopiBundle\ChillWopiBundle::class => ['all' => true],
\Lexik\Bundle\JWTAuthenticationBundle\LexikJWTAuthenticationBundle::class => ['all' => true], \Lexik\Bundle\JWTAuthenticationBundle\LexikJWTAuthenticationBundle::class => ['all' => true],
Chill\EventBundle\ChillEventBundle::class => ['all' => true],
]; ];