diff --git a/src/Bundle/ChillEventBundle/Controller/EventController.php b/src/Bundle/ChillEventBundle/Controller/EventController.php index 03069eaad..9d4352a02 100644 --- a/src/Bundle/ChillEventBundle/Controller/EventController.php +++ b/src/Bundle/ChillEventBundle/Controller/EventController.php @@ -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, diff --git a/src/Bundle/ChillEventBundle/Controller/EventListController.php b/src/Bundle/ChillEventBundle/Controller/EventListController.php new file mode 100644 index 000000000..ffdc9c804 --- /dev/null +++ b/src/Bundle/ChillEventBundle/Controller/EventListController.php @@ -0,0 +1,112 @@ +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(); + } +} diff --git a/src/Bundle/ChillEventBundle/DependencyInjection/ChillEventExtension.php b/src/Bundle/ChillEventBundle/DependencyInjection/ChillEventExtension.php index 8d35454ec..8ddcab58c 100644 --- a/src/Bundle/ChillEventBundle/DependencyInjection/ChillEventExtension.php +++ b/src/Bundle/ChillEventBundle/DependencyInjection/ChillEventExtension.php @@ -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'); diff --git a/src/Bundle/ChillEventBundle/Form/Type/PickEventType.php b/src/Bundle/ChillEventBundle/Form/Type/PickEventType.php index 487c0c3b2..f3c712801 100644 --- a/src/Bundle/ChillEventBundle/Form/Type/PickEventType.php +++ b/src/Bundle/ChillEventBundle/Form/Type/PickEventType.php @@ -114,7 +114,7 @@ final class PickEventType extends AbstractType } else { $centers = $this->authorizationHelper->getReachableCenters( $user, - (string) $options['role']->getRole() + $options['role'] ); } diff --git a/src/Bundle/ChillEventBundle/Menu/SectionMenuBuilder.php b/src/Bundle/ChillEventBundle/Menu/SectionMenuBuilder.php new file mode 100644 index 000000000..341848e42 --- /dev/null +++ b/src/Bundle/ChillEventBundle/Menu/SectionMenuBuilder.php @@ -0,0 +1,45 @@ +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']; + } +} diff --git a/src/Bundle/ChillEventBundle/Repository/EventACLAwareRepository.php b/src/Bundle/ChillEventBundle/Repository/EventACLAwareRepository.php new file mode 100644 index 000000000..7b9b027f9 --- /dev/null +++ b/src/Bundle/ChillEventBundle/Repository/EventACLAwareRepository.php @@ -0,0 +1,141 @@ +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; + } +} diff --git a/src/Bundle/ChillEventBundle/Repository/EventACLAwareRepositoryInterface.php b/src/Bundle/ChillEventBundle/Repository/EventACLAwareRepositoryInterface.php new file mode 100644 index 000000000..3c1da7c0a --- /dev/null +++ b/src/Bundle/ChillEventBundle/Repository/EventACLAwareRepositoryInterface.php @@ -0,0 +1,30 @@ +} $filters + */ + public function countAllViewable(array $filters): int; + + /** + * @param array{q?: string, dates?: array{from?: \DateTimeImmutable|null, to?: \DateTimeImmutable|null}, event_types?: list} $filters + * + * @return list + */ + public function findAllViewable(array $filters, int $offset = 0, int $limit = 50): array; +} diff --git a/src/Bundle/ChillEventBundle/Repository/EventTypeRepository.php b/src/Bundle/ChillEventBundle/Repository/EventTypeRepository.php new file mode 100644 index 000000000..28e5672ea --- /dev/null +++ b/src/Bundle/ChillEventBundle/Repository/EventTypeRepository.php @@ -0,0 +1,44 @@ + + */ +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 + */ + 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(); + } +} diff --git a/src/Bundle/ChillEventBundle/Resources/views/Event/page_list.html.twig b/src/Bundle/ChillEventBundle/Resources/views/Event/page_list.html.twig new file mode 100644 index 000000000..24b7c08a8 --- /dev/null +++ b/src/Bundle/ChillEventBundle/Resources/views/Event/page_list.html.twig @@ -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 %} +

{{ block('title') }}

+ + {{ filter|chill_render_filter_order_helper }} + +{# {% if is_granted('CHILL_EVENT_CREATE') %} #} + + {# {% endif %} #} + {% if events|length > 0 %} +
+ {% for e in events %} +
+
+
+
+ {{ e.name }} +
+

{{ e.type.name|localize_translatable_string }}

+ {% if e.moderator is not null %} +

{{ 'Moderator'|trans }}: {{ e.moderator|chill_entity_render_box }}

+ {% endif %} +
+
+
+

{{ e.date|format_datetime('medium', 'medium') }}

+

{{ 'count participations to this event'|trans({'count': e.participations|length}) }}

+
+
+
+ {% if e.participations|length > 0 %} +
+ {{ 'Participations'|trans }} : + {% 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 %} +
+ {% endif %} + {# +
+
+ {{ form_start(eventForms[e.id]) }} + {{ form_widget(eventForms[e.id].person_id) }} +
    +
  • +
+ {{ form_end(eventForms[e.id]) }} +
+
+ #} +
+
+
+
+
    + {% if is_granted('CHILL_EVENT_UPDATE', e) %} +
  • + {% endif %} + {% if is_granted('CHILL_EVENT_UPDATE', e) %} +
  • + {% endif %} +
  • +
+
+
+
+ {% endfor %} +
+ {% endif %} + + {{ chill_pagination(pagination) }} + +{% endblock %} diff --git a/src/Bundle/ChillEventBundle/Tests/Controller/EventListControllerTest.php b/src/Bundle/ChillEventBundle/Tests/Controller/EventListControllerTest.php new file mode 100644 index 000000000..a7814dd84 --- /dev/null +++ b/src/Bundle/ChillEventBundle/Tests/Controller/EventListControllerTest.php @@ -0,0 +1,42 @@ +getClientAuthenticated(); + + $client->request('GET', '/fr/event/event/list'); + self::assertResponseIsSuccessful(); + } +} diff --git a/src/Bundle/ChillEventBundle/Tests/Controller/ParticipationControllerTest.php b/src/Bundle/ChillEventBundle/Tests/Controller/ParticipationControllerTest.php index 6532f76ac..dbc9d574b 100644 --- a/src/Bundle/ChillEventBundle/Tests/Controller/ParticipationControllerTest.php +++ b/src/Bundle/ChillEventBundle/Tests/Controller/ParticipationControllerTest.php @@ -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; - } } diff --git a/src/Bundle/ChillEventBundle/Tests/Repository/EventACLAwareRepositoryTest.php b/src/Bundle/ChillEventBundle/Tests/Repository/EventACLAwareRepositoryTest.php new file mode 100644 index 000000000..f489f6cd4 --- /dev/null +++ b/src/Bundle/ChillEventBundle/Tests/Repository/EventACLAwareRepositoryTest.php @@ -0,0 +1,97 @@ +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() + ); + } +} diff --git a/src/Bundle/ChillEventBundle/config/services/menu.yaml b/src/Bundle/ChillEventBundle/config/services/menu.yaml deleted file mode 100644 index 6f49c02f0..000000000 --- a/src/Bundle/ChillEventBundle/config/services/menu.yaml +++ /dev/null @@ -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' } \ No newline at end of file diff --git a/src/Bundle/ChillEventBundle/translations/messages+int-icu.fr.yml b/src/Bundle/ChillEventBundle/translations/messages+intl-icu.fr.yml similarity index 69% rename from src/Bundle/ChillEventBundle/translations/messages+int-icu.fr.yml rename to src/Bundle/ChillEventBundle/translations/messages+intl-icu.fr.yml index 1dac8969e..5c2a5b7c9 100644 --- a/src/Bundle/ChillEventBundle/translations/messages+int-icu.fr.yml +++ b/src/Bundle/ChillEventBundle/translations/messages+intl-icu.fr.yml @@ -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} + } diff --git a/src/Bundle/ChillEventBundle/translations/messages.fr.yml b/src/Bundle/ChillEventBundle/translations/messages.fr.yml index 9be54f11d..0e87e30ad 100644 --- a/src/Bundle/ChillEventBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillEventBundle/translations/messages.fr.yml @@ -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 diff --git a/src/Bundle/ChillMainBundle/Test/DummyPaginator.php b/src/Bundle/ChillMainBundle/Test/DummyPaginator.php index 4457868ef..7e1442ce3 100644 --- a/src/Bundle/ChillMainBundle/Test/DummyPaginator.php +++ b/src/Bundle/ChillMainBundle/Test/DummyPaginator.php @@ -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