diff --git a/.changes/v2.22.0.md b/.changes/v2.22.0.md
new file mode 100644
index 000000000..fef006fd0
--- /dev/null
+++ b/.changes/v2.22.0.md
@@ -0,0 +1,6 @@
+## v2.22.0 - 2024-06-25
+### Feature
+* ([#216](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/216)) [event bundle] exports added for the event module
+
+### Traduction francophone
+* Exports sont ajoutés pour la module événement.
diff --git a/.changes/v2.22.1.md b/.changes/v2.22.1.md
new file mode 100644
index 000000000..b856cc95b
--- /dev/null
+++ b/.changes/v2.22.1.md
@@ -0,0 +1,5 @@
+## v2.22.1 - 2024-07-01
+### Fixed
+* Remove debug word
+### DX
+* Add a command for reading official address DB from Luxembourg and update chill addresses
diff --git a/.changes/v2.22.2.md b/.changes/v2.22.2.md
new file mode 100644
index 000000000..51b3c75da
--- /dev/null
+++ b/.changes/v2.22.2.md
@@ -0,0 +1,3 @@
+## v2.22.2 - 2024-07-03
+### Fixed
+* Remove scope required for event participation stats
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dc8f2305a..1d0fd76e4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,23 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html),
and is generated by [Changie](https://github.com/miniscruff/changie).
+## v2.22.2 - 2024-07-03
+### Fixed
+* Remove scope required for event participation stats
+
+## v2.22.1 - 2024-07-01
+### Fixed
+* Remove debug word
+### DX
+* Add a command for reading official address DB from Luxembourg and update chill addresses
+
+## v2.22.0 - 2024-06-25
+### Feature
+* ([#216](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/216)) [event bundle] exports added for the event module
+
+### Traduction francophone
+* Exports sont ajoutés pour la module événement.
+
## v2.21.0 - 2024-06-18
### Feature
* Add flash menu buttons in search results, to open directly a new calendar, or a new activity in an accompanying period
diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/concernedGroups.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/concernedGroups.html.twig
index 76db92d42..5528ab233 100644
--- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/concernedGroups.html.twig
+++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/concernedGroups.html.twig
@@ -87,7 +87,6 @@
{% if bloc.type == 'user' %}
- hello
{{ item|chill_entity_render_box({'render': 'raw', 'addAltNames': false, 'at_date': entity.date }) }}
{% else %}
diff --git a/src/Bundle/ChillEventBundle/Controller/EventController.php b/src/Bundle/ChillEventBundle/Controller/EventController.php
index ad67728d7..7a28e51f2 100644
--- a/src/Bundle/ChillEventBundle/Controller/EventController.php
+++ b/src/Bundle/ChillEventBundle/Controller/EventController.php
@@ -15,7 +15,7 @@ use Chill\EventBundle\Entity\Event;
use Chill\EventBundle\Entity\Participation;
use Chill\EventBundle\Form\EventType;
use Chill\EventBundle\Form\Type\PickEventType;
-use Chill\EventBundle\Security\Authorization\EventVoter;
+use Chill\EventBundle\Security\EventVoter;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Pagination\PaginatorFactory;
@@ -418,7 +418,6 @@ final class EventController extends AbstractController
$builder->add('event_id', HiddenType::class, [
'data' => $event->getId(),
]);
- dump($event->getId());
return $builder->getForm();
}
diff --git a/src/Bundle/ChillEventBundle/Controller/ParticipationController.php b/src/Bundle/ChillEventBundle/Controller/ParticipationController.php
index 8030a7878..a8b72dfd4 100644
--- a/src/Bundle/ChillEventBundle/Controller/ParticipationController.php
+++ b/src/Bundle/ChillEventBundle/Controller/ParticipationController.php
@@ -15,7 +15,7 @@ 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\EventBundle\Security\ParticipationVoter;
use Chill\PersonBundle\Repository\PersonRepository;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Doctrine\Common\Collections\Collection;
diff --git a/src/Bundle/ChillEventBundle/DependencyInjection/ChillEventExtension.php b/src/Bundle/ChillEventBundle/DependencyInjection/ChillEventExtension.php
index 8ddcab58c..0b30ca6c5 100644
--- a/src/Bundle/ChillEventBundle/DependencyInjection/ChillEventExtension.php
+++ b/src/Bundle/ChillEventBundle/DependencyInjection/ChillEventExtension.php
@@ -11,8 +11,8 @@ declare(strict_types=1);
namespace Chill\EventBundle\DependencyInjection;
-use Chill\EventBundle\Security\Authorization\EventVoter;
-use Chill\EventBundle\Security\Authorization\ParticipationVoter;
+use Chill\EventBundle\Security\EventVoter;
+use Chill\EventBundle\Security\ParticipationVoter;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
@@ -33,12 +33,13 @@ 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/security.yaml');
$loader->load('services/fixtures.yaml');
$loader->load('services/forms.yaml');
$loader->load('services/repositories.yaml');
$loader->load('services/search.yaml');
$loader->load('services/timeline.yaml');
+ $loader->load('services/export.yaml');
}
/** (non-PHPdoc).
diff --git a/src/Bundle/ChillEventBundle/Export/Aggregator/EventDateAggregator.php b/src/Bundle/ChillEventBundle/Export/Aggregator/EventDateAggregator.php
new file mode 100644
index 000000000..1e519997a
--- /dev/null
+++ b/src/Bundle/ChillEventBundle/Export/Aggregator/EventDateAggregator.php
@@ -0,0 +1,110 @@
+ 'month',
+ 'by week' => 'week',
+ 'by year' => 'year',
+ ];
+
+ private const DEFAULT_CHOICE = 'year';
+
+ public function addRole(): ?string
+ {
+ return null;
+ }
+
+ public function alterQuery(QueryBuilder $qb, $data)
+ {
+ $order = null;
+
+ switch ($data['frequency']) {
+ case 'month':
+ $fmt = 'YYYY-MM';
+
+ break;
+
+ case 'week':
+ $fmt = 'YYYY-IW';
+
+ break;
+
+ case 'year':
+ $fmt = 'YYYY';
+ $order = 'DESC';
+
+ break;
+
+ default:
+ throw new \RuntimeException(sprintf("The frequency data '%s' is invalid.", $data['frequency']));
+ }
+
+ $qb->addSelect(sprintf("TO_CHAR(event.date, '%s') AS date_aggregator", $fmt));
+ $qb->addGroupBy('date_aggregator');
+ $qb->addOrderBy('date_aggregator', $order);
+ }
+
+ public function applyOn(): string
+ {
+ return Declarations::EVENT;
+ }
+
+ public function buildForm(FormBuilderInterface $builder)
+ {
+ $builder->add('frequency', ChoiceType::class, [
+ 'choices' => self::CHOICES,
+ 'multiple' => false,
+ 'expanded' => true,
+ ]);
+ }
+
+ public function getFormDefaultData(): array
+ {
+ return ['frequency' => self::DEFAULT_CHOICE];
+ }
+
+ public function getLabels($key, array $values, $data)
+ {
+ return static function ($value) use ($data): string {
+ if ('_header' === $value) {
+ return 'by '.$data['frequency'];
+ }
+
+ if (null === $value) {
+ return '';
+ }
+
+ return match ($data['frequency']) {
+ default => $value,
+ };
+ };
+ }
+
+ public function getQueryKeys($data): array
+ {
+ return ['date_aggregator'];
+ }
+
+ public function getTitle(): string
+ {
+ return 'Group event by date';
+ }
+}
diff --git a/src/Bundle/ChillEventBundle/Export/Aggregator/EventTypeAggregator.php b/src/Bundle/ChillEventBundle/Export/Aggregator/EventTypeAggregator.php
new file mode 100644
index 000000000..c0010d77a
--- /dev/null
+++ b/src/Bundle/ChillEventBundle/Export/Aggregator/EventTypeAggregator.php
@@ -0,0 +1,83 @@
+getAllAliases(), true)) {
+ $qb->leftJoin('event.type', 'eventtype');
+ }
+
+ $qb->addSelect(sprintf('IDENTITY(event.type) AS %s', self::KEY));
+ $qb->addGroupBy(self::KEY);
+ }
+
+ public function applyOn(): string
+ {
+ return Declarations::EVENT;
+ }
+
+ public function buildForm(FormBuilderInterface $builder)
+ {
+ // no form required for this aggregator
+ }
+
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
+
+ public function getLabels($key, array $values, $data): \Closure
+ {
+ return function (int|string|null $value): string {
+ if ('_header' === $value) {
+ return 'Event type';
+ }
+
+ if (null === $value || '' === $value || null === $t = $this->eventTypeRepository->find($value)) {
+ return '';
+ }
+
+ return $this->translatableStringHelper->localize($t->getName());
+ };
+ }
+
+ public function getQueryKeys($data): array
+ {
+ return [self::KEY];
+ }
+
+ public function getTitle()
+ {
+ return 'Group by event type';
+ }
+}
diff --git a/src/Bundle/ChillEventBundle/Export/Aggregator/RoleAggregator.php b/src/Bundle/ChillEventBundle/Export/Aggregator/RoleAggregator.php
new file mode 100644
index 000000000..f8d1985e2
--- /dev/null
+++ b/src/Bundle/ChillEventBundle/Export/Aggregator/RoleAggregator.php
@@ -0,0 +1,83 @@
+getAllAliases(), true)) {
+ $qb->leftJoin('event_part.role', 'role');
+ }
+
+ $qb->addSelect(sprintf('IDENTITY(event_part.role) AS %s', self::KEY));
+ $qb->addGroupBy(self::KEY);
+ }
+
+ public function applyOn(): string
+ {
+ return Declarations::EVENT_PARTICIPANTS;
+ }
+
+ public function buildForm(FormBuilderInterface $builder)
+ {
+ // no form required for this aggregator
+ }
+
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
+
+ public function getLabels($key, array $values, $data): \Closure
+ {
+ return function (int|string|null $value): string {
+ if ('_header' === $value) {
+ return 'Participant role';
+ }
+
+ if (null === $value || '' === $value || null === $r = $this->roleRepository->find($value)) {
+ return '';
+ }
+
+ return $this->translatableStringHelper->localize($r->getName());
+ };
+ }
+
+ public function getQueryKeys($data): array
+ {
+ return [self::KEY];
+ }
+
+ public function getTitle()
+ {
+ return 'Group by participant role';
+ }
+}
diff --git a/src/Bundle/ChillEventBundle/Export/Declarations.php b/src/Bundle/ChillEventBundle/Export/Declarations.php
new file mode 100644
index 000000000..8a873a673
--- /dev/null
+++ b/src/Bundle/ChillEventBundle/Export/Declarations.php
@@ -0,0 +1,22 @@
+filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center'];
+ }
+
+ public function buildForm(FormBuilderInterface $builder)
+ {
+ }
+
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
+
+ public function getAllowedFormattersTypes()
+ {
+ return [FormatterInterface::TYPE_TABULAR];
+ }
+
+ public function getDescription()
+ {
+ return 'Count participants to an event by various parameters.';
+ }
+
+ public function getGroup(): string
+ {
+ return 'Exports of events';
+ }
+
+ public function getLabels($key, array $values, $data)
+ {
+ if ('export_count_event_participants' !== $key) {
+ throw new \LogicException("the key {$key} is not used by this export");
+ }
+
+ return static fn ($value) => '_header' === $value ? 'Count event participants' : $value;
+ }
+
+ public function getQueryKeys($data)
+ {
+ return ['export_count_event_participants'];
+ }
+
+ public function getResult($query, $data)
+ {
+ return $query->getQuery()->getResult(Query::HYDRATE_SCALAR);
+ }
+
+ public function getTitle()
+ {
+ return 'Count event participants';
+ }
+
+ public function getType(): string
+ {
+ return Declarations::EVENT_PARTICIPANTS;
+ }
+
+ public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
+ {
+ $centers = array_map(static fn ($el) => $el['center'], $acl);
+
+ $qb = $this->participationRepository
+ ->createQueryBuilder('event_part')
+ ->join('event_part.person', 'person');
+
+ $qb->select('COUNT(event_part.id) as export_count_event_participants');
+
+ if ($this->filterStatsByCenters) {
+ $qb
+ ->andWhere(
+ $qb->expr()->exists(
+ 'SELECT 1 FROM '.PersonCenterHistory::class.' acl_count_person_history WHERE acl_count_person_history.person = person
+ AND acl_count_person_history.center IN (:authorized_centers)
+ '
+ )
+ )
+ ->setParameter('authorized_centers', $centers);
+ }
+
+ return $qb;
+ }
+
+ public function requiredRole(): string
+ {
+ return ParticipationVoter::STATS;
+ }
+
+ public function supportsModifiers()
+ {
+ return [
+ Declarations::EVENT_PARTICIPANTS,
+ PersonDeclarations::PERSON_TYPE,
+ ];
+ }
+}
diff --git a/src/Bundle/ChillEventBundle/Export/Export/CountEvents.php b/src/Bundle/ChillEventBundle/Export/Export/CountEvents.php
new file mode 100644
index 000000000..5a1930862
--- /dev/null
+++ b/src/Bundle/ChillEventBundle/Export/Export/CountEvents.php
@@ -0,0 +1,128 @@
+filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center'];
+ }
+
+ public function buildForm(FormBuilderInterface $builder)
+ {
+ }
+
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
+
+ public function getAllowedFormattersTypes()
+ {
+ return [FormatterInterface::TYPE_TABULAR];
+ }
+
+ public function getDescription()
+ {
+ return 'Count events by various parameters.';
+ }
+
+ public function getGroup(): string
+ {
+ return 'Exports of events';
+ }
+
+ public function getLabels($key, array $values, $data)
+ {
+ if ('export_count_event' !== $key) {
+ throw new \LogicException("the key {$key} is not used by this export");
+ }
+
+ return static fn ($value) => '_header' === $value ? 'Number of events' : $value;
+ }
+
+ public function getQueryKeys($data)
+ {
+ return ['export_count_event'];
+ }
+
+ public function getResult($query, $data)
+ {
+ return $query->getQuery()->getResult(Query::HYDRATE_SCALAR);
+ }
+
+ public function getTitle()
+ {
+ return 'Count events';
+ }
+
+ public function getType(): string
+ {
+ return Declarations::EVENT;
+ }
+
+ public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
+ {
+ $centers = array_map(static fn ($el) => $el['center'], $acl);
+
+ $qb = $this->eventRepository
+ ->createQueryBuilder('event')
+ ->leftJoin('event.participations', 'epart')
+ ->leftJoin('epart.person', 'person');
+
+ $qb->select('COUNT(event.id) as export_count_event');
+
+ if ($this->filterStatsByCenters) {
+ $qb
+ ->andWhere(
+ $qb->expr()->exists(
+ 'SELECT 1 FROM '.PersonCenterHistory::class.' acl_count_person_history WHERE acl_count_person_history.person = person
+ AND acl_count_person_history.center IN (:authorized_centers)
+ '
+ )
+ )
+ ->setParameter('authorized_centers', $centers);
+ }
+
+ return $qb;
+ }
+
+ public function requiredRole(): string
+ {
+ return EventVoter::STATS;
+ }
+
+ public function supportsModifiers()
+ {
+ return [
+ Declarations::EVENT,
+ PersonDeclarations::PERSON_TYPE,
+ ];
+ }
+}
diff --git a/src/Bundle/ChillEventBundle/Export/Filter/EventDateFilter.php b/src/Bundle/ChillEventBundle/Export/Filter/EventDateFilter.php
new file mode 100644
index 000000000..650c5348d
--- /dev/null
+++ b/src/Bundle/ChillEventBundle/Export/Filter/EventDateFilter.php
@@ -0,0 +1,97 @@
+getDQLPart('where');
+ $clause = $qb->expr()->between(
+ 'event.date',
+ ':date_from',
+ ':date_to'
+ );
+
+ if ($where instanceof Expr\Andx) {
+ $where->add($clause);
+ } else {
+ $where = $qb->expr()->andX($clause);
+ }
+
+ $qb->add('where', $where);
+ $qb->setParameter(
+ 'date_from',
+ $this->rollingDateConverter->convert($data['date_from'])
+ );
+ $qb->setParameter(
+ 'date_to',
+ $this->rollingDateConverter->convert($data['date_to'])
+ );
+ }
+
+ public function applyOn(): string
+ {
+ return Declarations::EVENT;
+ }
+
+ public function buildForm(FormBuilderInterface $builder)
+ {
+ $builder
+ ->add('date_from', PickRollingDateType::class, [
+ 'label' => 'Events after this date',
+ ])
+ ->add('date_to', PickRollingDateType::class, [
+ 'label' => 'Events before this date',
+ ]);
+ }
+
+ public function getFormDefaultData(): array
+ {
+ return ['date_from' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), 'date_to' => new RollingDate(RollingDate::T_TODAY)];
+ }
+
+ public function describeAction($data, $format = 'string')
+ {
+ return [
+ 'Filtered by date of event: only between %date_from% and %date_to%',
+ [
+ '%date_from%' => $this->rollingDateConverter->convert($data['date_from'])->format('d-m-Y'),
+ '%date_to%' => $this->rollingDateConverter->convert($data['date_to'])->format('d-m-Y'),
+ ],
+ ];
+ }
+
+ public function getTitle()
+ {
+ return 'Filtered by event date';
+ }
+}
diff --git a/src/Bundle/ChillEventBundle/Export/Filter/EventTypeFilter.php b/src/Bundle/ChillEventBundle/Export/Filter/EventTypeFilter.php
new file mode 100644
index 000000000..d4c1a96c0
--- /dev/null
+++ b/src/Bundle/ChillEventBundle/Export/Filter/EventTypeFilter.php
@@ -0,0 +1,95 @@
+expr()->in('event.type', ':selected_event_types');
+
+ $qb->andWhere($clause);
+ $qb->setParameter('selected_event_types', $data['types']);
+ }
+
+ public function applyOn(): string
+ {
+ return Declarations::EVENT;
+ }
+
+ public function buildForm(FormBuilderInterface $builder)
+ {
+ $builder->add('types', EntityType::class, [
+ 'choices' => $this->eventTypeRepository->findAllActive(),
+ 'class' => EventType::class,
+ 'choice_label' => fn (EventType $ety) => $this->translatableStringHelper->localize($ety->getName()),
+ 'multiple' => true,
+ 'expanded' => false,
+ 'attr' => [
+ 'class' => 'select2',
+ ],
+ ]);
+ }
+
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
+
+ public function describeAction($data, $format = 'string')
+ {
+ $typeNames = array_map(
+ fn (EventType $t): string => $this->translatableStringHelper->localize($t->getName()),
+ $this->eventTypeRepository->findBy(['id' => $data['types'] instanceof \Doctrine\Common\Collections\Collection ? $data['types']->toArray() : $data['types']])
+ );
+
+ return ['Filtered by event type: only %list%', [
+ '%list%' => implode(', ', $typeNames),
+ ]];
+ }
+
+ public function getTitle()
+ {
+ return 'Filtered by event type';
+ }
+
+ public function validateForm($data, ExecutionContextInterface $context)
+ {
+ if (null === $data['types'] || 0 === \count($data['types'])) {
+ $context
+ ->buildViolation('At least one type must be chosen')
+ ->addViolation();
+ }
+ }
+}
diff --git a/src/Bundle/ChillEventBundle/Export/Filter/RoleFilter.php b/src/Bundle/ChillEventBundle/Export/Filter/RoleFilter.php
new file mode 100644
index 000000000..d791bafe7
--- /dev/null
+++ b/src/Bundle/ChillEventBundle/Export/Filter/RoleFilter.php
@@ -0,0 +1,95 @@
+expr()->in('event_part.role', ':selected_part_roles');
+
+ $qb->andWhere($clause);
+ $qb->setParameter('selected_part_roles', $data['part_roles']);
+ }
+
+ public function applyOn(): string
+ {
+ return Declarations::EVENT_PARTICIPANTS;
+ }
+
+ public function buildForm(FormBuilderInterface $builder)
+ {
+ $builder->add('part_roles', EntityType::class, [
+ 'choices' => $this->roleRepository->findAllActive(),
+ 'class' => Role::class,
+ 'choice_label' => fn (Role $r) => $this->translatableStringHelper->localize($r->getName()),
+ 'multiple' => true,
+ 'expanded' => false,
+ 'attr' => [
+ 'class' => 'select2',
+ ],
+ ]);
+ }
+
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
+
+ public function describeAction($data, $format = 'string')
+ {
+ $roleNames = array_map(
+ fn (Role $r): string => $this->translatableStringHelper->localize($r->getName()),
+ $this->roleRepository->findBy(['id' => $data['part_roles'] instanceof \Doctrine\Common\Collections\Collection ? $data['part_roles']->toArray() : $data['part_roles']])
+ );
+
+ return ['Filtered by participant roles: only %list%', [
+ '%list%' => implode(', ', $roleNames),
+ ]];
+ }
+
+ public function getTitle()
+ {
+ return 'Filter by participant roles';
+ }
+
+ public function validateForm($data, ExecutionContextInterface $context)
+ {
+ if (null === $data['part_roles'] || 0 === \count($data['part_roles'])) {
+ $context
+ ->buildViolation('At least one role must be chosen')
+ ->addViolation();
+ }
+ }
+}
diff --git a/src/Bundle/ChillEventBundle/Menu/PersonMenuBuilder.php b/src/Bundle/ChillEventBundle/Menu/PersonMenuBuilder.php
index 8abfc7cd6..e14e4c979 100644
--- a/src/Bundle/ChillEventBundle/Menu/PersonMenuBuilder.php
+++ b/src/Bundle/ChillEventBundle/Menu/PersonMenuBuilder.php
@@ -11,7 +11,7 @@ declare(strict_types=1);
namespace Chill\EventBundle\Menu;
-use Chill\EventBundle\Security\Authorization\EventVoter;
+use Chill\EventBundle\Security\EventVoter;
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
use Knp\Menu\MenuItem;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
diff --git a/src/Bundle/ChillEventBundle/Menu/SectionMenuBuilder.php b/src/Bundle/ChillEventBundle/Menu/SectionMenuBuilder.php
index 341848e42..276324938 100644
--- a/src/Bundle/ChillEventBundle/Menu/SectionMenuBuilder.php
+++ b/src/Bundle/ChillEventBundle/Menu/SectionMenuBuilder.php
@@ -11,7 +11,7 @@ declare(strict_types=1);
namespace Chill\EventBundle\Menu;
-use Chill\EventBundle\Security\Authorization\EventVoter;
+use Chill\EventBundle\Security\EventVoter;
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
use Knp\Menu\MenuItem;
use Symfony\Component\Security\Core\Security;
diff --git a/src/Bundle/ChillEventBundle/Repository/EventACLAwareRepository.php b/src/Bundle/ChillEventBundle/Repository/EventACLAwareRepository.php
index 7b9b027f9..22141b536 100644
--- a/src/Bundle/ChillEventBundle/Repository/EventACLAwareRepository.php
+++ b/src/Bundle/ChillEventBundle/Repository/EventACLAwareRepository.php
@@ -13,7 +13,7 @@ namespace Chill\EventBundle\Repository;
use Chill\EventBundle\Entity\Event;
use Chill\EventBundle\Entity\Participation;
-use Chill\EventBundle\Security\Authorization\EventVoter;
+use Chill\EventBundle\Security\EventVoter;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Security\Authorization\AuthorizationHelperForCurrentUserInterface;
use Chill\PersonBundle\Entity\Person;
diff --git a/src/Bundle/ChillEventBundle/Repository/RoleRepository.php b/src/Bundle/ChillEventBundle/Repository/RoleRepository.php
index fa0524a6d..b5444a85b 100644
--- a/src/Bundle/ChillEventBundle/Repository/RoleRepository.php
+++ b/src/Bundle/ChillEventBundle/Repository/RoleRepository.php
@@ -12,13 +12,57 @@ declare(strict_types=1);
namespace Chill\EventBundle\Repository;
use Chill\EventBundle\Entity\Role;
-use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
-use Doctrine\Persistence\ManagerRegistry;
+use Chill\MainBundle\Templating\TranslatableStringHelper;
+use Doctrine\ORM\EntityManagerInterface;
+use Doctrine\ORM\EntityRepository;
+use Doctrine\ORM\QueryBuilder;
+use Doctrine\Persistence\ObjectRepository;
-class RoleRepository extends ServiceEntityRepository
+readonly class RoleRepository implements ObjectRepository
{
- public function __construct(ManagerRegistry $registry)
+ private EntityRepository $repository;
+
+ public function __construct(EntityManagerInterface $entityManager, private TranslatableStringHelper $translatableStringHelper)
{
- parent::__construct($registry, Role::class);
+ $this->repository = $entityManager->getRepository(Role::class);
+ }
+
+ public function createQueryBuilder(string $alias, ?string $indexBy = null): QueryBuilder
+ {
+ return $this->repository->createQueryBuilder($alias, $indexBy);
+ }
+
+ public function find($id)
+ {
+ return $this->repository->find($id);
+ }
+
+ public function findAll(): array
+ {
+ return $this->repository->findAll();
+ }
+
+ public function findAllActive(): array
+ {
+ $roles = $this->repository->findBy(['active' => true]);
+
+ usort($roles, fn (Role $a, Role $b) => $this->translatableStringHelper->localize($a->getName()) <=> $this->translatableStringHelper->localize($b->getName()));
+
+ return $roles;
+ }
+
+ public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array
+ {
+ return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
+ }
+
+ public function findOneBy(array $criteria)
+ {
+ return $this->repository->findOneBy($criteria);
+ }
+
+ public function getClassName(): string
+ {
+ return Role::class;
}
}
diff --git a/src/Bundle/ChillEventBundle/Security/Authorization/EventVoter.php b/src/Bundle/ChillEventBundle/Security/EventVoter.php
similarity index 60%
rename from src/Bundle/ChillEventBundle/Security/Authorization/EventVoter.php
rename to src/Bundle/ChillEventBundle/Security/EventVoter.php
index c88f2804a..e490e0518 100644
--- a/src/Bundle/ChillEventBundle/Security/Authorization/EventVoter.php
+++ b/src/Bundle/ChillEventBundle/Security/EventVoter.php
@@ -9,18 +9,19 @@ declare(strict_types=1);
* the LICENSE file that was distributed with this source code.
*/
-namespace Chill\EventBundle\Security\Authorization;
+namespace Chill\EventBundle\Security;
use Chill\EventBundle\Entity\Event;
+use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Security\Authorization\AbstractChillVoter;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
+use Chill\MainBundle\Security\Authorization\VoterHelperFactoryInterface;
+use Chill\MainBundle\Security\Authorization\VoterHelperInterface;
use Chill\MainBundle\Security\ProvideRoleHierarchyInterface;
use Chill\PersonBundle\Entity\Person;
-use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Psr\Log\LoggerInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
-use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
/**
* Description of EventVoter.
@@ -42,61 +43,46 @@ class EventVoter extends AbstractChillVoter implements ProvideRoleHierarchyInter
final public const UPDATE = 'CHILL_EVENT_UPDATE';
- /**
- * @var AccessDecisionManagerInterface
- */
- protected $accessDecisionManager;
+ final public const STATS = 'CHILL_EVENT_STATS';
- /**
- * @var AuthorizationHelper
- */
- protected $authorizationHelper;
-
- /**
- * @var LoggerInterface
- */
- protected $logger;
+ private readonly VoterHelperInterface $voterHelper;
public function __construct(
- AccessDecisionManagerInterface $accessDecisionManager,
- AuthorizationHelper $authorizationHelper,
- LoggerInterface $logger
+ private readonly AuthorizationHelper $authorizationHelper,
+ private readonly LoggerInterface $logger,
+ VoterHelperFactoryInterface $voterHelperFactory
) {
- $this->accessDecisionManager = $accessDecisionManager;
- $this->authorizationHelper = $authorizationHelper;
- $this->logger = $logger;
+ $this->voterHelper = $voterHelperFactory
+ ->generate(self::class)
+ ->addCheckFor(null, [self::SEE])
+ ->addCheckFor(Event::class, [...self::ROLES])
+ ->addCheckFor(Person::class, [self::SEE, self::CREATE])
+ ->addCheckFor(Center::class, [self::STATS])
+ ->build();
}
public function getRoles(): array
{
- return self::ROLES;
+ return [...self::ROLES, self::STATS];
}
public function getRolesWithHierarchy(): array
{
return [
- 'Event' => self::ROLES,
+ 'Event' => $this->getRoles(),
];
}
public function getRolesWithoutScope(): array
{
- return [];
+ return [self::ROLES, self::STATS];
}
public function supports($attribute, $subject)
{
- return ($subject instanceof Event && \in_array($attribute, self::ROLES, true))
- || ($subject instanceof Person && \in_array($attribute, [self::CREATE, self::SEE], true))
- || (null === $subject && self::SEE === $attribute);
+ return $this->voterHelper->supports($attribute, $subject);
}
- /**
- * @param string $attribute
- * @param Event $subject
- *
- * @return bool
- */
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
{
$this->logger->debug(sprintf('Voting from %s class', self::class));
@@ -118,15 +104,5 @@ class EventVoter extends AbstractChillVoter implements ProvideRoleHierarchyInter
->getReachableCenters($token->getUser(), $attribute);
return \count($centers) > 0;
-
- if (!$this->accessDecisionManager->decide($token, [PersonVoter::SEE], $person)) {
- return false;
- }
-
- return $this->authorizationHelper->userHasAccess(
- $token->getUser(),
- $subject,
- $attribute
- );
}
}
diff --git a/src/Bundle/ChillEventBundle/Security/Authorization/ParticipationVoter.php b/src/Bundle/ChillEventBundle/Security/ParticipationVoter.php
similarity index 61%
rename from src/Bundle/ChillEventBundle/Security/Authorization/ParticipationVoter.php
rename to src/Bundle/ChillEventBundle/Security/ParticipationVoter.php
index ad2e90377..368a47cab 100644
--- a/src/Bundle/ChillEventBundle/Security/Authorization/ParticipationVoter.php
+++ b/src/Bundle/ChillEventBundle/Security/ParticipationVoter.php
@@ -9,18 +9,19 @@ declare(strict_types=1);
* the LICENSE file that was distributed with this source code.
*/
-namespace Chill\EventBundle\Security\Authorization;
+namespace Chill\EventBundle\Security;
use Chill\EventBundle\Entity\Participation;
+use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Security\Authorization\AbstractChillVoter;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
+use Chill\MainBundle\Security\Authorization\VoterHelperFactoryInterface;
+use Chill\MainBundle\Security\Authorization\VoterHelperInterface;
use Chill\MainBundle\Security\ProvideRoleHierarchyInterface;
use Chill\PersonBundle\Entity\Person;
-use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Psr\Log\LoggerInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
-use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
class ParticipationVoter extends AbstractChillVoter implements ProvideRoleHierarchyInterface
{
@@ -39,58 +40,48 @@ class ParticipationVoter extends AbstractChillVoter implements ProvideRoleHierar
final public const UPDATE = 'CHILL_EVENT_PARTICIPATION_UPDATE';
- /**
- * @var AccessDecisionManagerInterface
- */
- protected $accessDecisionManager;
+ final public const STATS = 'CHILL_EVENT_PARTICIPATION_STATS';
- /**
- * @var AuthorizationHelper
- */
- protected $authorizationHelper;
-
- /**
- * @var LoggerInterface
- */
- protected $logger;
+ private readonly VoterHelperInterface $voterHelper;
public function __construct(
- AccessDecisionManagerInterface $accessDecisionManager,
- AuthorizationHelper $authorizationHelper,
- LoggerInterface $logger
+ private readonly AuthorizationHelper $authorizationHelper,
+ private readonly LoggerInterface $logger,
+ VoterHelperFactoryInterface $voterHelperFactory
) {
- $this->accessDecisionManager = $accessDecisionManager;
- $this->authorizationHelper = $authorizationHelper;
- $this->logger = $logger;
+ $this->voterHelper = $voterHelperFactory
+ ->generate(self::class)
+ ->addCheckFor(null, [self::SEE])
+ ->addCheckFor(Participation::class, [...self::ROLES])
+ ->addCheckFor(Person::class, [self::SEE, self::CREATE])
+ ->addCheckFor(Center::class, [self::STATS])
+ ->build();
}
public function getRoles(): array
{
- return self::ROLES;
+ return [...self::ROLES, self::STATS];
}
public function getRolesWithHierarchy(): array
{
return [
- 'Event' => self::ROLES,
+ 'Participation' => $this->getRoles(),
];
}
public function getRolesWithoutScope(): array
{
- return [];
+ return [self::ROLES, self::STATS];
}
public function supports($attribute, $subject)
{
- return ($subject instanceof Participation && \in_array($attribute, self::ROLES, true))
- || ($subject instanceof Person && \in_array($attribute, [self::CREATE, self::SEE], true))
- || (null === $subject && self::SEE === $attribute);
+ return $this->voterHelper->supports($attribute, $subject);
}
/**
- * @param string $attribute
- * @param Participation $subject
+ * @param string $attribute
*
* @return bool
*/
@@ -115,15 +106,5 @@ class ParticipationVoter extends AbstractChillVoter implements ProvideRoleHierar
->getReachableCenters($token->getUser(), $attribute);
return \count($centers) > 0;
-
- if (!$this->accessDecisionManager->decide($token, [PersonVoter::SEE], $person)) {
- return false;
- }
-
- return $this->authorizationHelper->userHasAccess(
- $token->getUser(),
- $subject,
- $attribute
- );
}
}
diff --git a/src/Bundle/ChillEventBundle/Tests/Export/CountEventParticipationsTest.php b/src/Bundle/ChillEventBundle/Tests/Export/CountEventParticipationsTest.php
new file mode 100644
index 000000000..3d606dd04
--- /dev/null
+++ b/src/Bundle/ChillEventBundle/Tests/Export/CountEventParticipationsTest.php
@@ -0,0 +1,43 @@
+countEventParticipations = self::$container->get(CountEventParticipations::class);
+ }
+
+ public function testExecuteQuery(): void
+ {
+ $qb = $this->countEventParticipations->initiateQuery([], [], [])
+ ->setMaxResults(1);
+
+ $results = $qb->getQuery()->getResult(AbstractQuery::HYDRATE_ARRAY);
+
+ self::assertIsArray($results, 'smoke test: test that the result is an array');
+ }
+}
diff --git a/src/Bundle/ChillEventBundle/Tests/Export/CountEventTest.php b/src/Bundle/ChillEventBundle/Tests/Export/CountEventTest.php
new file mode 100644
index 000000000..4afbe64e9
--- /dev/null
+++ b/src/Bundle/ChillEventBundle/Tests/Export/CountEventTest.php
@@ -0,0 +1,43 @@
+countEvents = self::$container->get(CountEvents::class);
+ }
+
+ public function testExecuteQuery(): void
+ {
+ $qb = $this->countEvents->initiateQuery([], [], [])
+ ->setMaxResults(1);
+
+ $results = $qb->getQuery()->getResult(AbstractQuery::HYDRATE_ARRAY);
+
+ self::assertIsArray($results, 'smoke test: test that the result is an array');
+ }
+}
diff --git a/src/Bundle/ChillEventBundle/Tests/Export/aggregators/EventDateAggregatorTest.php b/src/Bundle/ChillEventBundle/Tests/Export/aggregators/EventDateAggregatorTest.php
new file mode 100644
index 000000000..96e377f4e
--- /dev/null
+++ b/src/Bundle/ChillEventBundle/Tests/Export/aggregators/EventDateAggregatorTest.php
@@ -0,0 +1,59 @@
+aggregator = self::$container->get(EventDateAggregator::class);
+ }
+
+ public function getAggregator()
+ {
+ return $this->aggregator;
+ }
+
+ public function getFormData(): array|\Generator
+ {
+ yield ['frequency' => 'YYYY'];
+ yield ['frequency' => 'YYYY-MM'];
+ yield ['frequency' => 'YYYY-IV'];
+ }
+
+ public function getQueryBuilders(): array
+ {
+ self::bootKernel();
+
+ $em = self::$container->get(EntityManagerInterface::class);
+
+ return [
+ $em->createQueryBuilder()
+ ->select('event.id')
+ ->from(Event::class, 'event'),
+ ];
+ }
+}
diff --git a/src/Bundle/ChillEventBundle/Tests/Export/aggregators/EventTypeAggregatorTest.php b/src/Bundle/ChillEventBundle/Tests/Export/aggregators/EventTypeAggregatorTest.php
new file mode 100644
index 000000000..321eae153
--- /dev/null
+++ b/src/Bundle/ChillEventBundle/Tests/Export/aggregators/EventTypeAggregatorTest.php
@@ -0,0 +1,59 @@
+aggregator = self::$container->get(EventTypeAggregator::class);
+ }
+
+ public function getAggregator()
+ {
+ return $this->aggregator;
+ }
+
+ public function getFormData(): array
+ {
+ return [
+ [],
+ ];
+ }
+
+ public function getQueryBuilders(): array
+ {
+ self::bootKernel();
+
+ $em = self::$container->get(EntityManagerInterface::class);
+
+ return [
+ $em->createQueryBuilder()
+ ->select('event.id')
+ ->from(Event::class, 'event'),
+ ];
+ }
+}
diff --git a/src/Bundle/ChillEventBundle/Tests/Export/aggregators/RoleAggregatorTest.php b/src/Bundle/ChillEventBundle/Tests/Export/aggregators/RoleAggregatorTest.php
new file mode 100644
index 000000000..0e4f0a896
--- /dev/null
+++ b/src/Bundle/ChillEventBundle/Tests/Export/aggregators/RoleAggregatorTest.php
@@ -0,0 +1,63 @@
+aggregator = self::$container->get(RoleAggregator::class);
+ }
+
+ public function getAggregator()
+ {
+ return $this->aggregator;
+ }
+
+ public function getFormData(): array
+ {
+ return [
+ [],
+ ];
+ }
+
+ public function getQueryBuilders(): array
+ {
+ self::bootKernel();
+
+ $em = self::$container->get(EntityManagerInterface::class);
+
+ return [
+ $em->createQueryBuilder()
+ ->select('event.id')
+ ->from(Event::class, 'event'),
+ $em->createQueryBuilder()
+ ->select('event_part')
+ ->from(Participation::class, 'event_part'),
+ ];
+ }
+}
diff --git a/src/Bundle/ChillEventBundle/Tests/Export/filters/EventDateFilterTest.php b/src/Bundle/ChillEventBundle/Tests/Export/filters/EventDateFilterTest.php
new file mode 100644
index 000000000..dd13c5c77
--- /dev/null
+++ b/src/Bundle/ChillEventBundle/Tests/Export/filters/EventDateFilterTest.php
@@ -0,0 +1,65 @@
+rollingDateConverter = self::$container->get(RollingDateConverterInterface::class);
+ }
+
+ public function getFilter()
+ {
+ return new EventDateFilter($this->rollingDateConverter);
+ }
+
+ public function getFormData()
+ {
+ return [
+ [
+ 'date_from' => new RollingDate(RollingDate::T_YEAR_CURRENT_START),
+ 'date_to' => new RollingDate(RollingDate::T_TODAY),
+ ],
+ ];
+ }
+
+ public function getQueryBuilders(): array
+ {
+ self::bootKernel();
+
+ $em = self::$container->get(EntityManagerInterface::class);
+
+ return [
+ $em->createQueryBuilder()
+ ->select('event.id')
+ ->from(Event::class, 'event'),
+ ];
+ }
+}
diff --git a/src/Bundle/ChillEventBundle/Tests/Export/filters/EventTypeFilterTest.php b/src/Bundle/ChillEventBundle/Tests/Export/filters/EventTypeFilterTest.php
new file mode 100644
index 000000000..8d54c8068
--- /dev/null
+++ b/src/Bundle/ChillEventBundle/Tests/Export/filters/EventTypeFilterTest.php
@@ -0,0 +1,76 @@
+filter = self::$container->get(EventTypeFilter::class);
+ }
+
+ public function getFilter(): EventTypeFilter|\Chill\MainBundle\Export\FilterInterface
+ {
+ return $this->filter;
+ }
+
+ public function getFormData()
+ {
+ self::bootKernel();
+
+ $em = self::$container->get(EntityManagerInterface::class);
+
+ $array = $em->createQueryBuilder()
+ ->from(EventType::class, 'et')
+ ->select('et')
+ ->getQuery()
+ ->getResult();
+
+ $data = [];
+
+ foreach ($array as $a) {
+ $data[] = [
+ 'types' => new ArrayCollection([$a]),
+ ];
+ }
+
+ return $data;
+ }
+
+ public function getQueryBuilders()
+ {
+ self::bootKernel();
+
+ $em = self::$container->get(EntityManagerInterface::class);
+
+ return [
+ $em->createQueryBuilder()
+ ->select('event.id')
+ ->from(Event::class, 'event'),
+ ];
+ }
+}
diff --git a/src/Bundle/ChillEventBundle/Tests/Export/filters/RoleFilterTest.php b/src/Bundle/ChillEventBundle/Tests/Export/filters/RoleFilterTest.php
new file mode 100644
index 000000000..f03aac5e0
--- /dev/null
+++ b/src/Bundle/ChillEventBundle/Tests/Export/filters/RoleFilterTest.php
@@ -0,0 +1,81 @@
+filter = self::$container->get(RoleFilter::class);
+ }
+
+ public function getFilter()
+ {
+ return $this->filter;
+ }
+
+ public function getFormData(): array
+ {
+ self::bootKernel();
+ $em = self::$container->get(EntityManagerInterface::class);
+
+ $array = $em->createQueryBuilder()
+ ->from(Role::class, 'r')
+ ->select('r')
+ ->getQuery()
+ ->setMaxResults(1)
+ ->getResult();
+
+ $data = [];
+
+ foreach ($array as $a) {
+ $data[] = [
+ 'roles' => new ArrayCollection([$a]),
+ ];
+ }
+
+ return $data;
+ }
+
+ public function getQueryBuilders()
+ {
+ self::bootKernel();
+
+ $em = self::$container->get(EntityManagerInterface::class);
+
+ return [
+ $em->createQueryBuilder()
+ ->select('event.id')
+ ->from(Event::class, 'event'),
+ $em->createQueryBuilder()
+ ->select('event_part')
+ ->from(Participation::class, 'event_part'),
+ ];
+ }
+}
diff --git a/src/Bundle/ChillEventBundle/Tests/Repository/EventACLAwareRepositoryTest.php b/src/Bundle/ChillEventBundle/Tests/Repository/EventACLAwareRepositoryTest.php
index 4bcaac1f6..758e50d56 100644
--- a/src/Bundle/ChillEventBundle/Tests/Repository/EventACLAwareRepositoryTest.php
+++ b/src/Bundle/ChillEventBundle/Tests/Repository/EventACLAwareRepositoryTest.php
@@ -12,7 +12,7 @@ declare(strict_types=1);
namespace Chill\EventBundle\Tests\Repository;
use Chill\EventBundle\Repository\EventACLAwareRepository;
-use Chill\EventBundle\Security\Authorization\EventVoter;
+use Chill\EventBundle\Security\EventVoter;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User;
diff --git a/src/Bundle/ChillEventBundle/config/services/authorization.yaml b/src/Bundle/ChillEventBundle/config/services/authorization.yaml
deleted file mode 100644
index 6f382b72e..000000000
--- a/src/Bundle/ChillEventBundle/config/services/authorization.yaml
+++ /dev/null
@@ -1,18 +0,0 @@
-services:
- chill_event.event_voter:
- class: Chill\EventBundle\Security\Authorization\EventVoter
- arguments:
- - "@security.access.decision_manager"
- - "@chill.main.security.authorization.helper"
- - "@logger"
- tags:
- - { name: security.voter }
-
- chill_event.event_participation:
- class: Chill\EventBundle\Security\Authorization\ParticipationVoter
- arguments:
- - "@security.access.decision_manager"
- - "@chill.main.security.authorization.helper"
- - "@logger"
- tags:
- - { name: security.voter }
diff --git a/src/Bundle/ChillEventBundle/config/services/export.yaml b/src/Bundle/ChillEventBundle/config/services/export.yaml
new file mode 100644
index 000000000..8f8399e31
--- /dev/null
+++ b/src/Bundle/ChillEventBundle/config/services/export.yaml
@@ -0,0 +1,41 @@
+services:
+ _defaults:
+ autowire: true
+ autoconfigure: true
+
+ # indicators
+
+ Chill\EventBundle\Export\Export\CountEvents:
+ tags:
+ - { name: chill.export, alias: 'count_events' }
+ Chill\EventBundle\Export\Export\CountEventParticipations:
+ tags:
+ - { name: chill.export, alias: 'count_event_participants' }
+
+ # filters
+
+ Chill\EventBundle\Export\Filter\EventDateFilter:
+ tags:
+ - { name: chill.export_filter, alias: 'event_date_filter' }
+
+ Chill\EventBundle\Export\Filter\EventTypeFilter:
+ tags:
+ - { name: chill.export_filter, alias: 'event_type_filter' }
+
+ Chill\EventBundle\Export\Filter\RoleFilter:
+ tags:
+ - { name: chill.export_filter, alias: 'role_filter' }
+
+ # aggregators
+
+ Chill\EventBundle\Export\Aggregator\EventTypeAggregator:
+ tags:
+ - { name: chill.export_aggregator, alias: event_type_aggregator }
+
+ Chill\EventBundle\Export\Aggregator\EventDateAggregator:
+ tags:
+ - { name: chill.export_aggregator, alias: event_date_aggregator }
+
+ Chill\EventBundle\Export\Aggregator\RoleAggregator:
+ tags:
+ - { name: chill.export_aggregator, alias: role_aggregator }
diff --git a/src/Bundle/ChillEventBundle/config/services/security.yaml b/src/Bundle/ChillEventBundle/config/services/security.yaml
new file mode 100644
index 000000000..13198d55f
--- /dev/null
+++ b/src/Bundle/ChillEventBundle/config/services/security.yaml
@@ -0,0 +1,14 @@
+services:
+ Chill\EventBundle\Security\EventVoter:
+ autowire: true
+ autoconfigure: true
+ tags:
+ - { name: security.voter }
+ - { name: chill.role }
+
+ Chill\EventBundle\Security\ParticipationVoter:
+ autowire: true
+ autoconfigure: true
+ tags:
+ - { name: security.voter }
+ - { name: chill.role }
diff --git a/src/Bundle/ChillEventBundle/translations/messages.fr.yml b/src/Bundle/ChillEventBundle/translations/messages.fr.yml
index 60d888f07..bb9f8acd3 100644
--- a/src/Bundle/ChillEventBundle/translations/messages.fr.yml
+++ b/src/Bundle/ChillEventBundle/translations/messages.fr.yml
@@ -81,9 +81,31 @@ Pick an event: Choisir un événement
Pick a type of event: Choisir un type d'événement
Pick a moderator: Choisir un animateur
+# exports
Select a format: Choisir un format
Export: Exporter
+Count events: Nombre d'événements
+Count events by various parameters.: Compte le nombre d'événements selon divers critères
+Exports of events: Exports d'événements
+
+Filtered by event date: Filtrer par date d'événement
+'Filtered by date of event: only between %date_from% and %date_to%': "Filtré par date d'événement: uniquement entre le %date_from% et le %date_to%"
+Events after this date: Événements après cette date
+Events before this date: Événements avant cette date
+Filtered by event type: Filtrer par type d'événement
+'Filtered by event type: only %list%': "Filtré par type: uniquement %list%"
+Group event by date: Grouper par date d'événement
+Group by event type: Grouper par type d'événement
+
+Count event participants: Nombre de participations
+Count participants to an event by various parameters.: Compte le nombre de participations selon divers critères
+Exports of event participants: Exports de participations
+'Filtered by participant roles: only %list%': "Filtré par rôles de participation: uniquement %list%"
+Filter by participant roles: Filtrer par rôles de participation
+Part roles: Rôles de participation
+Group by participant role: Grouper par rôle de participation
+
Events configuration: Configuration des événements
Events configuration menu: Menu des événements
diff --git a/src/Bundle/ChillMainBundle/Command/LoadAddressesLUFromBDAddressCommand.php b/src/Bundle/ChillMainBundle/Command/LoadAddressesLUFromBDAddressCommand.php
new file mode 100644
index 000000000..499751c7c
--- /dev/null
+++ b/src/Bundle/ChillMainBundle/Command/LoadAddressesLUFromBDAddressCommand.php
@@ -0,0 +1,40 @@
+setName('chill:main:address-ref-lux');
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int
+ {
+ $this->addressImporter->import();
+
+ return 0;
+ }
+}
diff --git a/src/Bundle/ChillMainBundle/Service/Import/AddressReferenceLU.php b/src/Bundle/ChillMainBundle/Service/Import/AddressReferenceLU.php
new file mode 100644
index 000000000..d70b5085d
--- /dev/null
+++ b/src/Bundle/ChillMainBundle/Service/Import/AddressReferenceLU.php
@@ -0,0 +1,97 @@
+client->request('GET', $downloadUrl);
+
+ if (200 !== $response->getStatusCode()) {
+ throw new \Exception('Could not download CSV: '.$response->getStatusCode());
+ }
+
+ $file = tmpfile();
+
+ foreach ($this->client->stream($response) as $chunk) {
+ fwrite($file, $chunk->getContent());
+ }
+
+ fseek($file, 0);
+
+ $csv = Reader::createFromStream($file);
+ $csv->setDelimiter(';');
+ $csv->setHeaderOffset(0);
+
+ $this->process_postal_code($csv);
+
+ $this->process_address($csv);
+
+ $this->addressToReferenceMatcher->checkAddressesMatchingReferences();
+
+ fclose($file);
+ }
+
+ private function process_address(Reader $csv): void
+ {
+ $stmt = Statement::create()->process($csv);
+ foreach ($stmt as $record) {
+ $this->addressBaseImporter->importAddress(
+ $record['id_geoportail'],
+ $record['code_postal'],
+ $record['code_postal'],
+ $record['rue'],
+ $record['numero'],
+ 'bd-addresses.lux',
+ (float) $record['lat_wgs84'],
+ (float) $record['lon_wgs84'],
+ 4326
+ );
+ }
+
+ $this->addressBaseImporter->finalize();
+ }
+
+ private function process_postal_code(Reader $csv): void
+ {
+ $stmt = Statement::create()->process($csv);
+ $arr_postal_codes = [];
+ foreach ($stmt as $record) {
+ if (false === \array_key_exists($record['code_postal'], $arr_postal_codes)) {
+ $this->postalCodeBaseImporter->importCode(
+ 'LU',
+ trim((string) $record['localite']),
+ trim((string) $record['code_postal']),
+ trim((string) $record['code_postal']),
+ 'bd-addresses.lux',
+ (float) $record['lat_wgs84'],
+ (float) $record['lon_wgs84'],
+ 4326
+ );
+ $arr_postal_codes[$record['code_postal']] = 1;
+ }
+ }
+ }
+}
diff --git a/src/Bundle/ChillMainBundle/config/services/command.yaml b/src/Bundle/ChillMainBundle/config/services/command.yaml
index 8a2327c0b..94cb2cf97 100644
--- a/src/Bundle/ChillMainBundle/config/services/command.yaml
+++ b/src/Bundle/ChillMainBundle/config/services/command.yaml
@@ -59,6 +59,12 @@ services:
tags:
- { name: console.command }
+ Chill\MainBundle\Command\LoadAddressesLUFromBDAddressCommand:
+ autoconfigure: true
+ autowire: true
+ tags:
+ - { name: console.command }
+
Chill\MainBundle\Command\ExecuteCronJobCommand:
autoconfigure: true
autowire: true