From d347f6ae60829abfa2e5464110bf9646d3a3cd2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 27 Nov 2023 11:51:56 +0100 Subject: [PATCH 01/17] Fix migrations to take into account the change in table name for Person's entity --- .../migrations/Version20160318111334.php | 29 +++++++++++++++---- .../migrations/Version20190110140538.php | 10 +++++-- .../migrations/Version20190115140042.php | 12 +++++--- .../migrations/Version20190201143121.php | 12 +++++--- 4 files changed, 48 insertions(+), 15 deletions(-) diff --git a/src/Bundle/ChillEventBundle/migrations/Version20160318111334.php b/src/Bundle/ChillEventBundle/migrations/Version20160318111334.php index 4689fe413..908f2bfea 100644 --- a/src/Bundle/ChillEventBundle/migrations/Version20160318111334.php +++ b/src/Bundle/ChillEventBundle/migrations/Version20160318111334.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\Migrations\Event; +use Doctrine\DBAL\Platforms\PostgreSQLPlatform; use Doctrine\DBAL\Schema\Schema; use Doctrine\Migrations\AbstractMigration; @@ -19,10 +20,14 @@ use Doctrine\Migrations\AbstractMigration; */ class Version20160318111334 extends AbstractMigration { + public function getDescription(): string + { + return 'initialize the bundle chill event'; + } + 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->abortIf($this->connection->getDatabasePlatform() instanceof PostgreSQLPlatform, '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_status DROP CONSTRAINT FK_A6CC85D0C54C8C93'); @@ -50,8 +55,7 @@ class Version20160318111334 extends AbstractMigration 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->abortIf($this->connection->getDatabasePlatform() instanceof PostgreSQLPlatform, '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_role_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); @@ -123,11 +127,26 @@ class Version20160318111334 extends AbstractMigration .'FOREIGN KEY (event_id) ' .'REFERENCES chill_event_event (id) ' .'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 ' .'FOREIGN KEY (person_id) ' .'REFERENCES Person (id) ' .'NOT DEFERRABLE INITIALLY IMMEDIATE'); + } + $this->addSql('ALTER TABLE chill_event_participation ' .'ADD CONSTRAINT FK_4E7768ACD60322AC ' .'FOREIGN KEY (role_id) ' diff --git a/src/Bundle/ChillEventBundle/migrations/Version20190110140538.php b/src/Bundle/ChillEventBundle/migrations/Version20190110140538.php index bc004119e..d83e1b397 100644 --- a/src/Bundle/ChillEventBundle/migrations/Version20190110140538.php +++ b/src/Bundle/ChillEventBundle/migrations/Version20190110140538.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\Migrations\Event; +use Doctrine\DBAL\Platforms\PostgreSQLPlatform; use Doctrine\DBAL\Schema\Schema; use Doctrine\Migrations\AbstractMigration; @@ -19,9 +20,14 @@ use Doctrine\Migrations\AbstractMigration; */ final class Version20190110140538 extends AbstractMigration { + public function getDescription(): string + { + return 'switch event date to datetime'; + } + public function down(Schema $schema): void { - $this->abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.'); + $this->abortIf($this->connection->getDatabasePlatform() instanceof PostgreSQLPlatform, '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 DROP DEFAULT'); @@ -29,7 +35,7 @@ final class Version20190110140538 extends AbstractMigration public function up(Schema $schema): void { - $this->abortIf('postgresql' !== $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'postgresql\'.'); + $this->abortIf($this->connection->getDatabasePlatform() instanceof PostgreSQLPlatform, '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 DROP DEFAULT'); diff --git a/src/Bundle/ChillEventBundle/migrations/Version20190115140042.php b/src/Bundle/ChillEventBundle/migrations/Version20190115140042.php index c0ced1044..4ef21a834 100644 --- a/src/Bundle/ChillEventBundle/migrations/Version20190115140042.php +++ b/src/Bundle/ChillEventBundle/migrations/Version20190115140042.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\Migrations\Event; +use Doctrine\DBAL\Platforms\PostgreSQLPlatform; use Doctrine\DBAL\Schema\Schema; use Doctrine\Migrations\AbstractMigration; @@ -19,10 +20,14 @@ use Doctrine\Migrations\AbstractMigration; */ final class Version20190115140042 extends AbstractMigration { + public function getDescription(): string + { + return 'add a moderator field to events'; + } + 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->abortIf($this->connection->getDatabasePlatform() instanceof PostgreSQLPlatform, 'Migration can only be executed safely on \'postgresql\'.'); $this->addSql('ALTER TABLE chill_event_event DROP CONSTRAINT FK_FA320FC8D0AFA354'); $this->addSql('DROP INDEX IDX_FA320FC8D0AFA354'); @@ -31,8 +36,7 @@ final class Version20190115140042 extends AbstractMigration 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->abortIf($this->connection->getDatabasePlatform() instanceof PostgreSQLPlatform, '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 CONSTRAINT FK_FA320FC8D0AFA354 FOREIGN KEY (moderator_id) REFERENCES chill_person_person (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); diff --git a/src/Bundle/ChillEventBundle/migrations/Version20190201143121.php b/src/Bundle/ChillEventBundle/migrations/Version20190201143121.php index f9d64da75..cc8445f95 100644 --- a/src/Bundle/ChillEventBundle/migrations/Version20190201143121.php +++ b/src/Bundle/ChillEventBundle/migrations/Version20190201143121.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\Migrations\Event; +use Doctrine\DBAL\Platforms\PostgreSQLPlatform; use Doctrine\DBAL\Schema\Schema; use Doctrine\Migrations\AbstractMigration; @@ -19,10 +20,14 @@ use Doctrine\Migrations\AbstractMigration; */ final class Version20190201143121 extends AbstractMigration { + public function getDescription(): string + { + return 'fix moderator: relation with user (not person)'; + } + 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->abortIf($this->connection->getDatabasePlatform() instanceof PostgreSQLPlatform, '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 ADD CONSTRAINT fk_fa320fc8d0afa354 FOREIGN KEY (moderator_id) REFERENCES chill_person_person (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); @@ -30,8 +35,7 @@ final class Version20190201143121 extends AbstractMigration 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->abortIf($this->connection->getDatabasePlatform() instanceof PostgreSQLPlatform, '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 ADD CONSTRAINT FK_FA320FC8D0AFA354 FOREIGN KEY (moderator_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); From 1a9af6b0b18c3ecbc4c801a553be30a2a591d3cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 27 Nov 2023 13:19:28 +0100 Subject: [PATCH 02/17] activate Event Bundle in test app --- tests/app/config/bundles.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/app/config/bundles.php b/tests/app/config/bundles.php index 36e3bcf03..cf7ccd2da 100644 --- a/tests/app/config/bundles.php +++ b/tests/app/config/bundles.php @@ -44,5 +44,6 @@ return [ ChampsLibres\WopiBundle\WopiBundle::class => ['all' => true], Chill\WopiBundle\ChillWopiBundle::class => ['all' => true], \Lexik\Bundle\JWTAuthenticationBundle\LexikJWTAuthenticationBundle::class => ['all' => true], + Chill\EventBundle\ChillEventBundle::class => ['all' => true], ]; From f11f7498d71c49a0d248173a4b01fcd58a57bbed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 27 Nov 2023 13:23:36 +0100 Subject: [PATCH 03/17] Add new option "as_id" to Pick*DynamicType This option will make the app return a single id of the entity in data, and not the entity json. --- .../Form/Type/PickUserDynamicType.php | 6 +++++- .../public/module/pick-entity/index.js | 19 +++++++++++++++---- .../Resources/views/Form/fields.html.twig | 3 ++- .../Form/Type/PickPersonDynamicType.php | 5 ++++- .../Form/Type/PickThirdpartyDynamicType.php | 5 ++++- 5 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/Bundle/ChillMainBundle/Form/Type/PickUserDynamicType.php b/src/Bundle/ChillMainBundle/Form/Type/PickUserDynamicType.php index a12042dab..5272b4aad 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/PickUserDynamicType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/PickUserDynamicType.php @@ -42,6 +42,7 @@ class PickUserDynamicType extends AbstractType $view->vars['types'] = ['user']; $view->vars['uniqid'] = uniqid('pick_user_dyn'); $view->vars['suggested'] = []; + $view->vars['as_id'] = true === $options['as_id'] ? '1' : '0'; foreach ($options['suggested'] as $user) { $view->vars['suggested'][] = $this->normalizer->normalize($user, 'json', ['groups' => 'read']); @@ -54,7 +55,10 @@ class PickUserDynamicType extends AbstractType ->setDefault('multiple', false) ->setAllowedTypes('multiple', ['bool']) ->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']); } public function getBlockPrefix() diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/pick-entity/index.js b/src/Bundle/ChillMainBundle/Resources/public/module/pick-entity/index.js index 99f83e1e3..6e939734e 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/module/pick-entity/index.js +++ b/src/Bundle/ChillMainBundle/Resources/public/module/pick-entity/index.js @@ -24,7 +24,8 @@ function loadDynamicPicker(element) { (input.value === '[]' || 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; if (!isMultiple) { if (input.value === '[]'){ @@ -50,7 +51,8 @@ function loadDynamicPicker(element) { types: JSON.parse(el.dataset.types), picked: picked === null ? [] : picked, uniqid: el.dataset.uniqid, - suggested: suggested + suggested: suggested, + as_id: as_id, } }, computed: { @@ -69,7 +71,12 @@ function loadDynamicPicker(element) { return el.type === entity.type && el.id === entity.id; })) { 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) } } else { @@ -78,7 +85,11 @@ function loadDynamicPicker(element) { })) { this.picked.splice(0, this.picked.length); 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); + } } } }, diff --git a/src/Bundle/ChillMainBundle/Resources/views/Form/fields.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Form/fields.html.twig index 28c55c9c1..1ff92fd5f 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Form/fields.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Form/fields.html.twig @@ -256,7 +256,8 @@ data-types="{{ form.vars['types']|json_encode }}" data-multiple="{{ form.vars['multiple'] }}" data-uniqid="{{ form.vars['uniqid'] }}" - data-suggested="{{ form.vars['suggested']|json_encode|escape('html_attr') }}"> + data-suggested="{{ form.vars['suggested']|json_encode|escape('html_attr') }}" + data-as-id="{{ form.vars['as_id'] }}"> {% endblock %} {% block pick_postal_code_widget %} diff --git a/src/Bundle/ChillPersonBundle/Form/Type/PickPersonDynamicType.php b/src/Bundle/ChillPersonBundle/Form/Type/PickPersonDynamicType.php index 440b6568c..94c625a38 100644 --- a/src/Bundle/ChillPersonBundle/Form/Type/PickPersonDynamicType.php +++ b/src/Bundle/ChillPersonBundle/Form/Type/PickPersonDynamicType.php @@ -41,6 +41,7 @@ class PickPersonDynamicType extends AbstractType $view->vars['types'] = ['person']; $view->vars['uniqid'] = uniqid('pick_user_dyn'); $view->vars['suggested'] = []; + $view->vars['as_id'] = true === $options['as_id'] ? '1' : '0'; foreach ($options['suggested'] as $person) { $view->vars['suggested'][] = $this->normalizer->normalize($person, 'json', ['groups' => 'read']); @@ -53,7 +54,9 @@ class PickPersonDynamicType extends AbstractType ->setDefault('multiple', false) ->setAllowedTypes('multiple', ['bool']) ->setDefault('compound', false) - ->setDefault('suggested', []); + ->setDefault('suggested', []) + ->setDefault('as_id', false) + ->setAllowedTypes('as_id', ['bool']); } public function getBlockPrefix() diff --git a/src/Bundle/ChillThirdPartyBundle/Form/Type/PickThirdpartyDynamicType.php b/src/Bundle/ChillThirdPartyBundle/Form/Type/PickThirdpartyDynamicType.php index 9022fb6dc..de233c360 100644 --- a/src/Bundle/ChillThirdPartyBundle/Form/Type/PickThirdpartyDynamicType.php +++ b/src/Bundle/ChillThirdPartyBundle/Form/Type/PickThirdpartyDynamicType.php @@ -41,6 +41,7 @@ class PickThirdpartyDynamicType extends AbstractType $view->vars['types'] = ['thirdparty']; $view->vars['uniqid'] = uniqid('pick_user_dyn'); $view->vars['suggested'] = []; + $view->vars['as_id'] = true === $options['as_id'] ? '1' : '0'; foreach ($options['suggested'] as $tp) { $view->vars['suggested'][] = $this->normalizer->normalize($tp, 'json', ['groups' => 'read']); @@ -53,7 +54,9 @@ class PickThirdpartyDynamicType extends AbstractType ->setDefault('multiple', false) ->setAllowedTypes('multiple', ['bool']) ->setDefault('compound', false) - ->setDefault('suggested', []); + ->setDefault('suggested', []) + ->setDefault('as_id', false) + ->setAllowedTypes('as_id', ['bool']); } public function getBlockPrefix() From 930a76cc666087d195dda9f9216d4b104fb92af9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 27 Nov 2023 13:24:19 +0100 Subject: [PATCH 04/17] use a PickPersonDynamic type in event bundle --- .../Controller/EventController.php | 66 +++++-------------- .../Controller/ParticipationController.php | 52 +++++++-------- .../ChillEventExtension.php | 4 +- .../Resources/views/Event/show.html.twig | 10 +++ .../config/services/controller.yaml | 16 ----- 5 files changed, 54 insertions(+), 94 deletions(-) delete mode 100644 src/Bundle/ChillEventBundle/config/services/controller.yaml diff --git a/src/Bundle/ChillEventBundle/Controller/EventController.php b/src/Bundle/ChillEventBundle/Controller/EventController.php index e383c5432..03069eaad 100644 --- a/src/Bundle/ChillEventBundle/Controller/EventController.php +++ b/src/Bundle/ChillEventBundle/Controller/EventController.php @@ -18,9 +18,9 @@ use Chill\EventBundle\Form\Type\PickEventType; use Chill\EventBundle\Security\Authorization\EventVoter; use Chill\MainBundle\Entity\Center; 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\Form\Type\PickPersonType; +use Chill\PersonBundle\Form\Type\PickPersonDynamicType; use Chill\PersonBundle\Privacy\PrivacyEvent; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Writer\Csv; @@ -42,49 +42,18 @@ use Symfony\Contracts\Translation\TranslatorInterface; /** * 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. */ public function __construct( - EventDispatcherInterface $eventDispatcher, - AuthorizationHelper $authorizationHelper, - FormFactoryInterface $formFactoryInterface, - TranslatorInterface $translator, - PaginatorFactory $paginator - ) { - $this->eventDispatcher = $eventDispatcher; - $this->authorizationHelper = $authorizationHelper; - $this->formFactoryInterface = $formFactoryInterface; - $this->translator = $translator; - $this->paginator = $paginator; - } + private readonly EventDispatcherInterface $eventDispatcher, + private readonly AuthorizationHelperInterface $authorizationHelper, + private readonly FormFactoryInterface $formFactoryInterface, + private readonly TranslatorInterface $translator, + private readonly PaginatorFactory $paginator + ) {} /** * @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/event/event/{event_id}/delete", name="chill_event__event_delete", requirements={"event_id"="\d+"}, methods={"GET", "DELETE"}) @@ -181,7 +150,7 @@ class EventController extends AbstractController $this->denyAccessUnlessGranted('CHILL_PERSON_SEE', $person); - $reachablesCircles = $this->authorizationHelper->getReachableCircles( + $reachablesCircles = $this->authorizationHelper->getReachableScopes( $this->getUser(), EventVoter::SEE, $person->getCenter() @@ -323,7 +292,7 @@ class EventController extends AbstractController } $this->denyAccessUnlessGranted( - 'CHILL_EVENT_SEE_DETAILS', + EventVoter::SEE_DETAILS, $event, 'You are not allowed to see details on this event' ); @@ -430,11 +399,9 @@ class EventController extends AbstractController */ protected function createAddParticipationByPersonForm(Event $event) { - /** @var \Symfony\Component\Form\FormBuilderInterface $builder */ - $builder = $this - ->get('form.factory') + $builder = $this->formFactoryInterface ->createNamedBuilder( - null, + '', FormType::class, null, [ @@ -444,10 +411,7 @@ class EventController extends AbstractController ] ); - $builder->add('person_id', PickPersonType::class, [ - 'role' => 'CHILL_EVENT_CREATE', - 'centers' => $event->getCenter(), - ]); + $builder->add('person_id', PickPersonDynamicType::class, ['as_id' => true, 'multiple' => false]); $builder->add('event_id', HiddenType::class, [ 'data' => $event->getId(), @@ -469,7 +433,7 @@ class EventController extends AbstractController */ protected function createExportByFormatForm() { - $builder = $this->createFormBuilder() + $builder = $this->createFormBuilder(['format' => 'xlsx']) ->add('format', ChoiceType::class, [ 'choices' => [ 'xlsx' => 'xlsx', diff --git a/src/Bundle/ChillEventBundle/Controller/ParticipationController.php b/src/Bundle/ChillEventBundle/Controller/ParticipationController.php index 9785c20b0..ff6ad92df 100644 --- a/src/Bundle/ChillEventBundle/Controller/ParticipationController.php +++ b/src/Bundle/ChillEventBundle/Controller/ParticipationController.php @@ -14,7 +14,10 @@ namespace Chill\EventBundle\Controller; use Chill\EventBundle\Entity\Event; use Chill\EventBundle\Entity\Participation; use Chill\EventBundle\Form\ParticipationType; +use Chill\EventBundle\Repository\EventRepository; use Chill\EventBundle\Security\Authorization\ParticipationVoter; +use Chill\PersonBundle\Repository\PersonRepository; +use Chill\PersonBundle\Security\Authorization\PersonVoter; use Doctrine\Common\Collections\Collection; use Psr\Log\LoggerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; @@ -28,14 +31,17 @@ use Symfony\Contracts\Translation\TranslatorInterface; /** * Class ParticipationController. */ -class ParticipationController extends AbstractController +final class ParticipationController extends AbstractController { /** * 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, + ) {} /** * @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/event/participation/create", name="chill_event_participation_create") @@ -321,7 +327,7 @@ class ParticipationController extends AbstractController */ public function editMultipleAction($event_id): Response|\Symfony\Component\HttpFoundation\RedirectResponse { - $event = $this->getDoctrine()->getRepository(Event::class) + $event = $this->getDoctrine()->getRepository(\Chill\EventBundle\Entity\Event::class) ->find($event_id); if (null === $event) { @@ -539,7 +545,7 @@ class ParticipationController extends AbstractController * If the request is multiple, the $participation object is cloned. * Limitations: the $participation should not be persisted. * - * @return Participation|Participation[] return one single participation if $multiple == false + * @return Participation|list return one single participation if $multiple == false * * @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 @@ -556,30 +562,25 @@ class ParticipationController extends AbstractController } $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 = $em->getRepository(Event::class) - ->find($event_id); + $event = $this->eventRepository->find($event_id); - if (null === $event) { - 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); + if (null === $event) { + 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 script should be able to handle multiple, so we translate // single person_id in an array $persons_ids = $request->query->has('person_id') ? - [$request->query->getInt('person_id', 0)] // sf4 check: - // prevent error: `Argument 2 passed to ::getInt() must be of the type int, null given` + [$request->query->get('person_id', 0)] : explode(',', (string) $request->query->get('persons_ids')); $participations = []; @@ -588,15 +589,14 @@ class ParticipationController extends AbstractController $participation = \count($persons_ids) > 1 ? clone $participation : $participation; if (null !== $person_id) { - $person = $em->getRepository(\Chill\PersonBundle\Entity\Person::class) - ->find($person_id); + $person = $this->personRepository->find($person_id); if (null === $person) { throw $this->createNotFoundException('The person with id '.$person_id.' is not found'); } $this->denyAccessUnlessGranted( - 'CHILL_PERSON_SEE', + PersonVoter::SEE, $person, 'The user is not allowed to see the person' ); diff --git a/src/Bundle/ChillEventBundle/DependencyInjection/ChillEventExtension.php b/src/Bundle/ChillEventBundle/DependencyInjection/ChillEventExtension.php index 07c431f2f..8d35454ec 100644 --- a/src/Bundle/ChillEventBundle/DependencyInjection/ChillEventExtension.php +++ b/src/Bundle/ChillEventBundle/DependencyInjection/ChillEventExtension.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\EventBundle\DependencyInjection; use Chill\EventBundle\Security\Authorization\EventVoter; +use Chill\EventBundle\Security\Authorization\ParticipationVoter; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; @@ -33,7 +34,6 @@ class ChillEventExtension extends Extension implements PrependExtensionInterface $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../config')); $loader->load('services.yaml'); $loader->load('services/authorization.yaml'); - $loader->load('services/controller.yaml'); $loader->load('services/fixtures.yaml'); $loader->load('services/forms.yaml'); $loader->load('services/menu.yaml'); @@ -61,6 +61,8 @@ class ChillEventExtension extends Extension implements PrependExtensionInterface EventVoter::SEE_DETAILS => [EventVoter::SEE], EventVoter::UPDATE => [EventVoter::SEE_DETAILS], EventVoter::CREATE => [EventVoter::SEE_DETAILS], + ParticipationVoter::SEE_DETAILS => [ParticipationVoter::SEE], + ParticipationVoter::UPDATE => [ParticipationVoter::SEE_DETAILS], ], ]); } diff --git a/src/Bundle/ChillEventBundle/Resources/views/Event/show.html.twig b/src/Bundle/ChillEventBundle/Resources/views/Event/show.html.twig index f6b84cf68..b7e3b7d10 100644 --- a/src/Bundle/ChillEventBundle/Resources/views/Event/show.html.twig +++ b/src/Bundle/ChillEventBundle/Resources/views/Event/show.html.twig @@ -4,6 +4,16 @@ {% import '@ChillPerson/Person/macro.html.twig' as person_macro %} +{% block js %} + {{ parent() }} + {{ encore_entry_script_tags('mod_pickentity_type') }} +{% endblock %} + +{% block css %} + {{ parent() }} + {{ encore_entry_link_tags('mod_pickentity_type') }} +{% endblock %} + {% block event_content -%}

{{ 'Details of an event'|trans }}

diff --git a/src/Bundle/ChillEventBundle/config/services/controller.yaml b/src/Bundle/ChillEventBundle/config/services/controller.yaml deleted file mode 100644 index 0ba2fac8e..000000000 --- a/src/Bundle/ChillEventBundle/config/services/controller.yaml +++ /dev/null @@ -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'] From cef218fed5d87ec46e454675513b8d9814527278 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 27 Nov 2023 18:14:07 +0100 Subject: [PATCH 05/17] Add interface for pagination --- .../Pagination/PaginatorFactory.php | 31 ++++------ .../Pagination/PaginatorFactoryInterface.php | 35 ++++++++++++ .../ChillMainBundle/Test/DummyPaginator.php | 56 +++++++++++++++++++ .../config/services/pagination.yaml | 1 + 4 files changed, 103 insertions(+), 20 deletions(-) create mode 100644 src/Bundle/ChillMainBundle/Pagination/PaginatorFactoryInterface.php create mode 100644 src/Bundle/ChillMainBundle/Test/DummyPaginator.php diff --git a/src/Bundle/ChillMainBundle/Pagination/PaginatorFactory.php b/src/Bundle/ChillMainBundle/Pagination/PaginatorFactory.php index 0daad7283..f7829b332 100644 --- a/src/Bundle/ChillMainBundle/Pagination/PaginatorFactory.php +++ b/src/Bundle/ChillMainBundle/Pagination/PaginatorFactory.php @@ -17,7 +17,7 @@ use Symfony\Component\Routing\RouterInterface; /** * Create paginator instances. */ -class PaginatorFactory +final readonly class PaginatorFactory implements PaginatorFactoryInterface { final public const DEFAULT_CURRENT_PAGE_KEY = 'page'; @@ -25,23 +25,20 @@ class PaginatorFactory final public const DEFAULT_PAGE_NUMBER = 1; - /** - * @param int $itemPerPage - */ public function __construct( /** * the request stack. */ - private readonly RequestStack $requestStack, + private RequestStack $requestStack, /** * the router and generator for url. */ - private readonly RouterInterface $router, + private RouterInterface $router, /** * the default item per page. This may be overriden by * 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, * thos route are overriden. * - * @param int $totalItems * @param string|null $route the specific route to use in pages * @param array|null $routeParameters the specific route parameters to use in pages - * - * @return PaginatorInterface */ public function create( - $totalItems, + int $totalItems, ?string $route = null, ?array $routeParameters = null - ) { + ): PaginatorInterface { return new Paginator( $totalItems, $this->getCurrentItemsPerPage(), @@ -74,7 +68,7 @@ class PaginatorFactory ); } - public function getCurrentItemsPerPage() + public function getCurrentItemsPerPage(): int { return $this->requestStack ->getCurrentRequest() @@ -82,16 +76,13 @@ class PaginatorFactory ->getInt(self::DEFAULT_ITEM_PER_NUMBER_KEY, $this->itemPerPage); } - public function getCurrentPageFirstItemNumber() + public function getCurrentPageFirstItemNumber(): int { return ($this->getCurrentPageNumber() - 1) * $this->getCurrentItemsPerPage(); } - /** - * @return int - */ - public function getCurrentPageNumber() + public function getCurrentPageNumber(): int { return $this->requestStack ->getCurrentRequest() @@ -99,14 +90,14 @@ class PaginatorFactory ->getInt(self::DEFAULT_CURRENT_PAGE_KEY, self::DEFAULT_PAGE_NUMBER); } - protected function getCurrentRoute() + private function getCurrentRoute() { $request = $this->requestStack->getCurrentRequest(); return $request->get('_route'); } - protected function getCurrentRouteParameters() + private function getCurrentRouteParameters() { return array_merge( $this->router->getContext()->getParameters(), diff --git a/src/Bundle/ChillMainBundle/Pagination/PaginatorFactoryInterface.php b/src/Bundle/ChillMainBundle/Pagination/PaginatorFactoryInterface.php new file mode 100644 index 000000000..c83d1f45c --- /dev/null +++ b/src/Bundle/ChillMainBundle/Pagination/PaginatorFactoryInterface.php @@ -0,0 +1,35 @@ +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; + } +} diff --git a/src/Bundle/ChillMainBundle/config/services/pagination.yaml b/src/Bundle/ChillMainBundle/config/services/pagination.yaml index f94bc31be..bdcef3a10 100644 --- a/src/Bundle/ChillMainBundle/config/services/pagination.yaml +++ b/src/Bundle/ChillMainBundle/config/services/pagination.yaml @@ -12,6 +12,7 @@ services: - "%chill_main.pagination.item_per_page%" Chill\MainBundle\Pagination\PaginatorFactory: '@chill_main.paginator_factory' + Chill\MainBundle\Pagination\PaginatorFactoryInterface: '@chill_main.paginator_factory' chill_main.paginator.twig_extensions: class: Chill\MainBundle\Pagination\ChillPaginationTwig From 7c3152f2775ad0a61d809af3403376e25d0c7d16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 27 Nov 2023 20:29:48 +0100 Subject: [PATCH 06/17] Fix migration when executed after the person entity table name change --- .../migrations/Version20141129012050.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Bundle/ChillReportBundle/migrations/Version20141129012050.php b/src/Bundle/ChillReportBundle/migrations/Version20141129012050.php index 2408df14c..c45d1ef0b 100644 --- a/src/Bundle/ChillReportBundle/migrations/Version20141129012050.php +++ b/src/Bundle/ChillReportBundle/migrations/Version20141129012050.php @@ -32,7 +32,18 @@ class Version20141129012050 extends AbstractMigration $this->addSql('CREATE INDEX IDX_C38372B2217BBB47 ON Report (person_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_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;'); } } From d8bf6a195fa5ca66bf4aa6d40c01e77d64c3b495 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 27 Nov 2023 20:30:25 +0100 Subject: [PATCH 07/17] add creation and update information on events and participation --- src/Bundle/ChillEventBundle/Entity/Event.php | 9 ++- .../ChillEventBundle/Entity/Participation.php | 9 ++- .../migrations/Version20231127134244.php | 59 +++++++++++++++++++ 3 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 src/Bundle/ChillEventBundle/migrations/Version20231127134244.php diff --git a/src/Bundle/ChillEventBundle/Entity/Event.php b/src/Bundle/ChillEventBundle/Entity/Event.php index b5d013588..6d286f2c4 100644 --- a/src/Bundle/ChillEventBundle/Entity/Event.php +++ b/src/Bundle/ChillEventBundle/Entity/Event.php @@ -11,6 +11,10 @@ declare(strict_types=1); namespace Chill\EventBundle\Entity; +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\HasCenterInterface; use Chill\MainBundle\Entity\HasScopeInterface; @@ -30,8 +34,11 @@ use Doctrine\ORM\Mapping as ORM; * * @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") */ diff --git a/src/Bundle/ChillEventBundle/Entity/Participation.php b/src/Bundle/ChillEventBundle/Entity/Participation.php index 464feaf30..1f7040c05 100644 --- a/src/Bundle/ChillEventBundle/Entity/Participation.php +++ b/src/Bundle/ChillEventBundle/Entity/Participation.php @@ -11,6 +11,10 @@ declare(strict_types=1); namespace Chill\EventBundle\Entity; +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\HasCenterInterface; use Chill\MainBundle\Entity\HasScopeInterface; @@ -30,8 +34,11 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; * * @ORM\HasLifecycleCallbacks */ -class Participation implements \ArrayAccess, HasCenterInterface, HasScopeInterface +class Participation implements \ArrayAccess, HasCenterInterface, HasScopeInterface, TrackUpdateInterface, TrackCreationInterface { + use TrackCreationTrait; + use TrackUpdateTrait; + /** * @ORM\ManyToOne( * targetEntity="Chill\EventBundle\Entity\Event", diff --git a/src/Bundle/ChillEventBundle/migrations/Version20231127134244.php b/src/Bundle/ChillEventBundle/migrations/Version20231127134244.php new file mode 100644 index 000000000..8a5fa592a --- /dev/null +++ b/src/Bundle/ChillEventBundle/migrations/Version20231127134244.php @@ -0,0 +1,59 @@ +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'); + } +} From e902b6d409b052892b0299e5df3769e035de363a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 27 Nov 2023 20:30:50 +0100 Subject: [PATCH 08/17] Create a page which list events --- .../Controller/EventController.php | 4 +- .../Controller/EventListController.php | 112 ++++++++++ .../ChillEventExtension.php | 1 - .../Form/Type/PickEventType.php | 2 +- .../Menu/SectionMenuBuilder.php | 45 ++++ .../Repository/EventACLAwareRepository.php | 141 ++++++++++++ .../EventACLAwareRepositoryInterface.php | 30 +++ .../Repository/EventTypeRepository.php | 44 ++++ .../Resources/views/Event/page_list.html.twig | 97 +++++++++ .../Controller/EventListControllerTest.php | 42 ++++ .../ParticipationControllerTest.php | 200 ++++++++---------- .../EventACLAwareRepositoryTest.php | 97 +++++++++ .../config/services/menu.yaml | 7 - ...nt-icu.fr.yml => messages+intl-icu.fr.yml} | 8 + .../translations/messages.fr.yml | 5 + .../ChillMainBundle/Test/DummyPaginator.php | 6 +- 16 files changed, 716 insertions(+), 125 deletions(-) create mode 100644 src/Bundle/ChillEventBundle/Controller/EventListController.php create mode 100644 src/Bundle/ChillEventBundle/Menu/SectionMenuBuilder.php create mode 100644 src/Bundle/ChillEventBundle/Repository/EventACLAwareRepository.php create mode 100644 src/Bundle/ChillEventBundle/Repository/EventACLAwareRepositoryInterface.php create mode 100644 src/Bundle/ChillEventBundle/Repository/EventTypeRepository.php create mode 100644 src/Bundle/ChillEventBundle/Resources/views/Event/page_list.html.twig create mode 100644 src/Bundle/ChillEventBundle/Tests/Controller/EventListControllerTest.php create mode 100644 src/Bundle/ChillEventBundle/Tests/Repository/EventACLAwareRepositoryTest.php delete mode 100644 src/Bundle/ChillEventBundle/config/services/menu.yaml rename src/Bundle/ChillEventBundle/translations/{messages+int-icu.fr.yml => messages+intl-icu.fr.yml} (69%) 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 From 9b9c2774addb8b672b5233381a9056505b1968c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 28 Nov 2023 12:35:19 +0100 Subject: [PATCH 09/17] Allow Pick*Type to submit the form when selection an entity, and apply inside Event --- .../Controller/EventController.php | 15 ++++++--------- .../Controller/EventListController.php | 7 ++++++- .../Resources/views/Event/page_list.html.twig | 5 ----- .../Resources/views/Event/show.html.twig | 5 +---- .../Form/Type/PickUserDynamicType.php | 5 ++++- .../Resources/public/module/pick-entity/index.js | 15 ++++++++++++--- .../public/vuejs/PickEntity/PickEntity.vue | 8 ++++++++ .../Resources/views/Form/fields.html.twig | 4 +++- .../Form/Type/PickPersonDynamicType.php | 5 ++++- .../Form/Type/PickThirdpartyDynamicType.php | 5 ++++- 10 files changed, 48 insertions(+), 26 deletions(-) diff --git a/src/Bundle/ChillEventBundle/Controller/EventController.php b/src/Bundle/ChillEventBundle/Controller/EventController.php index 9d4352a02..367c6c334 100644 --- a/src/Bundle/ChillEventBundle/Controller/EventController.php +++ b/src/Bundle/ChillEventBundle/Controller/EventController.php @@ -411,20 +411,17 @@ final class EventController extends AbstractController ] ); - $builder->add('person_id', PickPersonDynamicType::class, ['as_id' => true, 'multiple' => 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(), ]); - $builder->add( - 'submit', - SubmitType::class, - [ - 'label' => 'Add a participation', - ] - ); - return $builder->getForm(); } diff --git a/src/Bundle/ChillEventBundle/Controller/EventListController.php b/src/Bundle/ChillEventBundle/Controller/EventListController.php index ffdc9c804..60df81240 100644 --- a/src/Bundle/ChillEventBundle/Controller/EventListController.php +++ b/src/Bundle/ChillEventBundle/Controller/EventListController.php @@ -101,7 +101,12 @@ final readonly class EventListController ] ); - $builder->add('person_id', PickPersonDynamicType::class, ['as_id' => true, 'multiple' => 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(), diff --git a/src/Bundle/ChillEventBundle/Resources/views/Event/page_list.html.twig b/src/Bundle/ChillEventBundle/Resources/views/Event/page_list.html.twig index 24b7c08a8..f22b56c05 100644 --- a/src/Bundle/ChillEventBundle/Resources/views/Event/page_list.html.twig +++ b/src/Bundle/ChillEventBundle/Resources/views/Event/page_list.html.twig @@ -60,18 +60,13 @@ {% endif %}
{% endif %} - {#
{{ form_start(eventForms[e.id]) }} {{ form_widget(eventForms[e.id].person_id) }} -
    -
  • -
{{ form_end(eventForms[e.id]) }}
- #}
diff --git a/src/Bundle/ChillEventBundle/Resources/views/Event/show.html.twig b/src/Bundle/ChillEventBundle/Resources/views/Event/show.html.twig index b7e3b7d10..3156c61d8 100644 --- a/src/Bundle/ChillEventBundle/Resources/views/Event/show.html.twig +++ b/src/Bundle/ChillEventBundle/Resources/views/Event/show.html.twig @@ -136,11 +136,8 @@ 'class' : 'custom-select', 'style': 'min-width: 15em; max-width: 18em; display: inline-block;' }} ) }} -
- {{ form_widget(form_add_participation_by_person.submit, { 'attr' : { 'class' : 'btn btn-create' } } ) }} -
- {{ form_rest(form_add_participation_by_person) }} + {{ form_end(form_add_participation_by_person) }} diff --git a/src/Bundle/ChillMainBundle/Form/Type/PickUserDynamicType.php b/src/Bundle/ChillMainBundle/Form/Type/PickUserDynamicType.php index 5272b4aad..ad23e5655 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/PickUserDynamicType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/PickUserDynamicType.php @@ -43,6 +43,7 @@ class PickUserDynamicType extends AbstractType $view->vars['uniqid'] = uniqid('pick_user_dyn'); $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) { $view->vars['suggested'][] = $this->normalizer->normalize($user, 'json', ['groups' => 'read']); @@ -58,7 +59,9 @@ class PickUserDynamicType extends AbstractType ->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']); + ->setAllowedTypes('as_id', ['bool']) + ->setDefault('submit_on_adding_new_entity', false) + ->setAllowedTypes('submit_on_adding_new_entity', ['bool']); } public function getBlockPrefix() diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/pick-entity/index.js b/src/Bundle/ChillMainBundle/Resources/public/module/pick-entity/index.js index 6e939734e..6b33c0f52 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/module/pick-entity/index.js +++ b/src/Bundle/ChillMainBundle/Resources/public/module/pick-entity/index.js @@ -25,7 +25,9 @@ function loadDynamicPicker(element) { null : [ JSON.parse(input.value) ] ) suggested = JSON.parse(el.dataset.suggested), - as_id = parseInt(el.dataset.asId) === 1; + as_id = parseInt(el.dataset.asId) === 1, + submit_on_adding_new_entity = parseInt(el.dataset.submitOnAddingNewEntity) === 1 + label = el.dataset.label; if (!isMultiple) { if (input.value === '[]'){ @@ -40,6 +42,7 @@ function loadDynamicPicker(element) { ':picked="picked" ' + ':uniqid="uniqid" ' + ':suggested="notPickedSuggested" ' + + ':label="label" ' + '@addNewEntity="addNewEntity" ' + '@removeEntity="removeEntity">', components: { @@ -51,8 +54,10 @@ function loadDynamicPicker(element) { types: JSON.parse(el.dataset.types), picked: picked === null ? [] : picked, uniqid: el.dataset.uniqid, - suggested: suggested, - as_id: as_id, + suggested, + as_id, + submit_on_adding_new_entity, + label, } }, computed: { @@ -92,6 +97,10 @@ function loadDynamicPicker(element) { } } } + + if (this.submit_on_adding_new_entity) { + input.form.submit(); + } }, removeEntity({entity}) { if (-1 === this.suggested.findIndex(e => e.type === entity.type && e.id === entity.id)) { diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickEntity/PickEntity.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickEntity/PickEntity.vue index 0a39718e2..75abc4fdd 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickEntity/PickEntity.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickEntity/PickEntity.vue @@ -56,6 +56,10 @@ export default { suggested: { type: Array, default: [] + }, + label: { + type: String, + required: false, } }, emits: ['addNewEntity', 'removeEntity'], @@ -80,6 +84,10 @@ export default { }; }, translatedListOfTypes() { + if (this.label !== '') { + return this.label; + } + let trans = []; this.types.forEach(t => { if (this.$props.multiple) { diff --git a/src/Bundle/ChillMainBundle/Resources/views/Form/fields.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Form/fields.html.twig index 1ff92fd5f..9c60028ad 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Form/fields.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Form/fields.html.twig @@ -257,7 +257,9 @@ data-multiple="{{ form.vars['multiple'] }}" data-uniqid="{{ form.vars['uniqid'] }}" data-suggested="{{ form.vars['suggested']|json_encode|escape('html_attr') }}" - data-as-id="{{ form.vars['as_id'] }}"> + 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') }}"> {% endblock %} {% block pick_postal_code_widget %} diff --git a/src/Bundle/ChillPersonBundle/Form/Type/PickPersonDynamicType.php b/src/Bundle/ChillPersonBundle/Form/Type/PickPersonDynamicType.php index 94c625a38..71fd36225 100644 --- a/src/Bundle/ChillPersonBundle/Form/Type/PickPersonDynamicType.php +++ b/src/Bundle/ChillPersonBundle/Form/Type/PickPersonDynamicType.php @@ -42,6 +42,7 @@ class PickPersonDynamicType extends AbstractType $view->vars['uniqid'] = uniqid('pick_user_dyn'); $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) { $view->vars['suggested'][] = $this->normalizer->normalize($person, 'json', ['groups' => 'read']); @@ -56,7 +57,9 @@ class PickPersonDynamicType extends AbstractType ->setDefault('compound', false) ->setDefault('suggested', []) ->setDefault('as_id', false) - ->setAllowedTypes('as_id', ['bool']); + ->setAllowedTypes('as_id', ['bool']) + ->setDefault('submit_on_adding_new_entity', false) + ->setAllowedTypes('submit_on_adding_new_entity', ['bool']); } public function getBlockPrefix() diff --git a/src/Bundle/ChillThirdPartyBundle/Form/Type/PickThirdpartyDynamicType.php b/src/Bundle/ChillThirdPartyBundle/Form/Type/PickThirdpartyDynamicType.php index de233c360..e46e4544e 100644 --- a/src/Bundle/ChillThirdPartyBundle/Form/Type/PickThirdpartyDynamicType.php +++ b/src/Bundle/ChillThirdPartyBundle/Form/Type/PickThirdpartyDynamicType.php @@ -42,6 +42,7 @@ class PickThirdpartyDynamicType extends AbstractType $view->vars['uniqid'] = uniqid('pick_user_dyn'); $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) { $view->vars['suggested'][] = $this->normalizer->normalize($tp, 'json', ['groups' => 'read']); @@ -56,7 +57,9 @@ class PickThirdpartyDynamicType extends AbstractType ->setDefault('compound', false) ->setDefault('suggested', []) ->setDefault('as_id', false) - ->setAllowedTypes('as_id', ['bool']); + ->setAllowedTypes('as_id', ['bool']) + ->setDefault('submit_on_adding_new_entity', false) + ->setAllowedTypes('submit_on_adding_new_entity', ['bool']); } public function getBlockPrefix() From 6b7b2ae52225a831512561d0c4796ce3db4867cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 28 Nov 2023 12:36:44 +0100 Subject: [PATCH 10/17] fixup! Fix migrations to take into account the change in table name for Person's entity --- .../ChillEventBundle/migrations/Version20160318111334.php | 4 ---- .../ChillEventBundle/migrations/Version20190110140538.php | 4 ---- .../ChillEventBundle/migrations/Version20190115140042.php | 4 ---- .../ChillEventBundle/migrations/Version20190201143121.php | 4 ---- 4 files changed, 16 deletions(-) diff --git a/src/Bundle/ChillEventBundle/migrations/Version20160318111334.php b/src/Bundle/ChillEventBundle/migrations/Version20160318111334.php index 908f2bfea..1b74e297f 100644 --- a/src/Bundle/ChillEventBundle/migrations/Version20160318111334.php +++ b/src/Bundle/ChillEventBundle/migrations/Version20160318111334.php @@ -27,8 +27,6 @@ class Version20160318111334 extends AbstractMigration public function down(Schema $schema): void { - $this->abortIf($this->connection->getDatabasePlatform() instanceof PostgreSQLPlatform, '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_status DROP CONSTRAINT FK_A6CC85D0C54C8C93'); $this->addSql('ALTER TABLE chill_event_participation DROP CONSTRAINT FK_4E7768ACD60322AC'); @@ -55,8 +53,6 @@ class Version20160318111334 extends AbstractMigration public function up(Schema $schema): void { - $this->abortIf($this->connection->getDatabasePlatform() instanceof PostgreSQLPlatform, '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_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'); diff --git a/src/Bundle/ChillEventBundle/migrations/Version20190110140538.php b/src/Bundle/ChillEventBundle/migrations/Version20190110140538.php index d83e1b397..3c8b587ca 100644 --- a/src/Bundle/ChillEventBundle/migrations/Version20190110140538.php +++ b/src/Bundle/ChillEventBundle/migrations/Version20190110140538.php @@ -27,16 +27,12 @@ final class Version20190110140538 extends AbstractMigration public function down(Schema $schema): void { - $this->abortIf($this->connection->getDatabasePlatform() instanceof PostgreSQLPlatform, '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 DROP DEFAULT'); } public function up(Schema $schema): void { - $this->abortIf($this->connection->getDatabasePlatform() instanceof PostgreSQLPlatform, '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 DROP DEFAULT'); } diff --git a/src/Bundle/ChillEventBundle/migrations/Version20190115140042.php b/src/Bundle/ChillEventBundle/migrations/Version20190115140042.php index 4ef21a834..63ae29ee0 100644 --- a/src/Bundle/ChillEventBundle/migrations/Version20190115140042.php +++ b/src/Bundle/ChillEventBundle/migrations/Version20190115140042.php @@ -27,8 +27,6 @@ final class Version20190115140042 extends AbstractMigration public function down(Schema $schema): void { - $this->abortIf($this->connection->getDatabasePlatform() instanceof PostgreSQLPlatform, 'Migration can only be executed safely on \'postgresql\'.'); - $this->addSql('ALTER TABLE chill_event_event DROP CONSTRAINT FK_FA320FC8D0AFA354'); $this->addSql('DROP INDEX IDX_FA320FC8D0AFA354'); $this->addSql('ALTER TABLE chill_event_event DROP moderator_id'); @@ -36,8 +34,6 @@ final class Version20190115140042 extends AbstractMigration public function up(Schema $schema): void { - $this->abortIf($this->connection->getDatabasePlatform() instanceof PostgreSQLPlatform, '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 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)'); diff --git a/src/Bundle/ChillEventBundle/migrations/Version20190201143121.php b/src/Bundle/ChillEventBundle/migrations/Version20190201143121.php index cc8445f95..5640bc07b 100644 --- a/src/Bundle/ChillEventBundle/migrations/Version20190201143121.php +++ b/src/Bundle/ChillEventBundle/migrations/Version20190201143121.php @@ -27,16 +27,12 @@ final class Version20190201143121 extends AbstractMigration public function down(Schema $schema): void { - $this->abortIf($this->connection->getDatabasePlatform() instanceof PostgreSQLPlatform, '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 ADD CONSTRAINT fk_fa320fc8d0afa354 FOREIGN KEY (moderator_id) REFERENCES chill_person_person (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); } public function up(Schema $schema): void { - $this->abortIf($this->connection->getDatabasePlatform() instanceof PostgreSQLPlatform, '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 ADD CONSTRAINT FK_FA320FC8D0AFA354 FOREIGN KEY (moderator_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); } From 6d04e477f810598d8e2001fa81cb6139c8bdfabc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 28 Nov 2023 13:00:04 +0100 Subject: [PATCH 11/17] Clean database, to avoid double participations on event --- .../ChillEventBundle/Entity/Participation.php | 37 ++++++------------- .../Resources/views/Event/edit.html.twig | 10 ++--- .../migrations/Version20231128114959.php | 33 +++++++++++++++++ .../translations/messages.fr.yml | 1 + 4 files changed, 48 insertions(+), 33 deletions(-) create mode 100644 src/Bundle/ChillEventBundle/migrations/Version20231128114959.php diff --git a/src/Bundle/ChillEventBundle/Entity/Participation.php b/src/Bundle/ChillEventBundle/Entity/Participation.php index 1f7040c05..8076e8f5f 100644 --- a/src/Bundle/ChillEventBundle/Entity/Participation.php +++ b/src/Bundle/ChillEventBundle/Entity/Participation.php @@ -22,6 +22,7 @@ use Chill\MainBundle\Entity\Scope; use Chill\PersonBundle\Entity\Person; use DateTime; use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Context\ExecutionContextInterface; /** @@ -30,7 +31,9 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; * @ORM\Entity( * 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 */ @@ -55,30 +58,28 @@ class Participation implements \ArrayAccess, HasCenterInterface, HasScopeInterfa */ private ?int $id = null; - /** - * @ORM\Column(type="datetime") - */ - private ?\DateTime $lastUpdate = null; - /** * @ORM\ManyToOne(targetEntity="Chill\PersonBundle\Entity\Person") + * @Assert\NotNull() */ private ?Person $person = null; /** * @ORM\ManyToOne(targetEntity="Chill\EventBundle\Entity\Role") + * @Assert\NotNull() */ private ?Role $role = null; /** * @ORM\ManyToOne(targetEntity="Chill\EventBundle\Entity\Status") + * @Assert\NotNull() */ private ?Status $status = null; /** * @return Center */ - public function getCenter() + public function getCenter(): null|Center { if (null === $this->getEvent()) { throw new \RuntimeException('The event is not linked with this instance. You should initialize the event with a valid center before.'); @@ -100,7 +101,7 @@ class Participation implements \ArrayAccess, HasCenterInterface, HasScopeInterfa * * @return int */ - public function getId() + public function getId(): int { return $this->id; } @@ -112,7 +113,7 @@ class Participation implements \ArrayAccess, HasCenterInterface, HasScopeInterfa */ public function getLastUpdate() { - return $this->lastUpdate; + return $this->getUpdatedAt(); } /** @@ -242,10 +243,6 @@ class Participation implements \ArrayAccess, HasCenterInterface, HasScopeInterfa */ public function setEvent(?Event $event = null) { - if ($this->event !== $event) { - $this->update(); - } - $this->event = $event; return $this; @@ -258,10 +255,6 @@ class Participation implements \ArrayAccess, HasCenterInterface, HasScopeInterfa */ public function setPerson(?Person $person = null) { - if ($person !== $this->person) { - $this->update(); - } - $this->person = $person; return $this; @@ -274,9 +267,6 @@ class Participation implements \ArrayAccess, HasCenterInterface, HasScopeInterfa */ public function setRole(?Role $role = null) { - if ($role !== $this->role) { - $this->update(); - } $this->role = $role; return $this; @@ -289,10 +279,6 @@ class Participation implements \ArrayAccess, HasCenterInterface, HasScopeInterfa */ public function setStatus(?Status $status = null) { - if ($this->status !== $status) { - $this->update(); - } - $this->status = $status; return $this; @@ -302,11 +288,10 @@ class Participation implements \ArrayAccess, HasCenterInterface, HasScopeInterfa * Set lastUpdate. * * @return Participation + * @deprecated */ protected function update() { - $this->lastUpdate = new \DateTime('now'); - return $this; } } diff --git a/src/Bundle/ChillEventBundle/Resources/views/Event/edit.html.twig b/src/Bundle/ChillEventBundle/Resources/views/Event/edit.html.twig index a528f1f5c..af1e32cec 100644 --- a/src/Bundle/ChillEventBundle/Resources/views/Event/edit.html.twig +++ b/src/Bundle/ChillEventBundle/Resources/views/Event/edit.html.twig @@ -15,14 +15,10 @@ {{ form_row(edit_form.type, { 'label': 'Event type' }) }} {{ form_row(edit_form.moderator) }} -