Create a page which list events

This commit is contained in:
Julien Fastré 2023-11-27 20:30:50 +01:00
parent d8bf6a195f
commit e902b6d409
Signed by: julienfastre
GPG Key ID: BDE2190974723FCB
16 changed files with 716 additions and 125 deletions

View File

@ -251,7 +251,7 @@ final class EventController extends AbstractController
}
$form = $this->formFactoryInterface
->createNamedBuilder(null, FormType::class, null, [
->createNamedBuilder('', FormType::class, null, [
'csrf_protection' => false,
])
->setMethod('GET')
@ -354,7 +354,7 @@ final class EventController extends AbstractController
{
/** @var \Symfony\Component\Form\FormBuilderInterface $builder */
$builder = $this
->get('form.factory')
->formFactoryInterface
->createNamedBuilder(
null,
FormType::class,

View File

@ -0,0 +1,112 @@
<?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]);
$builder->add('event_id', HiddenType::class, [
'data' => $event->getId(),
]);
return $builder->getForm();
}
}

View File

@ -36,7 +36,6 @@ class ChillEventExtension extends Extension implements PrependExtensionInterface
$loader->load('services/authorization.yaml');
$loader->load('services/fixtures.yaml');
$loader->load('services/forms.yaml');
$loader->load('services/menu.yaml');
$loader->load('services/repositories.yaml');
$loader->load('services/search.yaml');
$loader->load('services/timeline.yaml');

View File

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

View File

@ -0,0 +1,45 @@
<?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,141 @@
<?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

@ -0,0 +1,97 @@
{% 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) }}
<ul class="record_actions">
<li><button type="submit" class="btn btn-create"></button></li>
</ul>
{{ 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

@ -0,0 +1,42 @@
<?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;
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 function count;
@ -23,15 +28,12 @@ use function count;
*/
final class ParticipationControllerTest extends WebTestCase
{
/**
* @var \Symfony\Component\BrowserKit\AbstractBrowser
*/
protected $client;
use PersonRandomHelper;
use PrepareClientTrait;
/**
* @var \Doctrine\ORM\EntityManagerInterface
*/
protected $em;
private EntityManagerInterface $em;
private EventRepository $eventRepository;
/**
* Keep a cache for each person id given by the function getRandomPerson.
@ -44,23 +46,21 @@ final class ParticipationControllerTest extends WebTestCase
*/
private array $personsIdsCache = [];
protected function setUp(): void
protected function prepareDI(): void
{
self::bootKernel();
$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->em = self::$container->get(EntityManagerInterface::class);
$this->eventRepository = self::$container->get(EventRepository::class);
$this->personsIdsCache = [];
}
protected function tearDown(): void
{
parent::tearDown();
self::ensureKernelShutdown();
}
/**
* This method test participation creation with wrong parameters.
*
@ -68,11 +68,13 @@ final class ParticipationControllerTest extends WebTestCase
*/
public function testCreateActionWrongParameters()
{
$client = $this->getClientAuthenticated();
$this->prepareDI();
$event = $this->getRandomEvent();
$person = $this->getRandomPerson();
$person = $this->getRandomPerson($this->em);
// missing person_id or persons_ids
$this->client->request(
$client->request(
'GET',
'/fr/event/participation/create',
[
@ -81,33 +83,33 @@ final class ParticipationControllerTest extends WebTestCase
);
$this->assertEquals(
400,
$this->client->getResponse()->getStatusCode(),
$client->getResponse()->getStatusCode(),
'Test that /fr/event/participation/create fail if '
.'both person_id and persons_ids are missing'
);
// having both person_id and persons_ids
$this->client->request(
$client->request(
'GET',
'/fr/event/participation/create',
[
'event_id' => $event->getId(),
'persons_ids' => implode(',', [
$this->getRandomPerson()->getId(),
$this->getRandomPerson()->getId(),
$this->getRandomPerson($this->em)->getId(),
$this->getRandomPerson($this->em)->getId(),
]),
'person_id' => $person->getId(),
]
);
$this->assertEquals(
400,
$this->client->getResponse()->getStatusCode(),
$client->getResponse()->getStatusCode(),
'test that /fr/event/participation/create fail if both person_id and '
.'persons_ids are set'
);
// missing event_id
$this->client->request(
$client->request(
'GET',
'/fr/event/participation/create',
[
@ -116,12 +118,12 @@ final class ParticipationControllerTest extends WebTestCase
);
$this->assertEquals(
400,
$this->client->getResponse()->getStatusCode(),
$client->getResponse()->getStatusCode(),
'Test that /fr/event/participation/create fails if event_id is missing'
);
// persons_ids with wrong content
$this->client->request(
$client->request(
'GET',
'/fr/event/participation/create',
[
@ -131,42 +133,47 @@ final class ParticipationControllerTest extends WebTestCase
);
$this->assertEquals(
400,
$this->client->getResponse()->getStatusCode(),
$client->getResponse()->getStatusCode(),
'Test that /fr/event/participation/create fails if persons_ids has wrong content'
);
}
public function testEditMultipleAction()
{
$client = $this->getClientAuthenticated();
$this->prepareDI();
/** @var \Chill\EventBundle\Entity\Event $event */
$event = $this->getRandomEventWithMultipleParticipations();
$crawler = $this->client->request('GET', '/fr/event/participation/'.$event->getId().
$crawler = $client->request('GET', '/fr/event/participation/'.$event->getId().
'/edit_multiple');
$this->assertEquals(200, $this->client->getResponse()->getStatusCode());
$this->assertEquals(200, $client->getResponse()->getStatusCode());
$button = $crawler->selectButton('Mettre à jour');
$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][status]' => $event->getType()->getStatuses()->first()->getId(),
'form[participations][1][role]' => $event->getType()->getRoles()->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'));
}
public function testNewActionWrongParameters()
{
$client = $this->getClientAuthenticated();
$this->prepareDI();
$event = $this->getRandomEvent();
$person = $this->getRandomPerson();
$person = $this->getRandomPerson($this->em);
// missing person_id or persons_ids
$this->client->request(
$client->request(
'GET',
'/fr/event/participation/new',
[
@ -175,33 +182,33 @@ final class ParticipationControllerTest extends WebTestCase
);
$this->assertEquals(
400,
$this->client->getResponse()->getStatusCode(),
$client->getResponse()->getStatusCode(),
'Test that /fr/event/participation/new fail if '
.'both person_id and persons_ids are missing'
);
// having both person_id and persons_ids
$this->client->request(
$client->request(
'GET',
'/fr/event/participation/new',
[
'event_id' => $event->getId(),
'persons_ids' => implode(',', [
$this->getRandomPerson()->getId(),
$this->getRandomPerson()->getId(),
$this->getRandomPerson($this->em)->getId(),
$this->getRandomPerson($this->em)->getId(),
]),
'person_id' => $person->getId(),
]
);
$this->assertEquals(
400,
$this->client->getResponse()->getStatusCode(),
$client->getResponse()->getStatusCode(),
'test that /fr/event/participation/new fail if both person_id and '
.'persons_ids are set'
);
// missing event_id
$this->client->request(
$client->request(
'GET',
'/fr/event/participation/new',
[
@ -210,12 +217,12 @@ final class ParticipationControllerTest extends WebTestCase
);
$this->assertEquals(
400,
$this->client->getResponse()->getStatusCode(),
$client->getResponse()->getStatusCode(),
'Test that /fr/event/participation/new fails if event_id is missing'
);
// persons_ids with wrong content
$this->client->request(
$client->request(
'GET',
'/fr/event/participation/new',
[
@ -225,13 +232,15 @@ final class ParticipationControllerTest extends WebTestCase
);
$this->assertEquals(
400,
$this->client->getResponse()->getStatusCode(),
$client->getResponse()->getStatusCode(),
'Test that /fr/event/participation/new fails if persons_ids has wrong content'
);
}
public function testNewMultipleAction()
{
$client = $this->getClientAuthenticated();
$this->prepareDI();
$event = $this->getRandomEvent();
// record the number of participation for the event (used later in this test)
$nbParticipations = $event->getParticipations()->count();
@ -244,10 +253,10 @@ final class ParticipationControllerTest extends WebTestCase
->toArray()
);
// get some random people
$person1 = $this->getRandomPerson();
$person2 = $this->getRandomPerson();
$person1 = $this->getRandomPerson($this->em);
$person2 = $this->getRandomPerson($this->em);
$crawler = $this->client->request(
$crawler = $client->request(
'GET',
'/fr/event/participation/new',
[
@ -258,7 +267,7 @@ final class ParticipationControllerTest extends WebTestCase
$this->assertEquals(
200,
$this->client->getResponse()->getStatusCode(),
$client->getResponse()->getStatusCode(),
'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->client->submit($button->form(), [
$client->submit($button->form(), [
'form' => [
'participations' => [
0 => [
@ -281,8 +290,8 @@ final class ParticipationControllerTest extends WebTestCase
],
]);
$this->assertTrue($this->client->getResponse()->isRedirect());
$crawler = $this->client->followRedirect();
$this->assertTrue($client->getResponse()->isRedirect());
$crawler = $client->followRedirect();
$span1 = $crawler->filter('table td span.entity-person a:contains("'
.$person1->getFirstName().'"):contains("'.$person1->getLastname().'")');
@ -300,13 +309,15 @@ final class ParticipationControllerTest extends WebTestCase
public function testNewMultipleWithAllPeopleParticipating()
{
$client = $this->getClientAuthenticated();
$this->prepareDI();
$event = $this->getRandomEventWithMultipleParticipations();
$persons_id = implode(',', $event->getParticipations()->map(
static fn ($p) => $p->getPerson()->getId()
)->toArray());
$crawler = $this->client->request(
$crawler = $client->request(
'GET',
'/fr/event/participation/new',
[
@ -317,13 +328,15 @@ final class ParticipationControllerTest extends WebTestCase
$this->assertEquals(
302,
$this->client->getResponse()->getStatusCode(),
$client->getResponse()->getStatusCode(),
'test that /fr/event/participation/new is redirecting'
);
}
public function testNewMultipleWithSomePeopleParticipating()
{
$client = $this->getClientAuthenticated();
$this->prepareDI();
$event = $this->getRandomEventWithMultipleParticipations();
// record the number of participation for the event (used later in this test)
$nbParticipations = $event->getParticipations()->count();
@ -335,12 +348,12 @@ final class ParticipationControllerTest extends WebTestCase
$this->personsIdsCache = array_merge($this->personsIdsCache, $persons_id);
// get a random person
$newPerson = $this->getRandomPerson();
$newPerson = $this->getRandomPerson($this->em);
// build the `persons_ids` parameter
$persons_ids_string = implode(',', [...$persons_id, $newPerson->getId()]);
$crawler = $this->client->request(
$crawler = $client->request(
'GET',
'/fr/event/participation/new',
[
@ -351,7 +364,7 @@ final class ParticipationControllerTest extends WebTestCase
$this->assertEquals(
200,
$this->client->getResponse()->getStatusCode(),
$client->getResponse()->getStatusCode(),
'test that /fr/event/participation/new is successful'
);
@ -377,12 +390,12 @@ final class ParticipationControllerTest extends WebTestCase
$this->assertNotNull($button, "test the form with button 'Créer' exists");
// submit the form
$this->client->submit($button->form(), [
$client->submit($button->form(), [
'participation[role]' => $event->getType()->getRoles()->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
$event = $this->em->getRepository(\Chill\EventBundle\Entity\Event::class)
@ -398,12 +411,14 @@ final class ParticipationControllerTest extends WebTestCase
public function testNewSingleAction()
{
$client = $this->getClientAuthenticated();
$this->prepareDI();
$event = $this->getRandomEvent();
// record the number of participation for the event
$nbParticipations = $event->getParticipations()->count();
$person = $this->getRandomPerson();
$person = $this->getRandomPerson($this->em);
$crawler = $this->client->request(
$crawler = $client->request(
'GET',
'/fr/event/participation/new',
[
@ -414,7 +429,7 @@ final class ParticipationControllerTest extends WebTestCase
$this->assertEquals(
200,
$this->client->getResponse()->getStatusCode(),
$client->getResponse()->getStatusCode(),
'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->client->submit($button->form(), [
$client->submit($button->form(), [
'participation[role]' => $event->getType()->getRoles()->first()->getId(),
'participation[status]' => $event->getType()->getStatuses()->first()->getId(),
]);
$this->assertTrue($this->client->getResponse()->isRedirect());
$crawler = $this->client->followRedirect();
$this->assertTrue($client->getResponse()->isRedirect());
$crawler = $client->followRedirect();
$span = $crawler->filter('table td span.entity-person a:contains("'
.$person->getFirstName().'"):contains("'.$person->getLastname().'")');
@ -442,23 +457,17 @@ final class ParticipationControllerTest extends WebTestCase
$this->assertEquals($nbParticipations + 1, $event->getParticipations()->count());
}
/**
* @return \Chill\EventBundle\Entity\Event
*/
protected function getRandomEvent(mixed $centerName = 'Center A', mixed $circleName = 'social')
private function getRandomEvent(string $centerName = 'Center A', string $circleName = 'social'): Event
{
$center = $this->em->getRepository(\Chill\MainBundle\Entity\Center::class)
->findByName($centerName);
$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';
$circles = $this->em->getRepository(\Chill\MainBundle\Entity\Scope::class)
->findAll();
array_filter($circles, static fn ($circle) => \in_array($circleName, $circle->getName(), true));
$circle = $circles[0];
$ids = $this->em->createQuery(
'SELECT DISTINCT e.id '.$dql
)
->setParameters(['cname' => $centerName, 'sname' => $circleName])
->getResult();
$events = $this->em->getRepository(\Chill\EventBundle\Entity\Event::class)
->findBy(['center' => $center, 'circle' => $circle]);
return $events[array_rand($events)];
return $this->eventRepository->find($ids[array_rand($ids)]['id']);
}
/**
@ -479,35 +488,4 @@ final class ParticipationControllerTest extends WebTestCase
$event :
$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,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

@ -11,3 +11,11 @@ count participations to this event: >-
one {Un participant à 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

@ -107,3 +107,8 @@ csv: csv
Create a new role: Créer un nouveau rôle
Create a new type: Créer un nouveau type
Create a new status: Créer un nouveau statut
event:
filter:
event_types: Par types d'événement
event_dates: Par date d'événement

View File

@ -20,9 +20,9 @@ use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
class DummyPaginator implements PaginatorFactoryInterface
{
public function __construct(
private UrlGeneratorInterface $urlGenerator,
private string $route,
private array $routeParameters = []
private readonly UrlGeneratorInterface $urlGenerator,
private readonly string $route,
private readonly array $routeParameters = []
) {}
public function create(int $totalItems, string $route = null, array $routeParameters = null): PaginatorInterface