Feature: [calendar] allow to create and generate calendar by person

This commit is contained in:
Julien Fastré 2022-10-21 13:24:02 +02:00
parent 43dcb46d38
commit bc1a7c1d7b
15 changed files with 677 additions and 245 deletions

View File

@ -29,7 +29,7 @@
{% endmacro %} {% endmacro %}
{% set blocks = [] %} {% set blocks = [] %}
{% if context == 'calendar_accompanyingCourse' or entity.activityType.personsVisible %} {% if context == 'calendar_accompanyingCourse' or context == 'calendar_person' or entity.activityType.personsVisible %}
{% if context == 'person' %} {% if context == 'person' %}
{% set blocks = blocks|merge([{ {% set blocks = blocks|merge([{
'title': 'Others persons'|trans, 'title': 'Others persons'|trans,
@ -54,7 +54,7 @@
}]) %} }]) %}
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if context == 'calendar_accompanyingCourse' or entity.activityType.thirdPartiesVisible %} {% if context == 'calendar_accompanyingCourse' or context == 'calendar_person' or entity.activityType.thirdPartiesVisible %}
{% set blocks = blocks|merge([{ {% set blocks = blocks|merge([{
'title': 'Third parties'|trans, 'title': 'Third parties'|trans,
'items': entity.thirdParties, 'items': entity.thirdParties,
@ -63,7 +63,7 @@
'key' : 'id', 'key' : 'id',
}]) %} }]) %}
{% endif %} {% endif %}
{% if context == 'calendar_accompanyingCourse' or entity.activityType.usersVisible %} {% if context == 'calendar_accompanyingCourse' or context == 'calendar_person' or entity.activityType.usersVisible %}
{% set blocks = blocks|merge([{ {% set blocks = blocks|merge([{
'title': 'Users concerned'|trans, 'title': 'Users concerned'|trans,
'items': entity.users, 'items': entity.users,
@ -143,7 +143,7 @@
{% if bloc.type == 'user' %} {% if bloc.type == 'user' %}
<span class="badge-user"> <span class="badge-user">
{{ item|chill_entity_render_box({'render': 'raw', 'addAltNames': false }) }} {{ item|chill_entity_render_box({'render': 'raw', 'addAltNames': false }) }}
{%- if context == 'calendar_accompanyingCourse' %} {%- if context == 'calendar_accompanyingCourse' or context == 'calendar_person' %}
{% set invite = entity.inviteForUser(item) %} {% set invite = entity.inviteForUser(item) %}
{% if invite is not null %} {% if invite is not null %}
{{ invite.invite_span(invite) }} {{ invite.invite_span(invite) }}

View File

@ -18,15 +18,19 @@ use Chill\CalendarBundle\Repository\CalendarACLAwareRepositoryInterface;
use Chill\DocGeneratorBundle\Repository\DocGeneratorTemplateRepository; use Chill\DocGeneratorBundle\Repository\DocGeneratorTemplateRepository;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Pagination\PaginatorFactory; use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Repository\UserRepository;
use Chill\MainBundle\Templating\Listing\FilterOrderHelper; use Chill\MainBundle\Templating\Listing\FilterOrderHelper;
use Chill\MainBundle\Templating\Listing\FilterOrderHelperFactoryInterface; use Chill\MainBundle\Templating\Listing\FilterOrderHelperFactoryInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Repository\AccompanyingPeriodRepository;
use Chill\PersonBundle\Repository\PersonRepository;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Chill\ThirdPartyBundle\Entity\ThirdParty; use Chill\ThirdPartyBundle\Entity\ThirdParty;
use DateTimeImmutable; use DateTimeImmutable;
use Exception; use Exception;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use RuntimeException;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Form; use Symfony\Component\Form\Form;
@ -40,6 +44,8 @@ use Symfony\Component\Serializer\SerializerInterface;
class CalendarController extends AbstractController class CalendarController extends AbstractController
{ {
private AccompanyingPeriodRepository $accompanyingPeriodRepository;
private CalendarACLAwareRepositoryInterface $calendarACLAwareRepository; private CalendarACLAwareRepositoryInterface $calendarACLAwareRepository;
private DocGeneratorTemplateRepository $docGeneratorTemplateRepository; private DocGeneratorTemplateRepository $docGeneratorTemplateRepository;
@ -50,12 +56,12 @@ class CalendarController extends AbstractController
private PaginatorFactory $paginator; private PaginatorFactory $paginator;
private PersonRepository $personRepository;
private RemoteCalendarConnectorInterface $remoteCalendarConnector; private RemoteCalendarConnectorInterface $remoteCalendarConnector;
private SerializerInterface $serializer; private SerializerInterface $serializer;
private UserRepository $userRepository;
public function __construct( public function __construct(
CalendarACLAwareRepositoryInterface $calendarACLAwareRepository, CalendarACLAwareRepositoryInterface $calendarACLAwareRepository,
DocGeneratorTemplateRepository $docGeneratorTemplateRepository, DocGeneratorTemplateRepository $docGeneratorTemplateRepository,
@ -64,7 +70,8 @@ class CalendarController extends AbstractController
PaginatorFactory $paginator, PaginatorFactory $paginator,
RemoteCalendarConnectorInterface $remoteCalendarConnector, RemoteCalendarConnectorInterface $remoteCalendarConnector,
SerializerInterface $serializer, SerializerInterface $serializer,
UserRepository $userRepository PersonRepository $personRepository,
AccompanyingPeriodRepository $accompanyingPeriodRepository
) { ) {
$this->calendarACLAwareRepository = $calendarACLAwareRepository; $this->calendarACLAwareRepository = $calendarACLAwareRepository;
$this->docGeneratorTemplateRepository = $docGeneratorTemplateRepository; $this->docGeneratorTemplateRepository = $docGeneratorTemplateRepository;
@ -73,7 +80,8 @@ class CalendarController extends AbstractController
$this->paginator = $paginator; $this->paginator = $paginator;
$this->remoteCalendarConnector = $remoteCalendarConnector; $this->remoteCalendarConnector = $remoteCalendarConnector;
$this->serializer = $serializer; $this->serializer = $serializer;
$this->userRepository = $userRepository; $this->personRepository = $personRepository;
$this->accompanyingPeriodRepository = $accompanyingPeriodRepository;
} }
/** /**
@ -83,19 +91,21 @@ class CalendarController extends AbstractController
*/ */
public function deleteAction(Request $request, Calendar $entity) public function deleteAction(Request $request, Calendar $entity)
{ {
$view = null;
$em = $this->getDoctrine()->getManager(); $em = $this->getDoctrine()->getManager();
$accompanyingPeriod = $entity->getAccompanyingPeriod(); [$person, $accompanyingPeriod] = [$entity->getPerson(), $entity->getAccompanyingPeriod()];
$user = null; // TODO legacy code ? remove it ?
if ($accompanyingPeriod instanceof AccompanyingPeriod) { if ($accompanyingPeriod instanceof AccompanyingPeriod) {
$view = '@ChillCalendar/Calendar/confirm_deleteByAccompanyingCourse.html.twig'; $view = '@ChillCalendar/Calendar/confirm_deleteByAccompanyingCourse.html.twig';
} elseif ($user instanceof User) { $redirectRoute = $this->generateUrl('chill_calendar_calendar_list_by_period', ['id' => $accompanyingPeriod->getId()]);
$view = '@ChillCalendar/Calendar/confirm_deleteByUser.html.twig'; } elseif ($person instanceof Person) {
$view = '@ChillCalendar/Calendar/confirm_deleteByPerson.html.twig';
$redirectRoute = $this->generateUrl('chill_calendar_calendar_list_by_person', ['id' => $person->getId()]);
} else {
throw new RuntimeException('nor person or accompanying period');
} }
$form = $this->createDeleteForm($entity->getId(), $user, $accompanyingPeriod); $form = $this->createDeleteForm($entity);
if ($request->getMethod() === Request::METHOD_DELETE) { if ($request->getMethod() === Request::METHOD_DELETE) {
$form->handleRequest($request); $form->handleRequest($request);
@ -112,20 +122,15 @@ class CalendarController extends AbstractController
$this->addFlash('success', $this->get('translator') $this->addFlash('success', $this->get('translator')
->trans('The calendar item has been successfully removed.')); ->trans('The calendar item has been successfully removed.'));
$params = $this->buildParamsToUrl($user, $accompanyingPeriod); return new RedirectResponse($redirectRoute);
return $this->redirectToRoute('chill_calendar_calendar_list_by_period', $params);
} }
} }
if (null === $view) {
throw $this->createNotFoundException('Template not found');
}
return $this->render($view, [ return $this->render($view, [
'calendar' => $entity, 'calendar' => $entity,
'delete_form' => $form->createView(), 'delete_form' => $form->createView(),
'accompanyingCourse' => $accompanyingPeriod, 'accompanyingCourse' => $accompanyingPeriod,
'person' => $person,
]); ]);
} }
@ -143,13 +148,16 @@ class CalendarController extends AbstractController
$view = null; $view = null;
$em = $this->getDoctrine()->getManager(); $em = $this->getDoctrine()->getManager();
[$user, $accompanyingPeriod] = $this->getEntity($request); [$person, $accompanyingPeriod] = [$entity->getPerson(), $entity->getAccompanyingPeriod()];
if ($accompanyingPeriod instanceof AccompanyingPeriod) { if ($accompanyingPeriod instanceof AccompanyingPeriod) {
$view = '@ChillCalendar/Calendar/editByAccompanyingCourse.html.twig'; $view = '@ChillCalendar/Calendar/editByAccompanyingCourse.html.twig';
} elseif ($user instanceof User) { $redirectRoute = $this->generateUrl('chill_calendar_calendar_list_by_period', ['id' => $accompanyingPeriod->getId()]);
throw new Exception('to analyze'); } elseif ($person instanceof Person) {
$view = '@ChillCalendar/Calendar/editByUser.html.twig'; $view = '@ChillCalendar/Calendar/editByPerson.html.twig';
$redirectRoute = $this->generateUrl('chill_calendar_calendar_list_by_person', ['id' => $person->getId()]);
} else {
throw new RuntimeException('no person nor accompanying period');
} }
$form = $this->createForm(CalendarType::class, $entity) $form = $this->createForm(CalendarType::class, $entity)
@ -163,33 +171,24 @@ class CalendarController extends AbstractController
$this->addFlash('success', $this->get('translator')->trans('Success : calendar item updated!')); $this->addFlash('success', $this->get('translator')->trans('Success : calendar item updated!'));
$params = $this->buildParamsToUrl($user, $accompanyingPeriod);
if ($form->get('save_and_create_doc')->isClicked()) { if ($form->get('save_and_create_doc')->isClicked()) {
return $this->redirectToRoute('chill_calendar_calendardoc_pick_template', ['id' => $entity->getId()]); return $this->redirectToRoute('chill_calendar_calendardoc_pick_template', ['id' => $entity->getId()]);
} }
return $this->redirectToRoute('chill_calendar_calendar_list_by_period', $params); return new RedirectResponse($redirectRoute);
} }
if ($form->isSubmitted() && !$form->isValid()) { if ($form->isSubmitted() && !$form->isValid()) {
$this->addFlash('error', $this->get('translator')->trans('This form contains errors')); $this->addFlash('error', $this->get('translator')->trans('This form contains errors'));
} }
$deleteForm = $this->createDeleteForm($entity->getId(), $user, $accompanyingPeriod);
if (null === $view) {
throw $this->createNotFoundException('Template not found');
}
$entity_array = $this->serializer->normalize($entity, 'json', ['groups' => 'read']); $entity_array = $this->serializer->normalize($entity, 'json', ['groups' => 'read']);
return $this->render($view, [ return $this->render($view, [
'entity' => $entity, 'entity' => $entity,
'form' => $form->createView(), 'form' => $form->createView(),
'delete_form' => $deleteForm->createView(), 'accompanyingCourse' => $entity->getAccompanyingPeriod(),
'accompanyingCourse' => $accompanyingPeriod, 'person' => $entity->getPerson(),
// 'user' => $user,
'entity_json' => $entity_array, 'entity_json' => $entity_array,
]); ]);
} }
@ -225,6 +224,37 @@ class CalendarController extends AbstractController
]); ]);
} }
/**
* Lists all Calendar entities on a person.
*
* @Route("/{_locale}/calendar/calendar/by-person/{id}", name="chill_calendar_calendar_list_by_person")
*/
public function listActionByPerson(Person $person): Response
{
$filterOrder = $this->buildListFilterOrder();
['from' => $from, 'to' => $to] = $filterOrder->getDateRangeData('startDate');
$total = $this->calendarACLAwareRepository
->countByPerson($person, $from, $to);
$paginator = $this->paginator->create($total);
$calendarItems = $this->calendarACLAwareRepository->findByPerson(
$person,
$from,
$to,
['startDate' => 'DESC'],
$paginator->getCurrentPageFirstItemNumber(),
$paginator->getItemsPerPage()
);
return $this->render('@ChillCalendar/Calendar/listByPerson.html.twig', [
'calendarItems' => $calendarItems,
'person' => $person,
'paginator' => $paginator,
'filterOrder' => $filterOrder,
'hasDocs' => 0 < $this->docGeneratorTemplateRepository->countByEntity(Calendar::class),
]);
}
/** /**
* @Route("/{_locale}/calendar/calendar/my", name="chill_calendar_calendar_list_my") * @Route("/{_locale}/calendar/calendar/my", name="chill_calendar_calendar_list_my")
*/ */
@ -261,27 +291,22 @@ class CalendarController extends AbstractController
$view = null; $view = null;
$em = $this->getDoctrine()->getManager(); $em = $this->getDoctrine()->getManager();
[$user, $accompanyingPeriod] = $this->getEntity($request); [$person, $accompanyingPeriod] = $this->getEntity($request);
if ($accompanyingPeriod instanceof AccompanyingPeriod) {
$view = '@ChillCalendar/Calendar/newByAccompanyingCourse.html.twig';
}
// elseif ($user instanceof User) {
// $view = '@ChillCalendar/Calendar/newUser.html.twig';
// }
$entity = new Calendar(); $entity = new Calendar();
if ($request->query->has('mainUser')) { if ($accompanyingPeriod instanceof AccompanyingPeriod) {
$entity->setMainUser($this->userRepository->find($request->query->getInt('mainUser'))); $view = '@ChillCalendar/Calendar/newByAccompanyingCourse.html.twig';
$entity->setAccompanyingPeriod($accompanyingPeriod);
$redirectRoute = $this->generateUrl('chill_calendar_calendar_list_by_period', ['id' => $accompanyingPeriod->getId()]);
} elseif ($person) {
$view = '@ChillCalendar/Calendar/newByPerson.html.twig';
$entity->setPerson($person)->addPerson($person);
$redirectRoute = $this->generateUrl('chill_calendar_calendar_list_by_person', ['id' => $person->getId()]);
} }
// if ($user instanceof User) { if ($request->query->has('mainUser')) {
// $entity->setPerson($user); $entity->setMainUser($this->userRepository->find($request->query->getInt('mainUser')));
// }
if ($accompanyingPeriod instanceof AccompanyingPeriod) {
$entity->setAccompanyingPeriod($accompanyingPeriod);
} }
$form = $this->createForm(CalendarType::class, $entity) $form = $this->createForm(CalendarType::class, $entity)
@ -296,13 +321,11 @@ class CalendarController extends AbstractController
$this->addFlash('success', $this->get('translator')->trans('Success : calendar item created!')); $this->addFlash('success', $this->get('translator')->trans('Success : calendar item created!'));
$params = $this->buildParamsToUrl($user, $accompanyingPeriod);
if ($form->get('save_and_create_doc')->isClicked()) { if ($form->get('save_and_create_doc')->isClicked()) {
return $this->redirectToRoute('chill_calendar_calendardoc_pick_template', ['id' => $entity->getId()]); return $this->redirectToRoute('chill_calendar_calendardoc_pick_template', ['id' => $entity->getId()]);
} }
return $this->redirectToRoute('chill_calendar_calendar_list_by_period', $params); return new RedirectResponse($redirectRoute);
} }
if ($form->isSubmitted() && !$form->isValid()) { if ($form->isSubmitted() && !$form->isValid()) {
@ -316,7 +339,8 @@ class CalendarController extends AbstractController
$entity_array = $this->serializer->normalize($entity, 'json', ['groups' => 'read']); $entity_array = $this->serializer->normalize($entity, 'json', ['groups' => 'read']);
return $this->render($view, [ return $this->render($view, [
'user' => $user, 'context' => $entity->getContext(),
'person' => $person,
'accompanyingCourse' => $accompanyingPeriod, 'accompanyingCourse' => $accompanyingPeriod,
'entity' => $entity, 'entity' => $entity,
'form' => $form->createView(), 'form' => $form->createView(),
@ -331,6 +355,7 @@ class CalendarController extends AbstractController
*/ */
public function showAction(Request $request, int $id): Response public function showAction(Request $request, int $id): Response
{ {
throw new Exception('not implemented');
$view = null; $view = null;
$em = $this->getDoctrine()->getManager(); $em = $this->getDoctrine()->getManager();
@ -465,49 +490,45 @@ class CalendarController extends AbstractController
/** /**
* Creates a form to delete a Calendar entity by id. * Creates a form to delete a Calendar entity by id.
*/ */
private function createDeleteForm(int $id, ?User $user, ?AccompanyingPeriod $accompanyingPeriod): FormInterface private function createDeleteForm(Calendar $calendar): FormInterface
{ {
$params = $this->buildParamsToUrl($user, $accompanyingPeriod);
$params['id'] = $id;
return $this->createFormBuilder() return $this->createFormBuilder()
->setAction($this->generateUrl('chill_calendar_calendar_delete', $params)) ->setAction($this->generateUrl('chill_calendar_calendar_delete', ['id' => $calendar->getId()]))
->setMethod('DELETE') ->setMethod('DELETE')
->add('submit', SubmitType::class, ['label' => 'Delete']) ->add('submit', SubmitType::class, ['label' => 'Delete'])
->getForm(); ->getForm();
} }
/**
* @return array{0: ?Person, 1: ?AccompanyingPeriod}
*/
private function getEntity(Request $request): array private function getEntity(Request $request): array
{ {
$em = $this->getDoctrine()->getManager(); $em = $this->getDoctrine()->getManager();
$user = $accompanyingPeriod = null; $person = $accompanyingPeriod = null;
if ($request->query->has('user_id')) { if ($request->query->has('person_id')) {
$user_id = $request->get('user_id'); $person = $this->personRepository->find($request->query->getInt('person_id'));
$user = $em->getRepository(User::class)->find($user_id);
if (null === $user) { if (null === $person) {
throw $this->createNotFoundException('User not found'); throw $this->createNotFoundException('Person not found');
} }
// TODO Add permission $this->denyAccessUnlessGranted(PersonVoter::SEE, $person);
// $this->denyAccessUnlessGranted('CHILL_PERSON_SEE', $user);
} elseif ($request->query->has('accompanying_period_id')) { } elseif ($request->query->has('accompanying_period_id')) {
$accompanying_period_id = $request->get('accompanying_period_id'); $accompanyingPeriod = $this->accompanyingPeriodRepository->find($request->query->getInt('accompanying_period_id'));
$accompanyingPeriod = $em->getRepository(AccompanyingPeriod::class)->find($accompanying_period_id);
if (null === $accompanyingPeriod) { if (null === $accompanyingPeriod) {
throw $this->createNotFoundException('Accompanying Period not found'); throw $this->createNotFoundException('Accompanying Period not found');
} }
// TODO Add permission $this->denyAccessUnlessGranted(AccompanyingPeriodVoter::SEE, $accompanyingPeriod);
// $this->denyAccessUnlessGranted('CHILL_PERSON_SEE', $person);
} else { } else {
throw $this->createNotFoundException('Person or Accompanying Period not found'); throw $this->createNotFoundException('Person or Accompanying Period not found');
} }
return [ return [
$user, $accompanyingPeriod, $person, $accompanyingPeriod,
]; ];
} }
} }

View File

@ -92,7 +92,7 @@ class Calendar implements TrackCreationInterface, TrackUpdateInterface
/** /**
* @ORM\ManyToOne(targetEntity="Chill\PersonBundle\Entity\AccompanyingPeriod", inversedBy="calendars") * @ORM\ManyToOne(targetEntity="Chill\PersonBundle\Entity\AccompanyingPeriod", inversedBy="calendars")
*/ */
private AccompanyingPeriod $accompanyingPeriod; private ?AccompanyingPeriod $accompanyingPeriod = null;
/** /**
* @ORM\ManyToOne(targetEntity="Chill\ActivityBundle\Entity\Activity") * @ORM\ManyToOne(targetEntity="Chill\ActivityBundle\Entity\Activity")
@ -173,7 +173,7 @@ class Calendar implements TrackCreationInterface, TrackUpdateInterface
* @ORM\ManyToOne(targetEntity=Person::class) * @ORM\ManyToOne(targetEntity=Person::class)
* @ORM\JoinColumn(nullable=true) * @ORM\JoinColumn(nullable=true)
*/ */
private ?Person $person; private ?Person $person = null;
/** /**
* @ORM\ManyToMany(targetEntity="Chill\PersonBundle\Entity\Person", inversedBy="calendars") * @ORM\ManyToMany(targetEntity="Chill\PersonBundle\Entity\Person", inversedBy="calendars")
@ -317,6 +317,22 @@ class Calendar implements TrackCreationInterface, TrackUpdateInterface
return $this->comment; return $this->comment;
} }
/**
* @return 'person'|'accompanying_period'|null
*/
public function getContext(): ?string
{
if ($this->getAccompanyingPeriod() !== null) {
return 'accompanying_period';
}
if ($this->getPerson() !== null) {
return 'person';
}
return null;
}
/** /**
* Each time the date and time is update, this version is incremented. * Each time the date and time is update, this version is incremented.
*/ */

View File

@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\CalendarBundle\Menu;
use Chill\CalendarBundle\Security\Voter\CalendarVoter;
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
use Knp\Menu\MenuItem;
use Symfony\Component\Security\Core\Security;
use Symfony\Contracts\Translation\TranslatorInterface;
class PersonMenuBuilder implements LocalMenuBuilderInterface
{
protected TranslatorInterface $translator;
private Security $security;
public function __construct(
Security $security,
TranslatorInterface $translator
) {
$this->security = $security;
$this->translator = $translator;
}
public function buildMenu($menuId, MenuItem $menu, array $parameters)
{
$person = $parameters['person'];
if ($this->security->isGranted(CalendarVoter::SEE, $person)) {
$menu->addChild($this->translator->trans('Calendar'), [
'route' => 'chill_calendar_calendar_list_by_person',
'routeParameters' => [
'id' => $person->getId(),
], ])
->setExtras(['order' => 198]);
}
}
public static function getMenuIds(): array
{
return ['person'];
}
}

View File

@ -20,16 +20,24 @@ namespace Chill\CalendarBundle\Repository;
use Chill\CalendarBundle\Entity\Calendar; use Chill\CalendarBundle\Entity\Calendar;
use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Repository\AccompanyingPeriodACLAwareRepositoryInterface;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
use DateTimeImmutable; use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
class CalendarACLAwareRepository implements CalendarACLAwareRepositoryInterface class CalendarACLAwareRepository implements CalendarACLAwareRepositoryInterface
{ {
private AccompanyingPeriodACLAwareRepositoryInterface $accompanyingPeriodACLAwareRepository;
private EntityManagerInterface $em; private EntityManagerInterface $em;
public function __construct(EntityManagerInterface $em) public function __construct(
{ AccompanyingPeriodACLAwareRepositoryInterface $accompanyingPeriodACLAwareRepository,
EntityManagerInterface $em
) {
$this->accompanyingPeriodACLAwareRepository = $accompanyingPeriodACLAwareRepository;
$this->em = $em; $this->em = $em;
} }
@ -56,6 +64,46 @@ class CalendarACLAwareRepository implements CalendarACLAwareRepositoryInterface
return $qb; return $qb;
} }
/**
* Base implementation. The list of allowed accompanying period is retrieved "manually" from @see{AccompanyingPeriodACLAwareRepository}.
*/
public function buildQueryByPerson(Person $person, ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate): QueryBuilder
{
// find the reachable accompanying periods for person
$periods = $this->accompanyingPeriodACLAwareRepository->findByPerson($person, AccompanyingPeriodVoter::SEE);
$qb = $this->em->createQueryBuilder()
->from(Calendar::class, 'c');
$qb
->where(
$qb->expr()->orX(
// the calendar where the person is the main person:
$qb->expr()->eq('c.person', ':person'),
// when the calendar is in a reachable period, and contains person
$qb->expr()->andX(
$qb->expr()->in('c.accompanyingPeriod', ':periods'),
$qb->expr()->isMemberOf(':person', 'c.persons')
)
)
)
->setParameter('person', $person)
->setParameter('periods', $periods);
// filter by date
if (null !== $startDate) {
$qb->andWhere($qb->expr()->gte('c.startDate', ':startDate'))
->setParameter('startDate', $startDate);
}
if (null !== $endDate) {
$qb->andWhere($qb->expr()->lte('c.endDate', ':endDate'))
->setParameter('endDate', $endDate);
}
return $qb;
}
public function countByAccompanyingPeriod(AccompanyingPeriod $period, ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate): int public function countByAccompanyingPeriod(AccompanyingPeriod $period, ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate): int
{ {
$qb = $this->buildQueryByAccompanyingPeriod($period, $startDate, $endDate)->select('count(c)'); $qb = $this->buildQueryByAccompanyingPeriod($period, $startDate, $endDate)->select('count(c)');
@ -63,6 +111,14 @@ class CalendarACLAwareRepository implements CalendarACLAwareRepositoryInterface
return $qb->getQuery()->getSingleScalarResult(); return $qb->getQuery()->getSingleScalarResult();
} }
public function countByPerson(Person $person, ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate): int
{
return $this->buildQueryByPerson($person, $startDate, $endDate)
->select('COUNT(c)')
->getQuery()
->getSingleScalarResult();
}
/** /**
* @return array|Calendar[] * @return array|Calendar[]
*/ */
@ -84,4 +140,24 @@ class CalendarACLAwareRepository implements CalendarACLAwareRepositoryInterface
return $qb->getQuery()->getResult(); return $qb->getQuery()->getResult();
} }
public function findByPerson(Person $person, ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate, ?array $orderBy = [], ?int $offset = null, ?int $limit = null): array
{
$qb = $this->buildQueryByPerson($person, $startDate, $endDate)
->select('c');
foreach ($orderBy as $sort => $order) {
$qb->addOrderBy('c.' . $sort, $order);
}
if (null !== $offset) {
$qb->setFirstResult($offset);
}
if (null !== $limit) {
$qb->setMaxResults($limit);
}
return $qb->getQuery()->getResult();
}
} }

View File

@ -20,14 +20,32 @@ namespace Chill\CalendarBundle\Repository;
use Chill\CalendarBundle\Entity\Calendar; use Chill\CalendarBundle\Entity\Calendar;
use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use DateTimeImmutable; use DateTimeImmutable;
interface CalendarACLAwareRepositoryInterface interface CalendarACLAwareRepositoryInterface
{ {
public function countByAccompanyingPeriod(AccompanyingPeriod $period, ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate): int; public function countByAccompanyingPeriod(AccompanyingPeriod $period, ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate): int;
/**
* Return the number or calendars associated with a person. See condition on @see{self::findByPerson}.
*/
public function countByPerson(Person $person, ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate): int;
/** /**
* @return array|Calendar[] * @return array|Calendar[]
*/ */
public function findByAccompanyingPeriod(AccompanyingPeriod $period, ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate, ?array $orderBy = [], ?int $offset = null, ?int $limit = null): array; public function findByAccompanyingPeriod(AccompanyingPeriod $period, ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate, ?array $orderBy = [], ?int $offset = null, ?int $limit = null): array;
/**
* Return all the calendars which are associated with a person, either on @see{Calendar::person} or within.
*
* @see{Calendar::persons}. The calendar may be associated with a person, or an accompanyingPeriod.
*
* The method may assume that the user is allowed to see the person, but must check that the user is allowed
* to see the calendar's accompanyingPeriod, if any.
*
* @return array|Calendar[]
*/
public function findByPerson(Person $person, ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate, ?array $orderBy = [], ?int $offset = null, ?int $limit = null): array;
} }

View File

@ -0,0 +1,177 @@
{# list used in context of person or accompanyingPeriod #}
{{ filterOrder|chill_render_filter_order_helper }}
{% if calendarItems|length > 0 %}
<div class="flex-table list-records context-accompanyingCourse">
{% for calendar in calendarItems %}
<div class="item-bloc">
<div class="item-row main">
<div class="item-col">
<div class="wrap-header">
<div class="wl-row">
<div class="wl-col title">
<p class="date-label">
{% if context == 'person' and calendar.context == 'accompanying_period' %}
<a href="{{ chill_path_add_return_path('chill_person_accompanying_course_index', {'accompanying_period_id': calendar.accompanyingPeriod.id}) }}" style="text-decoration: none;">
<span class="badge bg-primary">
<i class="fa fa-random"></i> {{ calendar.accompanyingPeriod.id }}
</span>
</a>
{% 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 %}
</p>
<div class="duration short-message">
<i class="fa fa-fw fa-hourglass-end"></i>
{{ calendar.duration|date('%H:%I') }}
{% if false == calendar.sendSMS or null == calendar.sendSMS %}
<!-- no sms will be send -->
{% else %}
{% if calendar.smsStatus == 'sms_sent' %}
<span title="{{ 'SMS already sent'|trans }}" class="badge bg-info">
<i class="fa fa-check "></i>
<i class="fa fa-envelope "></i>
</span>
{% else %}
<span title="{{ 'Will send SMS'|trans }}" class="badge bg-info">
<i class="fa fa-envelope "></i>
<i class="fa fa-hourglass-end "></i>
</span>
{% endif %}
{% endif %}
</div>
</div>
</div>
</div>
<div class="item-col">
<ul class="list-content">
{% if calendar.mainUser is not empty %}
<span class="badge-user">{{ calendar.mainUser|chill_entity_render_box }}</span>
{% endif %}
</ul>
</div>
</div>
</div>
{% if calendar.comment.comment is not empty
or calendar.users|length > 0
or calendar.thirdParties|length > 0
or calendar.users|length > 0 %}
<div class="item-row details separator">
<div class="item-col">
{% include 'ChillActivityBundle:Activity:concernedGroups.html.twig' with {
'context': calendar.context == 'person' ? 'calendar_person' : 'calendar_accompanyingCourse',
'render': 'wrap-list',
'entity': calendar
} %}
</div>
</div>
{% endif %}
{% if calendar.comment.comment is not empty %}
<div class="item-row details separator">
<div class="item-col comment">
{{ calendar.comment|chill_entity_render_box( { 'limit_lines': 3, 'metadata': false } ) }}
</div>
</div>
{% endif %}
{% if calendar.location is not empty %}
<div class="item-row separator">
<div>
{% if calendar.location.address is not same as(null) and calendar.location.name is not empty %}
<i class="fa fa-map-marker"></i>{% 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 %}
<i class="fa fa-map-marker"></i>{% endif %}
{% if calendar.location.phonenumber1 is not empty %}<i
class="fa fa-phone"></i> {{ calendar.location.phonenumber1|chill_format_phonenumber }}{% endif %}
{% if calendar.location.phonenumber2 is not empty %}<i
class="fa fa-phone"></i> {{ calendar.location.phonenumber2|chill_format_phonenumber }}{% endif %}
</div>
</div>
{% endif %}
<div class="item-row separator column">
<div>
{{ include('@ChillCalendar/Calendar/_documents.twig.html') }}
</div>
</div>
<div class="item-row">
<ul class="record_actions">
{% if is_granted('CHILL_CALENDAR_CALENDAR_SEE', calendar) and hasDocs %}
<li>
<a class="btn btn-create"
href="{{ chill_path_add_return_path('chill_calendar_calendardoc_pick_template', {'id': calendar.id }) }}">
{{ 'chill_calendar.Add a document'|trans }}
</a>
</li>
{% endif %}
{% if accompanyingCourse is defined and is_granted('CHILL_ACTIVITY_CREATE', accompanyingCourse) and calendar.activity is null %}
<li>
<a class="btn btn-create"
href="{{ chill_path_add_return_path('chill_calendar_calendar_to_activity', { 'id': calendar.id }) }}">
{{ 'Transform to activity'|trans }}
</a>
</li>
{% endif %}
{% if (calendar.isInvited(app.user)) %}
{% set invite = calendar.inviteForUser(app.user) %}
<li>
<div invite-answer data-status="{{ invite.status|e('html_attr') }}"
data-calendar-id="{{ calendar.id|e('html_attr') }}"></div>
</li>
{% endif %}
{% if false %}
<li>
<a href="{{ chill_path_add_return_path('chill_calendar_calendar_show', { 'id': calendar.id}) }}"
class="btn btn-show "></a>
</li>
{% endif %}
{# TOOD
{% if is_granted('CHILL_ACTIVITY_UPDATE', calendar) %}
#}
<li>
<a href="{{ chill_path_add_return_path('chill_calendar_calendar_edit', { 'id': calendar.id }) }}"
class="btn btn-update "></a>
</li>
{# TOOD
{% endif %}
{% if is_granted('CHILL_ACTIVITY_DELETE', calendar) %}
#}
<li>
<a href="{{ chill_path_add_return_path('chill_calendar_calendar_delete', { 'id': calendar.id } ) }}"
class="btn btn-delete "></a>
</li>
{#
{% endif %}
#}
</ul>
</div>
</div>
{% endfor %}
{% if calendarItems|length < paginator.getTotalItems %}
{{ chill_pagination(paginator) }}
{% endif %}
</div>
{% endif %}

View File

@ -0,0 +1,16 @@
{% extends "@ChillPerson/Person/layout.html.twig" %}
{% set activeRouteKey = 'chill_calendar_calendar_list' %}
{% block title 'Remove calendar item'|trans %}
{% block content %}
{{ include('@ChillMain/Util/confirmation_template.html.twig',
{
'title' : 'Remove calendar item'|trans,
'confirm_question' : 'Are you sure you want to remove the calendar item?'|trans,
'cancel_route' : 'chill_calendar_calendar_list_by_period',
'cancel_parameters' : { 'id' : person.id },
'form' : delete_form
} ) }}
{% endblock %}

View File

@ -0,0 +1,34 @@
{% extends "@ChillPerson/Person/layout.html.twig" %}
{% set activeRouteKey = 'chill_calendar_calendar_list' %}
{% block title 'Update calendar'|trans %}
{% block content %}
<div class="calendar-edit">
{% include 'ChillCalendarBundle:Calendar:edit.html.twig' with {'context': 'person'} %}
</div>
{% endblock %}
{% block js %}
{{ parent() }}
<script type="text/javascript">
window.entity = {{ entity_json|json_encode|raw }};
window.startDate = {{ entity.startDate|date('Y-m-d H:i:s')|json_encode|raw }};
window.endDate = {{ entity.endDate|date('Y-m-d H:i:s')|json_encode|raw }};
window.mainUser = {{ entity.mainUser.id }};
</script>
{{ encore_entry_script_tags('vue_calendar') }}
{% endblock %}
{% block css %}
{{ parent() }}
{{ encore_entry_link_tags('vue_calendar') }}
{{ encore_entry_link_tags('page_calendar') }}
{% endblock %}
{% block block_post_menu %}
<div id="calendarControls"></div>
{% endblock %}

View File

@ -22,7 +22,6 @@
{% block content %} {% block content %}
<h1>{{ 'Calendar list' |trans }}</h1> <h1>{{ 'Calendar list' |trans }}</h1>
{{ filterOrder|chill_render_filter_order_helper }}
{% if calendarItems|length == 0 %} {% if calendarItems|length == 0 %}
<p class="chill-no-data-statement"> <p class="chill-no-data-statement">
@ -31,171 +30,9 @@
class="btn btn-create button-small"></a> class="btn btn-create button-small"></a>
</p> </p>
{% else %} {% else %}
{{ include('@ChillCalendar/Calendar/_list.html.twig', {context: 'accompanying_course'}) }}
<div class="flex-table list-records context-accompanyingCourse">
{% for calendar in calendarItems %}
<div class="item-bloc">
<div class="item-row main">
<div class="item-col">
<div class="wrap-header">
<div class="wl-row">
<div class="wl-col title">
{% if calendar.endDate.diff(calendar.startDate).days >= 1 %}
<p class="date-label">{{ calendar.startDate|format_datetime('short', 'short') }}
- {{ calendar.endDate|format_datetime('short', 'short') }}</p>
{% else %}
<p class="date-label">{{ calendar.startDate|format_datetime('short', 'short') }}
- {{ calendar.endDate|format_datetime('none', 'short') }}</p>
{% endif %}
<div class="duration short-message">
<i class="fa fa-fw fa-hourglass-end"></i>
{{ calendar.duration|date('%H:%I') }}
{% if false == calendar.sendSMS or null == calendar.sendSMS %}
<!-- no sms will be send -->
{% else %}
{% if calendar.smsStatus == 'sms_sent' %}
<span title="{{ 'SMS already sent'|trans }}" class="badge bg-info">
<i class="fa fa-check "></i>
<i class="fa fa-envelope "></i>
</span>
{% else %}
<span title="{{ 'Will send SMS'|trans }}" class="badge bg-info">
<i class="fa fa-envelope "></i>
<i class="fa fa-hourglass-end "></i>
</span>
{% endif %}
{% endif %}
</div>
</div>
</div>
</div>
<div class="item-col">
<ul class="list-content">
{% if calendar.mainUser is not empty %}
<span class="badge-user">{{ calendar.mainUser|chill_entity_render_box }}</span>
{% endif %}
</ul>
</div>
</div>
</div>
{% if calendar.comment.comment is not empty
or calendar.users|length > 0
or calendar.thirdParties|length > 0
or calendar.users|length > 0 %}
<div class="item-row details separator">
<div class="item-col">
{% include 'ChillActivityBundle:Activity:concernedGroups.html.twig' with {
'context': 'calendar_accompanyingCourse',
'render': 'wrap-list',
'entity': calendar
} %}
</div>
</div>
{% endif %}
{% if calendar.comment.comment is not empty %}
<div class="item-row details separator">
<div class="item-col comment">
{{ calendar.comment|chill_entity_render_box( { 'limit_lines': 3, 'metadata': false } ) }}
</div>
</div>
{% endif %}
{% if calendar.location is not empty %}
<div class="item-row separator">
<div>
{% if calendar.location.address is not same as(null) and calendar.location.name is not empty %}
<i class="fa fa-map-marker"></i>{% 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 %}
<i class="fa fa-map-marker"></i>{% endif %}
{% if calendar.location.phonenumber1 is not empty %}<i
class="fa fa-phone"></i> {{ calendar.location.phonenumber1|chill_format_phonenumber }}{% endif %}
{% if calendar.location.phonenumber2 is not empty %}<i
class="fa fa-phone"></i> {{ calendar.location.phonenumber2|chill_format_phonenumber }}{% endif %}
</div>
</div>
{% endif %}
<div class="item-row separator column">
<div>
{{ include('@ChillCalendar/Calendar/_documents.twig.html') }}
</div>
</div>
<div class="item-row">
<ul class="record_actions">
{% if is_granted('CHILL_CALENDAR_CALENDAR_SEE', calendar) and hasDocs %}
<li>
<a class="btn btn-create"
href="{{ chill_path_add_return_path('chill_calendar_calendardoc_pick_template', {'id': calendar.id }) }}">
{{ 'chill_calendar.Add a document'|trans }}
</a>
</li>
{% endif %}
{% if is_granted('CHILL_ACTIVITY_CREATE', accompanyingCourse) and calendar.activity is null %}
<li>
<a class="btn btn-create"
href="{{ chill_path_add_return_path('chill_calendar_calendar_to_activity', { 'id': calendar.id }) }}">
{{ 'Transform to activity'|trans }}
</a>
</li>
{% endif %}
{% if (calendar.isInvited(app.user)) %}
{% set invite = calendar.inviteForUser(app.user) %}
<li>
<div invite-answer data-status="{{ invite.status|e('html_attr') }}"
data-calendar-id="{{ calendar.id|e('html_attr') }}"></div>
</li>
{% endif %}
{% if false %}
<li>
<a href="{{ chill_path_add_return_path('chill_calendar_calendar_show', { 'id': calendar.id, 'user_id': user_id, 'accompanying_period_id': accompanying_course_id }) }}"
class="btn btn-show "></a>
</li>
{% endif %}
{# TOOD
{% if is_granted('CHILL_ACTIVITY_UPDATE', calendar) %}
#}
<li>
<a href="{{ chill_path_add_return_path('chill_calendar_calendar_edit', { 'id': calendar.id, 'user_id': user_id, 'accompanying_period_id': accompanying_course_id }) }}"
class="btn btn-update "></a>
</li>
{# TOOD
{% endif %}
{% if is_granted('CHILL_ACTIVITY_DELETE', calendar) %}
#}
<li>
<a href="{{ chill_path_add_return_path('chill_calendar_calendar_delete', { 'id': calendar.id, 'user_id' : user_id, 'accompanying_period_id': accompanying_course_id } ) }}"
class="btn btn-delete "></a>
</li>
{#
{% endif %}
#}
</ul>
</div>
</div>
{% endfor %}
{% if calendarItems|length < paginator.getTotalItems %}
{{ chill_pagination(paginator) }}
{% endif %}
</div>
{% endif %} {% endif %}
<ul class="record_actions sticky-form-buttons"> <ul class="record_actions sticky-form-buttons">
{% if accompanyingCourse.user is not same as(null) %} {% if accompanyingCourse.user is not same as(null) %}
<li> <li>

View File

@ -0,0 +1,52 @@
{% extends "@ChillPerson/Person/layout.html.twig" %}
{% set activeRouteKey = 'chill_calendar_calendar_list' %}
{% block title %}{{ 'Calendar list' |trans }}{% endblock title %}
{% set user_id = null %}
{% block js %}
{{ parent() }}
{{ encore_entry_script_tags('mod_answer') }}
{{ encore_entry_script_tags('mod_async_upload') }}
{% endblock %}
{% block css %}
{{ parent() }}
{{ encore_entry_link_tags('mod_answer') }}
{{ encore_entry_link_tags('mod_async_upload') }}
{% endblock %}
{% block content %}
<h1>{{ 'Calendar list' |trans }}</h1>
{% if calendarItems|length == 0 %}
<p class="chill-no-data-statement">
{{ "There is no calendar items."|trans }}
<a href="{{ path('chill_calendar_calendar_new', {'user_id': user_id, 'person_id': person.id}) }}"
class="btn btn-create btn-sm">
{{ 'Create'|trans }}
</a>
</p>
{% else %}
{{ include ('@ChillCalendar/Calendar/_list.html.twig', {context: 'person'}) }}
{% endif %}
<ul class="record_actions sticky-form-buttons">
<li>
<a href="{{ path('chill_calendar_calendar_new', {'user_id': user_id, 'person_id': person.id, 'mainUser': app.user.id }) }}"
class="btn btn-create">
{{ 'chill_calendar.Create for me'|trans }}
</a>
</li>
<li>
<a href="{{ path('chill_calendar_calendar_new', {'user_id': user_id, 'person_id': person.id}) }}"
class="btn btn-create">
{{ 'Create'|trans }}
</a>
</li>
</ul>
{% endblock %}

View File

@ -69,7 +69,7 @@
<a <a
class="btn btn-cancel" class="btn btn-cancel"
{%- if context == 'person' -%} {%- if context == 'person' -%}
href="{{ chill_return_path_or('chill_calendar_calendar_list', { 'person_id': person.id } )}}" href="{{ chill_return_path_or('chill_calendar_calendar_list_by_person', { 'id': person.id } )}}"
{%- else -%} {%- else -%}
href="{{ chill_return_path_or('chill_calendar_calendar_list_by_period', { 'id': accompanyingCourse.id } )}}" href="{{ chill_return_path_or('chill_calendar_calendar_list_by_period', { 'id': accompanyingCourse.id } )}}"
{%- endif -%} {%- endif -%}

View File

@ -0,0 +1,34 @@
{% extends "@ChillPerson/Person/layout.html.twig" %}
{% set activeRouteKey = 'chill_calendar_calendar_new' %}
{% block title 'Calendar item creation' |trans %}
{% block content %}
<div class="calendar-new">
<div id="calendar"></div> {# <=== vue component #}
{% include 'ChillCalendarBundle:Calendar:new.html.twig' with {'context': 'person'} %}
</div>
{% endblock %}
{% block js %}
{{ parent() }}
{{ encore_entry_script_tags('mod_pickentity_type') }}
<script type="text/javascript">
window.entity = {{ entity_json|json_encode|raw }};
{% if app.user.currentLocation is not null %}window.default_location_id = {{ app.user.currentLocation.id }};{% endif %};
</script>
{{ encore_entry_script_tags('vue_calendar') }}
{% endblock %}
{% block css %}
{{ parent() }}
{{ encore_entry_link_tags('vue_calendar') }}
{{ encore_entry_link_tags('mod_pickentity_type') }}
{% endblock %}
{% block block_post_menu %}
<div id="calendarControls"></div>
{% endblock %}

View File

@ -24,7 +24,9 @@ use Chill\MainBundle\Security\Authorization\VoterHelperFactoryInterface;
use Chill\MainBundle\Security\Authorization\VoterHelperInterface; use Chill\MainBundle\Security\Authorization\VoterHelperInterface;
use Chill\MainBundle\Security\ProvideRoleHierarchyInterface; use Chill\MainBundle\Security\ProvideRoleHierarchyInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter; use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use LogicException; use LogicException;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\Security;
@ -51,6 +53,7 @@ class CalendarVoter extends AbstractChillVoter implements ProvideRoleHierarchyIn
$this->voterHelper = $voterHelperFactory $this->voterHelper = $voterHelperFactory
->generate(self::class) ->generate(self::class)
->addCheckFor(AccompanyingPeriod::class, [self::SEE]) ->addCheckFor(AccompanyingPeriod::class, [self::SEE])
->addCheckFor(Person::class, [self::SEE])
->addCheckFor(Calendar::class, [self::SEE, self::CREATE, self::EDIT, self::DELETE]) ->addCheckFor(Calendar::class, [self::SEE, self::CREATE, self::EDIT, self::DELETE])
->build(); ->build();
} }
@ -89,6 +92,14 @@ class CalendarVoter extends AbstractChillVoter implements ProvideRoleHierarchyIn
// we first check here that the user has read access to the period // we first check here that the user has read access to the period
return $this->security->isGranted(AccompanyingPeriodVoter::SEE, $subject); return $this->security->isGranted(AccompanyingPeriodVoter::SEE, $subject);
default:
throw new LogicException('subject not implemented');
}
} elseif ($subject instanceof Person) {
switch ($attribute) {
case self::SEE:
return $this->security->isGranted(PersonVoter::SEE, $subject);
default: default:
throw new LogicException('subject not implemented'); throw new LogicException('subject not implemented');
} }
@ -103,6 +114,16 @@ class CalendarVoter extends AbstractChillVoter implements ProvideRoleHierarchyIn
case self::DELETE: case self::DELETE:
return $this->security->isGranted(AccompanyingPeriodVoter::EDIT, $period); return $this->security->isGranted(AccompanyingPeriodVoter::EDIT, $period);
} }
} elseif (null !== $person = $subject->getPerson()) {
switch ($attribute) {
case self::SEE:
case self::EDIT:
case self::CREATE:
return $this->security->isGranted(PersonVoter::SEE, $person);
case self::DELETE:
return $this->security->isGranted(PersonVoter::UPDATE, $person);
}
} }
} }

View File

@ -0,0 +1,78 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\CalendarBundle\Tests\Repository;
use Chill\CalendarBundle\Repository\CalendarACLAwareRepository;
use Chill\PersonBundle\DataFixtures\Helper\PersonRandomHelper;
use Chill\PersonBundle\Repository\AccompanyingPeriodACLAwareRepositoryInterface;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
use Prophecy\PhpUnit\ProphecyTrait;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
/**
* @internal
* @coversNothing
*/
final class CalendarACLAwareRepositoryTest extends KernelTestCase
{
use PersonRandomHelper;
use ProphecyTrait;
private EntityManagerInterface $entityManager;
protected function setUp(): void
{
self::bootKernel();
$this->entityManager = self::$container->get(EntityManagerInterface::class);
}
public function testCountByPerosn()
{
$person = $this->getRandomPerson($this->entityManager);
$periodRepository = $this->prophesize(AccompanyingPeriodACLAwareRepositoryInterface::class);
$periodRepository->findByPerson($person, AccompanyingPeriodVoter::SEE)->willReturn([]);
$calendarRepository = new CalendarACLAwareRepository(
$periodRepository->reveal(),
$this->entityManager
);
$count = $calendarRepository->countByPerson($person, new DateTimeImmutable('yesterday'), new DateTimeImmutable('tomorrow'));
$this->assertIsInt($count);
}
/**
* Test that the query does not throw any error.
*/
public function testFindByPerson()
{
$person = $this->getRandomPerson($this->entityManager);
$periodRepository = $this->prophesize(AccompanyingPeriodACLAwareRepositoryInterface::class);
$periodRepository->findByPerson($person, AccompanyingPeriodVoter::SEE)->willReturn([]);
$calendarRepository = new CalendarACLAwareRepository(
$periodRepository->reveal(),
$this->entityManager
);
$calendars = $calendarRepository->findByPerson($person, null, null, ['startDate' => 'ASC'], 10, 1);
$this->assertIsArray($calendars);
}
}