diff --git a/.changes/unreleased/Feature-20250808-120802.yaml b/.changes/unreleased/Feature-20250808-120802.yaml new file mode 100644 index 000000000..50d1eb8ba --- /dev/null +++ b/.changes/unreleased/Feature-20250808-120802.yaml @@ -0,0 +1,6 @@ +kind: Feature +body: Create invitation list in user menu +time: 2025-08-08T12:08:02.446361367+02:00 +custom: + Issue: "385" + SchemaChange: No schema change diff --git a/src/Bundle/ChillCalendarBundle/Controller/CalendarController.php b/src/Bundle/ChillCalendarBundle/Controller/CalendarController.php index 94db03b1f..ae8114ea7 100644 --- a/src/Bundle/ChillCalendarBundle/Controller/CalendarController.php +++ b/src/Bundle/ChillCalendarBundle/Controller/CalendarController.php @@ -266,7 +266,7 @@ class CalendarController extends AbstractController } if (!$this->getUser() instanceof User) { - throw new UnauthorizedHttpException('you are not an user'); + throw new UnauthorizedHttpException('you are not a user'); } $view = '@ChillCalendar/Calendar/listByUser.html.twig'; diff --git a/src/Bundle/ChillCalendarBundle/Controller/MyInvitationsController.php b/src/Bundle/ChillCalendarBundle/Controller/MyInvitationsController.php new file mode 100644 index 000000000..7af5ac18f --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Controller/MyInvitationsController.php @@ -0,0 +1,58 @@ +denyAccessUnlessGranted('ROLE_USER'); + + $user = $this->getUser(); + + if (!$user instanceof User) { + throw new UnauthorizedHttpException('you are not a user'); + } + + $total = count($this->inviteRepository->findBy(['user' => $user])); + $paginator = $this->paginator->create($total); + + $invitations = $this->inviteRepository->findBy( + ['user' => $user], + ['createdAt' => 'DESC'], + $paginator->getItemsPerPage(), + $paginator->getCurrentPageFirstItemNumber() + ); + + $view = '@ChillCalendar/Invitations/listByUser.html.twig'; + + return $this->render($view, [ + 'invitations' => $invitations, + 'paginator' => $paginator, + 'templates' => $this->docGeneratorTemplateRepository->findByEntity(Calendar::class), + ]); + } +} diff --git a/src/Bundle/ChillCalendarBundle/Menu/UserMenuBuilder.php b/src/Bundle/ChillCalendarBundle/Menu/UserMenuBuilder.php index 3a062f7b8..672b53460 100644 --- a/src/Bundle/ChillCalendarBundle/Menu/UserMenuBuilder.php +++ b/src/Bundle/ChillCalendarBundle/Menu/UserMenuBuilder.php @@ -25,6 +25,13 @@ class UserMenuBuilder implements LocalMenuBuilderInterface if ($this->security->isGranted('ROLE_USER')) { $menu->addChild('My calendar list', [ 'route' => 'chill_calendar_calendar_list_my', + ]) + ->setExtras([ + 'order' => 8, + 'icon' => 'tasks', + ]); + $menu->addChild('invite.list.title', [ + 'route' => 'chill_calendar_invitations_list_my', ]) ->setExtras([ 'order' => 9, diff --git a/src/Bundle/ChillCalendarBundle/Repository/InviteRepository.php b/src/Bundle/ChillCalendarBundle/Repository/InviteRepository.php index 8778330f8..6cb34738c 100644 --- a/src/Bundle/ChillCalendarBundle/Repository/InviteRepository.php +++ b/src/Bundle/ChillCalendarBundle/Repository/InviteRepository.php @@ -41,7 +41,7 @@ class InviteRepository implements ObjectRepository /** * @return array|Invite[] */ - public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null) + public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array { return $this->entityRepository->findBy($criteria, $orderBy, $limit, $offset); } diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/_list.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/_list.html.twig index cff5c00cc..01241643b 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/_list.html.twig +++ b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/_list.html.twig @@ -1,240 +1,229 @@ -{# list used in context of person or accompanyingPeriod #} +{# list used in context of person, accompanyingPeriod or user #} -{% if calendarItems|length > 0 %} -
+
+
+
+
+
+
+

+ {% if context == 'person' and calendar.context == 'accompanying_period' %} + + + {{ calendar.accompanyingPeriod.id }} + + + {% endif %} + {% if calendar.endDate.diff(calendar.startDate).days >= 1 %} + {{ calendar.startDate|format_datetime('short', 'short') }} + - {{ calendar.endDate|format_datetime('short', 'short') }} + {% else %} + {{ calendar.startDate|format_datetime('short', 'short') }} + - {{ calendar.endDate|format_datetime('none', 'short') }} + {% endif %} +

- {% for calendar in calendarItems %} - -
-
-
-
-
-
-

- {% if context == 'person' and calendar.context == 'accompanying_period' %} - - - {{ calendar.accompanyingPeriod.id }} - - - {% endif %} - {% if calendar.endDate.diff(calendar.startDate).days >= 1 %} - {{ calendar.startDate|format_datetime('short', 'short') }} - - {{ calendar.endDate|format_datetime('short', 'short') }} - {% else %} - {{ calendar.startDate|format_datetime('short', 'short') }} - - {{ calendar.endDate|format_datetime('none', 'short') }} - {% endif %} -

- -
- - {{ calendar.duration|date('%H:%I') }} - {% if false == calendar.sendSMS or null == calendar.sendSMS %} - - {% else %} - {% if calendar.smsStatus == 'sms_sent' %} - - - - - {% else %} - - - - - {% endif %} - {% endif %} -
- -
-
+
+ + {{ calendar.duration|date('%H:%I') }} + {% if false == calendar.sendSMS or null == calendar.sendSMS %} + + {% else %} + {% if calendar.smsStatus == 'sms_sent' %} + + + + + {% else %} + + + + + {% endif %} + {% endif %}
-
-
    - {% if calendar.mainUser is not empty %} - {{ calendar.mainUser|chill_entity_render_box({'at_date': calendar.startDate}) }} +
+
+
+ +
+
    + {% if calendar.mainUser is not empty %} + {{ calendar.mainUser|chill_entity_render_box({'at_date': calendar.startDate}) }} + {% endif %} +
+
+ +
+
+ + {% if calendar.comment.comment is not empty + or calendar.users|length > 0 + or calendar.thirdParties|length > 0 + or calendar.users|length > 0 %} +
+
+ {% include '@ChillActivity/Activity/concernedGroups.html.twig' with { + 'context': calendar.context == 'person' ? 'calendar_person' : 'calendar_accompanyingCourse', + 'render': 'wrap-list', + 'entity': calendar + } %} +
+ +
+ {% endif %} + + {% if calendar.comment.comment is not empty %} +
+
+ {{ calendar.comment|chill_entity_render_box( { 'limit_lines': 3, 'metadata': false } ) }} +
+
+ {% endif %} + + {% if calendar.location is not empty %} +
+
+ {% if calendar.location.address is not same as(null) and calendar.location.name is not empty %} + {% endif %} + {% if calendar.location.name is not empty %}{{ calendar.location.name }}{% endif %} + {% if calendar.location.address is not same as(null) %}{{ calendar.location.address|chill_entity_render_box({'multiline': false, 'with_picto': (calendar.location.name is empty)}) }}{% else %} + {% endif %} + {% if calendar.location.phonenumber1 is not empty %} {{ calendar.location.phonenumber1|chill_format_phonenumber }}{% endif %} + {% if calendar.location.phonenumber2 is not empty %} {{ calendar.location.phonenumber2|chill_format_phonenumber }}{% endif %} +
+
+ {% endif %} + +
+
+ + {{ include('@ChillCalendar/Calendar/_documents.twig.html') }} +
+
+ + {% if calendar.activity is not null %} +
+
+
+
+

{{ 'Activity'|trans }}

+
+

+ + + {{ calendar.activity.type.name | localize_translatable_string }} + + {% if calendar.activity.emergency %} + {{ 'Emergency'|trans|upper }} + {% endif %} + +

+ +
    +
  • + + {{ 'Created by'|trans }} + {{ calendar.activity.createdBy|chill_entity_render_string({'at_date': calendar.activity.createdAt}) }}, {{ 'on'|trans }} {{ calendar.activity.createdAt|format_datetime('short', 'short') }} + +
  • + {% if is_granted('CHILL_ACTIVITY_SEE', calendar.activity) %} +
  • + +
  • {% endif %}
-
+
- - {% if calendar.comment.comment is not empty - or calendar.users|length > 0 - or calendar.thirdParties|length > 0 - or calendar.users|length > 0 %} -
-
- {% include '@ChillActivity/Activity/concernedGroups.html.twig' with { - 'context': calendar.context == 'person' ? 'calendar_person' : 'calendar_accompanyingCourse', - 'render': 'wrap-list', - 'entity': calendar - } %} -
- -
- {% endif %} - - {% if calendar.comment.comment is not empty %} -
-
- {{ calendar.comment|chill_entity_render_box( { 'limit_lines': 3, 'metadata': false } ) }} -
-
- {% endif %} - - {% if calendar.location is not empty %} -
-
- {% if calendar.location.address is not same as(null) and calendar.location.name is not empty %} - {% endif %} - {% if calendar.location.name is not empty %}{{ calendar.location.name }}{% endif %} - {% if calendar.location.address is not same as(null) %}{{ calendar.location.address|chill_entity_render_box({'multiline': false, 'with_picto': (calendar.location.name is empty)}) }}{% else %} - {% endif %} - {% if calendar.location.phonenumber1 is not empty %} {{ calendar.location.phonenumber1|chill_format_phonenumber }}{% endif %} - {% if calendar.location.phonenumber2 is not empty %} {{ calendar.location.phonenumber2|chill_format_phonenumber }}{% endif %} -
-
- {% endif %} - -
-
- - {{ include('@ChillCalendar/Calendar/_documents.twig.html') }} -
-
- - {% if calendar.activity is not null %} -
-
-
-
-

{{ 'Activity'|trans }}

-
-

- - - {{ calendar.activity.type.name | localize_translatable_string }} - - {% if calendar.activity.emergency %} - {{ 'Emergency'|trans|upper }} - {% endif %} - -

- -
    -
  • - - {{ 'Created by'|trans }} - {{ calendar.activity.createdBy|chill_entity_render_string({'at_date': calendar.activity.createdAt}) }}, {{ 'on'|trans }} {{ calendar.activity.createdAt|format_datetime('short', 'short') }} - -
  • - {% if is_granted('CHILL_ACTIVITY_SEE', calendar.activity) %} -
  • - -
  • - {% endif %} -
- -
-
-
-
-
- {% endif %} - -
-
    - {% if is_granted('CHILL_CALENDAR_DOC_EDIT', calendar) %} - {% if templates|length == 0 %} -
  • - - {{ 'chill_calendar.Add a document'|trans }} - -
  • - {% else %} -
  • - -
  • - {% endif %} - {% endif %} - {% if calendar.activity is null and ( - (calendar.context == 'accompanying_period' and is_granted('CHILL_ACTIVITY_CREATE', calendar.accompanyingPeriod)) - or - (calendar.context == 'person' and is_granted('CHILL_ACTIVITY_CREATE', calendar.person)) - ) - %} -
  • - - {{ 'Transform to activity'|trans }} - -
  • - {% endif %} - - {% if (calendar.isInvited(app.user)) %} - {% set invite = calendar.inviteForUser(app.user) %} -
  • -
    -
  • - {% endif %} - {% if false %} -
  • - -
  • - {% endif %} - {% if is_granted('CHILL_CALENDAR_CALENDAR_EDIT', calendar) %} -
  • - -
  • - {% endif %} - {% if is_granted('CHILL_CALENDAR_CALENDAR_DELETE', calendar) %} -
  • - -
  • - {% endif %} -
- -
-
- {% endfor %} +
+ {% endif %} - {% if calendarItems|length < paginator.getTotalItems %} - {{ chill_pagination(paginator) }} - {% endif %} +
+
    + {% if is_granted('CHILL_CALENDAR_DOC_EDIT', calendar) %} + {% if templates|length == 0 %} +
  • + + {{ 'chill_calendar.Add a document'|trans }} + +
  • + {% else %} +
  • + +
  • + {% endif %} + {% endif %} + {% if calendar.activity is null and ( + (calendar.context == 'accompanying_period' and is_granted('CHILL_ACTIVITY_CREATE', calendar.accompanyingPeriod)) + or + (calendar.context == 'person' and is_granted('CHILL_ACTIVITY_CREATE', calendar.person)) + ) + %} +
  • + + {{ 'Transform to activity'|trans }} + +
  • + {% endif %} + + {% if (calendar.isInvited(app.user)) %} + {% set invite = calendar.inviteForUser(app.user) %} +
  • +
    +
  • + {% endif %} + {% if false %} +
  • + +
  • + {% endif %} + {% if is_granted('CHILL_CALENDAR_CALENDAR_EDIT', calendar) %} +
  • + +
  • + {% endif %} + {% if is_granted('CHILL_CALENDAR_CALENDAR_DELETE', calendar) %} +
  • + +
  • + {% endif %} +
-{% endif %} + +
+ + diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/listByAccompanyingCourse.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/listByAccompanyingCourse.html.twig index 7ce1003bc..96ddb3388 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/listByAccompanyingCourse.html.twig +++ b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/listByAccompanyingCourse.html.twig @@ -34,7 +34,18 @@ {% endif %}

{% else %} - {{ include('@ChillCalendar/Calendar/_list.html.twig', {context: 'accompanying_course'}) }} + {% if calendarItems|length > 0 %} +
+ {% for calendar in calendarItems %} + {{ include('@ChillCalendar/Calendar/_list.html.twig', {context: 'accompanying_course'}) }} + {% endfor %} +
+ + {% if calendarItems|length < paginator.getTotalItems %} + {{ chill_pagination(paginator) }} + {% endif %} + + {% endif %} {% endif %}
    diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/listByPerson.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/listByPerson.html.twig index 9e3b59d2a..dc44b721c 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/listByPerson.html.twig +++ b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/listByPerson.html.twig @@ -33,7 +33,17 @@ {% endif %}

    {% else %} - {{ include ('@ChillCalendar/Calendar/_list.html.twig', {context: 'person'}) }} + {% if calendarItems|length > 0 %} +
    + {% for calendar in calendarItems %} + {{ include ('@ChillCalendar/Calendar/_list.html.twig', {context: 'person'}) }} + {% endfor %} +
    + + {% if calendarItems|length < paginator.getTotalItems %} + {{ chill_pagination(paginator) }} + {% endif %} + {% endif %} {% endif %}
      diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/Invitations/listByUser.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/Invitations/listByUser.html.twig new file mode 100644 index 000000000..c7b7ecc86 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/views/Invitations/listByUser.html.twig @@ -0,0 +1,40 @@ +{% extends "@ChillMain/layout.html.twig" %} + +{% set activeRouteKey = 'chill_calendar_invitations_list' %} + +{% block title %}{{ 'invite.list.title'|trans }}{% endblock title %} + +{% block content %} + +

      {{ 'invite.list.title'|trans }}

      + + {% if invitations|length == 0 %} +

      + {{ "invite.list.none"|trans }} +

      + {% else %} +
      + {% for invitation in invitations %} + {% set calendar = invitation.getCalendar %} + {{ include('@ChillCalendar/Calendar/_list.html.twig', {context: 'user'}) }} + {% endfor %} +
      + + {% if invitations|length < paginator.getTotalItems %} + {{ chill_pagination(paginator) }} + {% endif %} + {% endif %} + +{% endblock %} + +{% block js %} + {{ parent() }} + {{ encore_entry_script_tags('mod_answer') }} + {{ encore_entry_script_tags('mod_document_action_buttons_group') }} +{% endblock %} + +{% block css %} + {{ parent() }} + {{ encore_entry_link_tags('mod_answer') }} + {{ encore_entry_link_tags('mod_document_action_buttons_group') }} +{% endblock %} diff --git a/src/Bundle/ChillCalendarBundle/Tests/Controller/MyInvitationsControllerTest.php b/src/Bundle/ChillCalendarBundle/Tests/Controller/MyInvitationsControllerTest.php new file mode 100644 index 000000000..32ed70456 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Tests/Controller/MyInvitationsControllerTest.php @@ -0,0 +1,292 @@ +prophesize(InviteRepository::class); + $paginatorFactory = $this->prophesize(PaginatorFactory::class); + $docGeneratorTemplateRepository = $this->prophesize(DocGeneratorTemplateRepositoryInterface::class); + + // Create controller instance + $this->controller = new MyInvitationsController( + $inviteRepository->reveal(), + $paginatorFactory->reveal(), + $docGeneratorTemplateRepository->reveal() + ); + + // Set up necessary services for AbstractController + $authorizationChecker = $this->prophesize(AuthorizationCheckerInterface::class); + $tokenStorage = $this->prophesize(TokenStorageInterface::class); + $twig = $this->prophesize(Environment::class); + + // Use reflection to set the container + $reflection = new \ReflectionClass($this->controller); + $containerProperty = $reflection->getParentClass()->getProperty('container'); + $containerProperty->setAccessible(true); + + // Create a mock container + $container = $this->prophesize(\Psr\Container\ContainerInterface::class); + $container->has('security.authorization_checker')->willReturn(true); + $container->get('security.authorization_checker')->willReturn($authorizationChecker->reveal()); + $container->has('security.token_storage')->willReturn(true); + $container->get('security.token_storage')->willReturn($tokenStorage->reveal()); + $container->has('twig')->willReturn(true); + $container->get('twig')->willReturn($twig->reveal()); + + $containerProperty->setValue($this->controller, $container->reveal()); + } + + public function testMyInvitationsReturnsCorrectAmountOfInvitations(): void + { + // Create test user + $user = new User(); + $user->setUsername('testuser'); + + // Create test invitations + $invite1 = new Invite(); + $invite1->setUser($user); + $invite1->setStatus(Invite::PENDING); + + $invite2 = new Invite(); + $invite2->setUser($user); + $invite2->setStatus(Invite::ACCEPTED); + + $invite3 = new Invite(); + $invite3->setUser($user); + $invite3->setStatus(Invite::DECLINED); + + $allInvitations = [$invite1, $invite2, $invite3]; + $paginatedInvitations = [$invite1, $invite2]; // First page with 2 items per page + + // Set up repository prophecies + $inviteRepository = $this->prophesize(InviteRepository::class); + $inviteRepository->findBy(['user' => $user])->willReturn($allInvitations); + $inviteRepository->findBy( + ['user' => $user], + ['createdAt' => 'DESC'], + 2, // items per page + 0 // offset + )->willReturn($paginatedInvitations); + + // Set up paginator prophecies + $paginator = $this->prophesize(PaginatorInterface::class); + $paginator->getItemsPerPage()->willReturn(2); + $paginator->getCurrentPageFirstItemNumber()->willReturn(0); + + $paginatorFactory = $this->prophesize(PaginatorFactory::class); + $paginatorFactory->create(3)->willReturn($paginator->reveal()); + + // Set up doc generator repository + $docGeneratorTemplateRepository = $this->prophesize(DocGeneratorTemplateRepositoryInterface::class); + $docGeneratorTemplateRepository->findByEntity(Calendar::class)->willReturn([]); + + // Create controller with mocked dependencies + $controller = new MyInvitationsController( + $inviteRepository->reveal(), + $paginatorFactory->reveal(), + $docGeneratorTemplateRepository->reveal() + ); + + // Set up authorization checker to return true for ROLE_USER + $authorizationChecker = $this->prophesize(AuthorizationCheckerInterface::class); + $authorizationChecker->isGranted('ROLE_USER', null)->willReturn(true); + + // Set up token storage to return user + $token = $this->prophesize(TokenInterface::class); + $token->getUser()->willReturn($user); + $tokenStorage = $this->prophesize(TokenStorageInterface::class); + $tokenStorage->getToken()->willReturn($token->reveal()); + + // Set up twig to return a response + $twig = $this->prophesize(Environment::class); + $twig->render('@ChillCalendar/Invitations/listByUser.html.twig', [ + 'invitations' => $paginatedInvitations, + 'paginator' => $paginator->reveal(), + 'templates' => [], + ])->willReturn('rendered content'); + + // Set up container + $container = $this->prophesize(\Psr\Container\ContainerInterface::class); + $container->has('security.authorization_checker')->willReturn(true); + $container->get('security.authorization_checker')->willReturn($authorizationChecker->reveal()); + $container->has('security.token_storage')->willReturn(true); + $container->get('security.token_storage')->willReturn($tokenStorage->reveal()); + $container->has('twig')->willReturn(true); + $container->get('twig')->willReturn($twig->reveal()); + + // Use reflection to set the container + $reflection = new \ReflectionClass($controller); + $containerProperty = $reflection->getParentClass()->getProperty('container'); + $containerProperty->setAccessible(true); + $containerProperty->setValue($controller, $container->reveal()); + + // Create request + $request = new Request(); + + // Execute the action + $response = $controller->myInvitations($request); + + // Assert that response is successful + self::assertInstanceOf(Response::class, $response); + self::assertSame(200, $response->getStatusCode()); + self::assertSame('rendered content', $response->getContent()); + } + + public function testMyInvitationsPageLoads(): void + { + // Create test user + $user = new User(); + $user->setUsername('testuser'); + + // Set up repository prophecies - no invitations + $inviteRepository = $this->prophesize(InviteRepository::class); + $inviteRepository->findBy(['user' => $user])->willReturn([]); + $inviteRepository->findBy( + ['user' => $user], + ['createdAt' => 'DESC'], + 20, // default items per page + 0 // offset + )->willReturn([]); + + // Set up paginator prophecies + $paginator = $this->prophesize(PaginatorInterface::class); + $paginator->getItemsPerPage()->willReturn(20); + $paginator->getCurrentPageFirstItemNumber()->willReturn(0); + + $paginatorFactory = $this->prophesize(PaginatorFactory::class); + $paginatorFactory->create(0)->willReturn($paginator->reveal()); + + // Set up doc generator repository + $docGeneratorTemplateRepository = $this->prophesize(DocGeneratorTemplateRepositoryInterface::class); + $docGeneratorTemplateRepository->findByEntity(Calendar::class)->willReturn([]); + + // Create controller with mocked dependencies + $controller = new MyInvitationsController( + $inviteRepository->reveal(), + $paginatorFactory->reveal(), + $docGeneratorTemplateRepository->reveal() + ); + + // Set up authorization checker to return true for ROLE_USER + $authorizationChecker = $this->prophesize(AuthorizationCheckerInterface::class); + $authorizationChecker->isGranted('ROLE_USER', null)->willReturn(true); + + // Set up token storage to return user + $token = $this->prophesize(TokenInterface::class); + $token->getUser()->willReturn($user); + $tokenStorage = $this->prophesize(TokenStorageInterface::class); + $tokenStorage->getToken()->willReturn($token->reveal()); + + // Set up twig to return a response + $twig = $this->prophesize(Environment::class); + $twig->render('@ChillCalendar/Invitations/listByUser.html.twig', [ + 'invitations' => [], + 'paginator' => $paginator->reveal(), + 'templates' => [], + ])->willReturn('empty page content'); + + // Set up container + $container = $this->prophesize(\Psr\Container\ContainerInterface::class); + $container->has('security.authorization_checker')->willReturn(true); + $container->get('security.authorization_checker')->willReturn($authorizationChecker->reveal()); + $container->has('security.token_storage')->willReturn(true); + $container->get('security.token_storage')->willReturn($tokenStorage->reveal()); + $container->has('twig')->willReturn(true); + $container->get('twig')->willReturn($twig->reveal()); + + // Use reflection to set the container + $reflection = new \ReflectionClass($controller); + $containerProperty = $reflection->getParentClass()->getProperty('container'); + $containerProperty->setAccessible(true); + $containerProperty->setValue($controller, $container->reveal()); + + // Create request + $request = new Request(); + + // Execute the action + $response = $controller->myInvitations($request); + + // Assert that page loads successfully + self::assertInstanceOf(Response::class, $response); + self::assertSame(200, $response->getStatusCode()); + self::assertSame('empty page content', $response->getContent()); + } + + public function testMyInvitationsRequiresAuthentication(): void + { + // Create controller with minimal dependencies + $inviteRepository = $this->prophesize(InviteRepository::class); + $paginatorFactory = $this->prophesize(PaginatorFactory::class); + $docGeneratorTemplateRepository = $this->prophesize(DocGeneratorTemplateRepositoryInterface::class); + + $controller = new MyInvitationsController( + $inviteRepository->reveal(), + $paginatorFactory->reveal(), + $docGeneratorTemplateRepository->reveal() + ); + + // Set up authorization checker to return false for ROLE_USER + $authorizationChecker = $this->prophesize(AuthorizationCheckerInterface::class); + $authorizationChecker->isGranted('ROLE_USER')->willReturn(false); + $authorizationChecker->isGranted('ROLE_USER', null)->willReturn(false); + + // Set up container + $container = $this->prophesize(\Psr\Container\ContainerInterface::class); + $container->has('security.authorization_checker')->willReturn(true); + $container->get('security.authorization_checker')->willReturn($authorizationChecker->reveal()); + + // Use reflection to set the container + $reflection = new \ReflectionClass($controller); + $containerProperty = $reflection->getParentClass()->getProperty('container'); + $containerProperty->setAccessible(true); + $containerProperty->setValue($controller, $container->reveal()); + + // Create request + $request = new Request(); + + // Expect AccessDeniedException + $this->expectException(\Symfony\Component\Security\Core\Exception\AccessDeniedException::class); + + // Execute the action + $controller->myInvitations($request); + } +} diff --git a/src/Bundle/ChillCalendarBundle/translations/messages.fr.yml b/src/Bundle/ChillCalendarBundle/translations/messages.fr.yml index cfb2fe057..179597346 100644 --- a/src/Bundle/ChillCalendarBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillCalendarBundle/translations/messages.fr.yml @@ -86,6 +86,9 @@ invite: declined: Refusé pending: En attente tentative: Accepté provisoirement + list: + none: Il n'y aucun invitation + title: Mes invitations # exports Exports of calendar: Exports des rendez-vous diff --git a/src/Bundle/ChillDocGeneratorBundle/Repository/DocGeneratorTemplateRepositoryInterface.php b/src/Bundle/ChillDocGeneratorBundle/Repository/DocGeneratorTemplateRepositoryInterface.php index e5071e76a..f2e3cf629 100644 --- a/src/Bundle/ChillDocGeneratorBundle/Repository/DocGeneratorTemplateRepositoryInterface.php +++ b/src/Bundle/ChillDocGeneratorBundle/Repository/DocGeneratorTemplateRepositoryInterface.php @@ -20,4 +20,9 @@ use Doctrine\Persistence\ObjectRepository; interface DocGeneratorTemplateRepositoryInterface extends ObjectRepository { public function countByEntity(string $entity): int; + + /** + * @return array|DocGeneratorTemplate[] + */ + public function findByEntity(string $entity, ?int $start = 0, ?int $limit = 50): array; } diff --git a/src/Bundle/ChillMainBundle/Pagination/PaginatorFactory.php b/src/Bundle/ChillMainBundle/Pagination/PaginatorFactory.php index 9a809287b..fa4897224 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. */ -final readonly class PaginatorFactory implements PaginatorFactoryInterface +class PaginatorFactory implements PaginatorFactoryInterface { final public const DEFAULT_CURRENT_PAGE_KEY = 'page'; @@ -29,16 +29,16 @@ final readonly class PaginatorFactory implements PaginatorFactoryInterface /** * the request stack. */ - private RequestStack $requestStack, + private readonly RequestStack $requestStack, /** * the router and generator for url. */ - private RouterInterface $router, + private readonly RouterInterface $router, /** * the default item per page. This may be overriden by * the request or inside the paginator. */ - private int $itemPerPage = 20, + private readonly int $itemPerPage = 20, ) {} /**