diff --git a/src/Bundle/ChillMainBundle/Controller/SavedExportController.php b/src/Bundle/ChillMainBundle/Controller/SavedExportController.php index 753cb782d..8e08f67d6 100644 --- a/src/Bundle/ChillMainBundle/Controller/SavedExportController.php +++ b/src/Bundle/ChillMainBundle/Controller/SavedExportController.php @@ -18,6 +18,7 @@ use Chill\MainBundle\Export\ExportInterface; use Chill\MainBundle\Export\ExportManager; use Chill\MainBundle\Export\GroupedExportInterface; use Chill\MainBundle\Form\SavedExportType; +use Chill\MainBundle\Repository\ExportGenerationRepository; use Chill\MainBundle\Repository\SavedExportRepositoryInterface; use Chill\MainBundle\Security\Authorization\ExportGenerationVoter; use Chill\MainBundle\Security\Authorization\SavedExportVoter; @@ -46,6 +47,7 @@ final readonly class SavedExportController private Security $security, private TranslatorInterface $translator, private UrlGeneratorInterface $urlGenerator, + private ExportGenerationRepository $exportGenerationRepository, ) {} #[Route(path: '/{_locale}/exports/saved/{id}/delete', name: 'chill_main_export_saved_delete')] @@ -172,7 +174,10 @@ final readonly class SavedExportController throw new AccessDeniedHttpException(); } - $exports = $this->savedExportRepository->findByUser($user, ['title' => 'ASC']); + $exports = array_filter( + $this->savedExportRepository->findSharedWithUser($user, ['exportAlias' => 'ASC', 'title' => 'ASC']), + fn (SavedExport $savedExport): bool => $this->security->isGranted(SavedExportVoter::GENERATE, $savedExport), + ); // group by center /** @var array $exportsGrouped */ @@ -189,12 +194,20 @@ final readonly class SavedExportController ksort($exportsGrouped); + // get last executions + $lastExecutions = []; + foreach ($exports as $savedExport) { + $lastExecutions[$savedExport->getId()->toString()] = $this->exportGenerationRepository + ->findExportGenerationBySavedExportAndUser($savedExport, $user, 5); + } + return new Response( $this->templating->render( '@ChillMain/SavedExport/index.html.twig', [ 'grouped_exports' => $exportsGrouped, 'total' => \count($exports), + 'last_executions' => $lastExecutions, ], ), ); diff --git a/src/Bundle/ChillMainBundle/Repository/SavedExportRepository.php b/src/Bundle/ChillMainBundle/Repository/SavedExportRepository.php index 702fdf7d6..63b68bd5d 100644 --- a/src/Bundle/ChillMainBundle/Repository/SavedExportRepository.php +++ b/src/Bundle/ChillMainBundle/Repository/SavedExportRepository.php @@ -13,8 +13,10 @@ namespace Chill\MainBundle\Repository; use Chill\MainBundle\Entity\SavedExport; use Chill\MainBundle\Entity\User; +use Chill\MainBundle\Entity\UserGroup; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; +use Doctrine\ORM\QueryBuilder; use Doctrine\Persistence\ObjectRepository; /** @@ -55,6 +57,30 @@ class SavedExportRepository implements SavedExportRepositoryInterface ->where($qb->expr()->eq('se.user', ':user')) ->setParameter('user', $user); + return $this->prepareResult($qb, $orderBy, $limit, $offset); + } + + public function findSharedWithUser(User $user, ?array $orderBy = [], ?int $limit = null, ?int $offset = null): array + { + $qb = $this->repository->createQueryBuilder('se'); + + $qb + ->where( + $qb->expr()->orX( + $qb->expr()->eq('se.user', ':user'), + $qb->expr()->isMemberOf(':user', 'se.sharedWithUsers'), + $qb->expr()->exists( + sprintf('SELECT 1 FROM %s ug WHERE ug MEMBER OF se.sharedWithGroups AND :user MEMBER OF ug.users', UserGroup::class) + ) + ) + ) + ->setParameter('user', $user); + + return $this->prepareResult($qb, $orderBy, $limit, $offset); + } + + private function prepareResult(QueryBuilder $qb, ?array $orderBy = [], ?int $limit = null, ?int $offset = null): array + { if (null !== $limit) { $qb->setMaxResults($limit); } diff --git a/src/Bundle/ChillMainBundle/Repository/SavedExportRepositoryInterface.php b/src/Bundle/ChillMainBundle/Repository/SavedExportRepositoryInterface.php index 3b168505f..d9bc1d42e 100644 --- a/src/Bundle/ChillMainBundle/Repository/SavedExportRepositoryInterface.php +++ b/src/Bundle/ChillMainBundle/Repository/SavedExportRepositoryInterface.php @@ -34,6 +34,8 @@ interface SavedExportRepositoryInterface extends ObjectRepository */ public function findByUser(User $user, ?array $orderBy = [], ?int $limit = null, ?int $offset = null): array; + public function findSharedWithUser(User $user, ?array $orderBy = [], ?int $limit = null, ?int $offset = null): array; + public function findOneBy(array $criteria): ?SavedExport; public function getClassName(): string; diff --git a/src/Bundle/ChillMainBundle/Resources/views/Dev/dev.assets.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Dev/dev.assets.html.twig index 5aafb6635..b7b91a246 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Dev/dev.assets.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Dev/dev.assets.html.twig @@ -392,4 +392,24 @@ Toutes les classes btn-* de bootstrap sont fonctionnelles + +

Badges

+ +

+ Primary + Secondary + Success + Danger + Warning + Info + Light + Dark + chill-blue + chill-green + chill-yellow + chill-orange + chill-red + chill-beige +

+ {% endblock %} diff --git a/src/Bundle/ChillMainBundle/Resources/views/Export/_navbar.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Export/_navbar.html.twig index a7d7216b4..ae62a6313 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Export/_navbar.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Export/_navbar.html.twig @@ -6,7 +6,7 @@ - \ No newline at end of file + diff --git a/src/Bundle/ChillMainBundle/Resources/views/SavedExport/index.html.twig b/src/Bundle/ChillMainBundle/Resources/views/SavedExport/index.html.twig index d354cd2ae..be5970d5c 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/SavedExport/index.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/SavedExport/index.html.twig @@ -1,14 +1,74 @@ {% extends "@ChillMain/layout.html.twig" %} {% block css %} + {{ parent() }} {{ encore_entry_link_tags('mod_saved_export_button') }} + {% endblock %} {% block js %} + {{ parent() }} {{ encore_entry_script_tags('mod_saved_export_button') }} {% endblock %} -{% block title %}{{ 'saved_export.My saved exports'|trans }}{% endblock %} +{% block title %}{{ 'saved_export.Saved exports'|trans }}{% endblock %} + +{% macro render_export_card(saved, export, export_alias, generations) %} +
+
+
+ {{ export.title|trans }} +
+
+

{{ saved.title }}

+ {% if app.user is same as saved.createdBy %} +

+ {% if app.user is same as saved.createdBy %}{{ 'saved_export.Owner'|trans }}{% endif %} +

+ {% endif %} +

{{ saved.description|chill_markdown_to_html }}

+
+ {% if generations|length > 0 %} +
    + {% for generation in generations %} +
  • + {{ generation.createdAt|format_datetime('short', 'short') }} + {% if generation.status == 'pending' %} +  {{ 'export.generation.Export generation is pending_short'|trans }} + {% elseif generation.status == 'failure' %} +  {{ 'export.generation.Error_short'|trans }} + {% endif %} +
  • + {% endfor %} +
+ {% endif %} + +
+
+{% endmacro %} {% block content %}
@@ -23,73 +83,23 @@ {% for group, saveds in grouped_exports %} {% if group != '_' %} -

{{ group }}

-
+

{{ group }}

+
{% for s in saveds %} -
-
-

{{ s.export.title|trans }}

-

{{ s.saved.title }}

- -
{{ 'saved_export.Created on %date%'|trans({'%date%': s.saved.createdAt|format_datetime('long', 'short')}) }}
- -
- {{ s.saved.description|chill_markdown_to_html }} -
- - -
-
+ {{ _self.render_export_card(s.saved, s.export, s.saved.exportAlias, last_executions[s.saved.id.toString()]) }} {% endfor %}
{% endif %} {% endfor %} {% if grouped_exports|keys|length > 1 and grouped_exports['_']|default([])|length > 0 %} -

{{ 'Ungrouped exports'|trans }}

+

{{ 'Ungrouped exports'|trans }}

{% endif %}
{% for saveds in grouped_exports['_']|default([]) %} {% for s in saveds %} -
-
-

{{ s.export.title|trans }}

-

{{ s.saved.title }}

- -
{{ 'saved_export.Created on %date%'|trans({'%date%': s.saved.createdAt|format_datetime('long', 'short')}) }}
- -
- {{ s.saved.description|chill_markdown_to_html }} -
- -
-
    -
  • -
  • -
  • -
-
-
-
+ {{ _self.render_export_card(s.saved, s.export, s.saved.exportAlias, last_executions[s.saved.id.toString()]) }} {% endfor %} {% endfor %}
diff --git a/src/Bundle/ChillMainBundle/Security/Authorization/ChillExportVoter.php b/src/Bundle/ChillMainBundle/Security/Authorization/ChillExportVoter.php index 10552d1f7..a3db10580 100644 --- a/src/Bundle/ChillMainBundle/Security/Authorization/ChillExportVoter.php +++ b/src/Bundle/ChillMainBundle/Security/Authorization/ChillExportVoter.php @@ -11,6 +11,9 @@ declare(strict_types=1); namespace Chill\MainBundle\Security\Authorization; +use Chill\MainBundle\Export\DirectExportInterface; +use Chill\MainBundle\Export\ExportInterface; +use Chill\MainBundle\Export\ExportManager; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authorization\Voter\Voter; @@ -20,7 +23,7 @@ class ChillExportVoter extends Voter private readonly VoterHelperInterface $helper; - public function __construct(VoterHelperFactoryInterface $voterHelperFactory) + public function __construct(VoterHelperFactoryInterface $voterHelperFactory, ExportManager $exportManager) { $this->helper = $voterHelperFactory ->generate(self::class) @@ -30,6 +33,13 @@ class ChillExportVoter extends Voter protected function supports($attribute, $subject): bool { + if ( + ($subject instanceof ExportInterface or $subject instanceof DirectExportInterface) + && $attribute === $subject->requiredRole() + ) { + return true; + } + return $this->helper->supports($attribute, $subject); } diff --git a/src/Bundle/ChillMainBundle/translations/messages.fr.yml b/src/Bundle/ChillMainBundle/translations/messages.fr.yml index 10f106e95..c608f1fd6 100644 --- a/src/Bundle/ChillMainBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillMainBundle/translations/messages.fr.yml @@ -787,7 +787,7 @@ saved_export: Edit: Modifier un export enregistré Delete saved ?: Supprimer un export enregistré ? Are you sure you want to delete this saved ?: Êtes-vous sûr·e de vouloir supprimer cet export ? - My saved exports: Mes exports enregistrés + Saved exports: Exports enregistrés Export is deleted: L'export est supprimé Saved export is saved!: L'export est enregistré Created on %date%: Créé le %date% @@ -795,6 +795,7 @@ saved_export: update_filters_aggregators_and_execute: Modifier les filtres et regroupements et télécharger execute: Générer Update existing: Mettre à jour le rapport enregistré existant + Owner: Propriétaire absence: # single letter for absence