diff --git a/src/Bundle/ChillCalendarBundle/Controller/CalendarController.php b/src/Bundle/ChillCalendarBundle/Controller/CalendarController.php index 6c249778a..9eabd370f 100644 --- a/src/Bundle/ChillCalendarBundle/Controller/CalendarController.php +++ b/src/Bundle/ChillCalendarBundle/Controller/CalendarController.php @@ -15,6 +15,7 @@ use Chill\CalendarBundle\Entity\Calendar; use Chill\CalendarBundle\Form\CalendarType; use Chill\CalendarBundle\RemoteCalendar\Connector\RemoteCalendarConnectorInterface; use Chill\CalendarBundle\Repository\CalendarACLAwareRepositoryInterface; +use Chill\CalendarBundle\Security\Voter\CalendarVoter; use Chill\DocGeneratorBundle\Repository\DocGeneratorTemplateRepository; use Chill\MainBundle\Entity\User; use Chill\MainBundle\Pagination\PaginatorFactory; @@ -147,6 +148,8 @@ class CalendarController extends AbstractController */ public function editAction(Calendar $entity, Request $request): Response { + $this->denyAccessUnlessGranted(CalendarVoter::EDIT, $entity); + if (!$this->remoteCalendarConnector->isReady()) { return $this->remoteCalendarConnector->getMakeReadyResponse($request->getUri()); } @@ -208,6 +211,8 @@ class CalendarController extends AbstractController */ public function listActionByCourse(AccompanyingPeriod $accompanyingPeriod): Response { + $this->denyAccessUnlessGranted(CalendarVoter::SEE, $accompanyingPeriod); + $filterOrder = $this->buildListFilterOrder(); ['from' => $from, 'to' => $to] = $filterOrder->getDateRangeData('startDate'); @@ -228,6 +233,7 @@ class CalendarController extends AbstractController 'accompanyingCourse' => $accompanyingPeriod, 'paginator' => $paginator, 'filterOrder' => $filterOrder, + 'nbIgnored' => $this->calendarACLAwareRepository->countIgnoredByAccompanyingPeriod($accompanyingPeriod, $from, $to), 'hasDocs' => 0 < $this->docGeneratorTemplateRepository->countByEntity(Calendar::class), ]); } @@ -239,6 +245,8 @@ class CalendarController extends AbstractController */ public function listActionByPerson(Person $person): Response { + $this->denyAccessUnlessGranted(CalendarVoter::SEE, $person); + $filterOrder = $this->buildListFilterOrder(); ['from' => $from, 'to' => $to] = $filterOrder->getDateRangeData('startDate'); @@ -259,6 +267,7 @@ class CalendarController extends AbstractController 'person' => $person, 'paginator' => $paginator, 'filterOrder' => $filterOrder, + 'nbIgnored' => $this->calendarACLAwareRepository->countIgnoredByPerson($person, $from, $to), 'hasDocs' => 0 < $this->docGeneratorTemplateRepository->countByEntity(Calendar::class), ]); } @@ -309,7 +318,7 @@ class CalendarController extends AbstractController $view = '@ChillCalendar/Calendar/newByAccompanyingCourse.html.twig'; $entity->setAccompanyingPeriod($accompanyingPeriod); $redirectRoute = $this->generateUrl('chill_calendar_calendar_list_by_period', ['id' => $accompanyingPeriod->getId()]); - } elseif ($person) { + } elseif (null !== $person) { $view = '@ChillCalendar/Calendar/newByPerson.html.twig'; $entity->setPerson($person)->addPerson($person); $redirectRoute = $this->generateUrl('chill_calendar_calendar_list_by_person', ['id' => $person->getId()]); @@ -319,6 +328,8 @@ class CalendarController extends AbstractController $entity->setMainUser($this->userRepository->find($request->query->getInt('mainUser'))); } + $this->denyAccessUnlessGranted(CalendarVoter::CREATE, $entity); + $form = $this->createForm(CalendarType::class, $entity) ->add('save', SubmitType::class); @@ -442,6 +453,8 @@ class CalendarController extends AbstractController */ public function toActivity(Request $request, Calendar $calendar): RedirectResponse { + $this->denyAccessUnlessGranted(CalendarVoter::SEE, $calendar); + $personsId = array_map( static fn (Person $p): int => $p->getId(), $calendar->getPersons()->toArray() diff --git a/src/Bundle/ChillCalendarBundle/Controller/CalendarDocController.php b/src/Bundle/ChillCalendarBundle/Controller/CalendarDocController.php index 1f6b42dea..d25998e1e 100644 --- a/src/Bundle/ChillCalendarBundle/Controller/CalendarDocController.php +++ b/src/Bundle/ChillCalendarBundle/Controller/CalendarDocController.php @@ -12,12 +12,20 @@ declare(strict_types=1); namespace Chill\CalendarBundle\Controller; use Chill\CalendarBundle\Entity\Calendar; +use Chill\CalendarBundle\Entity\CalendarDoc; +use Chill\CalendarBundle\Form\CalendarDocCreateType; +use Chill\CalendarBundle\Form\CalendarDocEditType; +use Chill\CalendarBundle\Security\Voter\CalendarDocVoter; use Chill\CalendarBundle\Security\Voter\CalendarVoter; use Chill\DocGeneratorBundle\Repository\DocGeneratorTemplateRepository; +use Doctrine\ORM\EntityManagerInterface; use RuntimeException; +use Symfony\Component\Form\Extension\Core\Type\SubmitType; +use Symfony\Component\Form\FormFactoryInterface; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Security\Core\Exception\AccessDeniedException; @@ -32,18 +40,202 @@ class CalendarDocController private EngineInterface $engine; + private EntityManagerInterface $entityManager; + + private FormFactoryInterface $formFactory; + private Security $security; private SerializerInterface $serializer; private UrlGeneratorInterface $urlGenerator; - public function __construct(Security $security, DocGeneratorTemplateRepository $docGeneratorTemplateRepository, UrlGeneratorInterface $urlGenerator, EngineInterface $engine) - { - $this->security = $security; + public function __construct( + DocGeneratorTemplateRepository $docGeneratorTemplateRepository, + EngineInterface $engine, + EntityManagerInterface $entityManager, + FormFactoryInterface $formFactory, + Security $security, + UrlGeneratorInterface $urlGenerator + ) { $this->docGeneratorTemplateRepository = $docGeneratorTemplateRepository; - $this->urlGenerator = $urlGenerator; $this->engine = $engine; + $this->entityManager = $entityManager; + $this->formFactory = $formFactory; + $this->security = $security; + $this->urlGenerator = $urlGenerator; + } + + /** + * @Route("/{_locale}/calendar/calendar-doc/{id}/new", name="chill_calendar_calendardoc_new") + */ + public function create(Calendar $calendar, Request $request): Response + { + $calendarDoc = (new CalendarDoc($calendar, null))->setCalendar($calendar); + + if (!$this->security->isGranted(CalendarDocVoter::EDIT, $calendarDoc)) { + throw new AccessDeniedHttpException(); + } + + // set variables + switch ($calendarDoc->getCalendar()->getContext()) { + case 'accompanying_period': + $view = '@ChillCalendar/CalendarDoc/new_accompanying_period.html.twig'; + $returnRoute = 'chill_calendar_calendar_list_by_period'; + $returnParams = ['id' => $calendarDoc->getCalendar()->getAccompanyingPeriod()->getId()]; + + break; + + case 'person': + $view = '@ChillCalendar/CalendarDoc/new_person.html.twig'; + $returnRoute = 'chill_calendar_calendar_list_by_person'; + $returnParams = ['id' => $calendarDoc->getCalendar()->getPerson()->getId()]; + + break; + + default: + throw new UnexpectedValueException('Unsupported context'); + } + + $calendarDocDTO = new CalendarDoc\CalendarDocCreateDTO(); + $form = $this->formFactory->create(CalendarDocCreateType::class, $calendarDocDTO); + + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $calendarDoc->createFromDTO($calendarDocDTO); + + $this->entityManager->persist($calendarDoc); + $this->entityManager->flush(); + + if ($request->query->has('returnPath')) { + return new RedirectResponse($request->query->get('returnPath')); + } + + return new RedirectResponse( + $this->urlGenerator->generate($returnRoute, $returnParams) + ); + } + + return new Response( + $this->engine->render( + $view, + ['calendar_doc' => $calendarDoc, 'form' => $form->createView()] + ) + ); + } + + /** + * @Route("/{_locale}/calendar/calendar-doc/{id}/delete", name="chill_calendar_calendardoc_delete") + */ + public function delete(CalendarDoc $calendarDoc, Request $request): Response + { + if (!$this->security->isGranted(CalendarDocVoter::EDIT, $calendarDoc)) { + throw new AccessDeniedHttpException('Not authorized to delete document'); + } + + switch ($calendarDoc->getCalendar()->getContext()) { + case 'accompanying_period': + $view = '@ChillCalendar/CalendarDoc/delete_accompanying_period.html.twig'; + $returnRoute = 'chill_calendar_calendar_list_by_period'; + $returnParams = ['id' => $calendarDoc->getCalendar()->getAccompanyingPeriod()->getId()]; + + break; + + case 'person': + $view = '@ChillCalendar/CalendarDoc/delete_person.html.twig'; + $returnRoute = 'chill_calendar_calendar_list_by_person'; + $returnParams = ['id' => $calendarDoc->getCalendar()->getPerson()->getId()]; + + break; + } + + $form = $this->formFactory->createBuilder() + ->add('submit', SubmitType::class, [ + 'label' => 'Delete', + ]) + ->getForm(); + + $form->handleRequest($request); + + if ($form->isSubmitted()) { + $this->entityManager->remove($calendarDoc); + $this->entityManager->flush(); + + if ($request->query->has('returnPath')) { + return new RedirectResponse($request->query->get('returnPath')); + } + + return new RedirectResponse( + $this->urlGenerator->generate($returnRoute, $returnParams) + ); + } + + return new Response( + $this->engine->render( + $view, + [ + 'calendar_doc' => $calendarDoc, + 'form' => $form->createView(), + ] + ) + ); + } + + /** + * @Route("/{_locale}/calendar/calendar-doc/{id}/edit", name="chill_calendar_calendardoc_edit") + */ + public function edit(CalendarDoc $calendarDoc, Request $request): Response + { + if (!$this->security->isGranted(CalendarDocVoter::EDIT, $calendarDoc)) { + throw new AccessDeniedHttpException(); + } + + // set variables + switch ($calendarDoc->getCalendar()->getContext()) { + case 'accompanying_period': + $view = '@ChillCalendar/CalendarDoc/edit_accompanying_period.html.twig'; + $returnRoute = 'chill_calendar_calendar_list_by_period'; + $returnParams = ['id' => $calendarDoc->getCalendar()->getAccompanyingPeriod()->getId()]; + + break; + + case 'person': + $view = '@ChillCalendar/CalendarDoc/edit_person.html.twig'; + $returnRoute = 'chill_calendar_calendar_list_by_person'; + $returnParams = ['id' => $calendarDoc->getCalendar()->getPerson()->getId()]; + + break; + + default: + throw new UnexpectedValueException('Unsupported context'); + } + + $calendarDocEditDTO = new CalendarDoc\CalendarDocEditDTO($calendarDoc); + $form = $this->formFactory->create(CalendarDocEditType::class, $calendarDocEditDTO); + + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $calendarDoc->editFromDTO($calendarDocEditDTO); + + $this->entityManager->flush(); + + if ($request->query->has('returnPath')) { + return new RedirectResponse($request->query->get('returnPath')); + } + + return new RedirectResponse( + $this->urlGenerator->generate($returnRoute, $returnParams) + ); + } + + return new Response( + $this->engine->render( + $view, + ['calendar_doc' => $calendarDoc, 'form' => $form->createView()] + ) + ); } /** diff --git a/src/Bundle/ChillCalendarBundle/DependencyInjection/ChillCalendarExtension.php b/src/Bundle/ChillCalendarBundle/DependencyInjection/ChillCalendarExtension.php index 08890912b..c848366bb 100644 --- a/src/Bundle/ChillCalendarBundle/DependencyInjection/ChillCalendarExtension.php +++ b/src/Bundle/ChillCalendarBundle/DependencyInjection/ChillCalendarExtension.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\CalendarBundle\DependencyInjection; +use Chill\CalendarBundle\Security\Voter\CalendarVoter; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; @@ -52,9 +53,10 @@ class ChillCalendarExtension extends Extension implements PrependExtensionInterf { $this->preprendRoutes($container); $this->prependCruds($container); + $this->prependRoleHierarchy($container); } - protected function prependCruds(ContainerBuilder $container) + private function prependCruds(ContainerBuilder $container) { $container->prependExtensionConfig('chill_main', [ 'cruds' => [ @@ -130,7 +132,18 @@ class ChillCalendarExtension extends Extension implements PrependExtensionInterf ]); } - protected function preprendRoutes(ContainerBuilder $container) + private function prependRoleHierarchy(ContainerBuilder $container): void + { + $container->prependExtensionConfig('security', [ + 'role_hierarchy' => [ + CalendarVoter::CREATE => [CalendarVoter::SEE], + CalendarVoter::EDIT => [CalendarVoter::SEE], + CalendarVoter::DELETE => [CalendarVoter::SEE], + ], + ]); + } + + private function preprendRoutes(ContainerBuilder $container) { $container->prependExtensionConfig('chill_main', [ 'routing' => [ diff --git a/src/Bundle/ChillCalendarBundle/Entity/Calendar.php b/src/Bundle/ChillCalendarBundle/Entity/Calendar.php index 351954492..0f112cf36 100644 --- a/src/Bundle/ChillCalendarBundle/Entity/Calendar.php +++ b/src/Bundle/ChillCalendarBundle/Entity/Calendar.php @@ -18,6 +18,7 @@ use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface; use Chill\MainBundle\Doctrine\Model\TrackUpdateTrait; use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable; use Chill\MainBundle\Entity\Embeddable\PrivateCommentEmbeddable; +use Chill\MainBundle\Entity\HasCentersInterface; use Chill\MainBundle\Entity\Location; use Chill\MainBundle\Entity\User; use Chill\PersonBundle\Entity\AccompanyingPeriod; @@ -48,7 +49,7 @@ use function in_array; * "chill_calendar_calendar": Calendar::class * }) */ -class Calendar implements TrackCreationInterface, TrackUpdateInterface +class Calendar implements TrackCreationInterface, TrackUpdateInterface, HasCentersInterface { use RemoteCalendarTrait; @@ -312,6 +313,20 @@ class Calendar implements TrackCreationInterface, TrackUpdateInterface return $this->cancelReason; } + public function getCenters(): ?iterable + { + switch ($this->getContext()) { + case 'person': + return [$this->getPerson()->getCenter()]; + + case 'accompanying_period': + return $this->getAccompanyingPeriod()->getCenters(); + + default: + throw new LogicException('context not supported: ' . $this->getContext()); + } + } + public function getComment(): CommentEmbeddable { return $this->comment; diff --git a/src/Bundle/ChillCalendarBundle/Entity/CalendarDoc.php b/src/Bundle/ChillCalendarBundle/Entity/CalendarDoc.php index 458f38654..7012b64e0 100644 --- a/src/Bundle/ChillCalendarBundle/Entity/CalendarDoc.php +++ b/src/Bundle/ChillCalendarBundle/Entity/CalendarDoc.php @@ -11,6 +11,8 @@ declare(strict_types=1); namespace Chill\CalendarBundle\Entity; +use Chill\CalendarBundle\Entity\CalendarDoc\CalendarDocCreateDTO; +use Chill\CalendarBundle\Entity\CalendarDoc\CalendarDocEditDTO; use Chill\DocStoreBundle\Entity\StoredObject; use Chill\MainBundle\Doctrine\Model\TrackCreationInterface; use Chill\MainBundle\Doctrine\Model\TrackCreationTrait; @@ -52,14 +54,14 @@ class CalendarDoc implements TrackCreationInterface, TrackUpdateInterface * @ORM\ManyToOne(targetEntity=StoredObject::class, cascade={"persist"}) * @ORM\JoinColumn(nullable=false) */ - private StoredObject $storedObject; + private ?StoredObject $storedObject; /** * @ORM\Column(type="boolean", nullable=false, options={"default": false}) */ private bool $trackDateTimeVersion = false; - public function __construct(Calendar $calendar, StoredObject $storedObject) + public function __construct(Calendar $calendar, ?StoredObject $storedObject) { $this->setCalendar($calendar); @@ -67,6 +69,22 @@ class CalendarDoc implements TrackCreationInterface, TrackUpdateInterface $this->datetimeVersion = $calendar->getDateTimeVersion(); } + public function createFromDTO(CalendarDocCreateDTO $calendarDocCreateDTO): void + { + $this->storedObject = $calendarDocCreateDTO->doc; + $this->storedObject->setTitle($calendarDocCreateDTO->title); + } + + public function editFromDTO(CalendarDocEditDTO $calendarDocEditDTO): void + { + if (null !== $calendarDocEditDTO->doc) { + $calendarDocEditDTO->doc->setTitle($this->getStoredObject()->getTitle()); + $this->setStoredObject($calendarDocEditDTO->doc); + } + + $this->getStoredObject()->setTitle($calendarDocEditDTO->title); + } + public function getCalendar(): Calendar { return $this->calendar; diff --git a/src/Bundle/ChillCalendarBundle/Entity/CalendarDoc/CalendarDocCreateDTO.php b/src/Bundle/ChillCalendarBundle/Entity/CalendarDoc/CalendarDocCreateDTO.php new file mode 100644 index 000000000..b7209e46f --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Entity/CalendarDoc/CalendarDocCreateDTO.php @@ -0,0 +1,30 @@ +title = $calendarDoc->getStoredObject()->getTitle(); + } +} diff --git a/src/Bundle/ChillCalendarBundle/Form/CalendarDocCreateType.php b/src/Bundle/ChillCalendarBundle/Form/CalendarDocCreateType.php new file mode 100644 index 000000000..b77886097 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Form/CalendarDocCreateType.php @@ -0,0 +1,42 @@ +add('title', TextType::class, [ + 'label' => 'chill_calendar.Document title', + 'required' => true, + ]) + ->add('doc', StoredObjectType::class, [ + 'label' => 'chill_calendar.Document object', + 'required' => true, + ]); + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + 'data_class' => CalendarDocCreateDTO::class, + ]); + } +} diff --git a/src/Bundle/ChillCalendarBundle/Form/CalendarDocEditType.php b/src/Bundle/ChillCalendarBundle/Form/CalendarDocEditType.php new file mode 100644 index 000000000..26a9d59e2 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Form/CalendarDocEditType.php @@ -0,0 +1,41 @@ +add('title', TextType::class, [ + 'label' => 'chill_calendar.Document title', + 'required' => true, + ]) + ->add('doc', StoredObjectType::class, [ + 'label' => 'chill_calendar.Document object', + ]); + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + 'data_class' => CalendarDocEditDTO::class, + ]); + } +} diff --git a/src/Bundle/ChillCalendarBundle/Repository/CalendarACLAwareRepository.php b/src/Bundle/ChillCalendarBundle/Repository/CalendarACLAwareRepository.php index 15d0d83c0..6861df341 100644 --- a/src/Bundle/ChillCalendarBundle/Repository/CalendarACLAwareRepository.php +++ b/src/Bundle/ChillCalendarBundle/Repository/CalendarACLAwareRepository.php @@ -64,31 +64,38 @@ class CalendarACLAwareRepository implements CalendarACLAwareRepositoryInterface return $qb; } + public function buildQueryByAccompanyingPeriodIgnoredByDates(AccompanyingPeriod $period, ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate): QueryBuilder + { + $qb = $this->em->createQueryBuilder(); + $qb->from(Calendar::class, 'c'); + + $andX = $qb->expr()->andX($qb->expr()->eq('c.accompanyingPeriod', ':period')); + $qb->setParameter('period', $period); + + if (null !== $startDate) { + $andX->add($qb->expr()->lt('c.startDate', ':startDate')); + $qb->setParameter('startDate', $startDate); + } + + if (null !== $endDate) { + $andX->add($qb->expr()->gt('c.endDate', ':endDate')); + $qb->setParameter('endDate', $endDate); + } + + $qb->where($andX); + + 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); + $this->addQueryByPersonWithoutDate($qb, $person); // filter by date if (null !== $startDate) { @@ -104,6 +111,30 @@ class CalendarACLAwareRepository implements CalendarACLAwareRepositoryInterface return $qb; } + /** + * Base implementation. The list of allowed accompanying period is retrieved "manually" from @see{AccompanyingPeriodACLAwareRepository}. + */ + public function buildQueryByPersonIgnoredByDates(Person $person, ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate): QueryBuilder + { + $qb = $this->em->createQueryBuilder() + ->from(Calendar::class, 'c'); + + $this->addQueryByPersonWithoutDate($qb, $person); + + // filter by date + if (null !== $startDate) { + $qb->andWhere($qb->expr()->lt('c.startDate', ':startDate')) + ->setParameter('startDate', $startDate); + } + + if (null !== $endDate) { + $qb->andWhere($qb->expr()->gt('c.endDate', ':endDate')) + ->setParameter('endDate', $endDate); + } + + return $qb; + } + public function countByAccompanyingPeriod(AccompanyingPeriod $period, ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate): int { $qb = $this->buildQueryByAccompanyingPeriod($period, $startDate, $endDate)->select('count(c)'); @@ -119,6 +150,21 @@ class CalendarACLAwareRepository implements CalendarACLAwareRepositoryInterface ->getSingleScalarResult(); } + public function countIgnoredByAccompanyingPeriod(AccompanyingPeriod $period, ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate): int + { + $qb = $this->buildQueryByAccompanyingPeriodIgnoredByDates($period, $startDate, $endDate)->select('count(c)'); + + return $qb->getQuery()->getSingleScalarResult(); + } + + public function countIgnoredByPerson(Person $person, ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate): int + { + return $this->buildQueryByPersonIgnoredByDates($person, $startDate, $endDate) + ->select('COUNT(c)') + ->getQuery() + ->getSingleScalarResult(); + } + /** * @return array|Calendar[] */ @@ -160,4 +206,25 @@ class CalendarACLAwareRepository implements CalendarACLAwareRepositoryInterface return $qb->getQuery()->getResult(); } + + private function addQueryByPersonWithoutDate(QueryBuilder $qb, Person $person): void + { + // find the reachable accompanying periods for person + $periods = $this->accompanyingPeriodACLAwareRepository->findByPerson($person, AccompanyingPeriodVoter::SEE); + + $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); + } } diff --git a/src/Bundle/ChillCalendarBundle/Repository/CalendarACLAwareRepositoryInterface.php b/src/Bundle/ChillCalendarBundle/Repository/CalendarACLAwareRepositoryInterface.php index d860c3470..70fb02590 100644 --- a/src/Bundle/ChillCalendarBundle/Repository/CalendarACLAwareRepositoryInterface.php +++ b/src/Bundle/ChillCalendarBundle/Repository/CalendarACLAwareRepositoryInterface.php @@ -32,6 +32,18 @@ interface CalendarACLAwareRepositoryInterface */ public function countByPerson(Person $person, ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate): int; + /** + * Return the number or calendars associated with an accompanyign period which **does not** match the date conditions. + */ + public function countIgnoredByAccompanyingPeriod(AccompanyingPeriod $period, ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate): int; + + /** + * Return the number or calendars associated with a person which **does not** match the date conditions. + * + * See condition on @see{self::findByPerson}. + */ + public function countIgnoredByPerson(Person $person, ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate): int; + /** * @return array|Calendar[] */ diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/_documents.twig.html b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/_documents.twig.html index 187f36dd7..a568ec009 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/_documents.twig.html +++ b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/_documents.twig.html @@ -3,11 +3,6 @@ {% import "@ChillDocStore/Macro/macro.html.twig" as m %} {% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %} - -