From a73dca5efe81b0fbea96f27f33e2824e58e3f6c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 25 Nov 2022 17:27:42 +0100 Subject: [PATCH 1/5] Feature: [calendar] show the number of calendars ignored by the date filter, in the list --- .../Controller/CalendarController.php | 2 + .../Repository/CalendarACLAwareRepository.php | 101 +++++++++++++++--- .../CalendarACLAwareRepositoryInterface.php | 12 +++ .../listByAccompanyingCourse.html.twig | 8 +- .../views/Calendar/listByPerson.html.twig | 10 +- .../translations/messages+intl-icu.fr.yml | 8 ++ 6 files changed, 116 insertions(+), 25 deletions(-) create mode 100644 src/Bundle/ChillCalendarBundle/translations/messages+intl-icu.fr.yml diff --git a/src/Bundle/ChillCalendarBundle/Controller/CalendarController.php b/src/Bundle/ChillCalendarBundle/Controller/CalendarController.php index dea2099e1..e35a9619b 100644 --- a/src/Bundle/ChillCalendarBundle/Controller/CalendarController.php +++ b/src/Bundle/ChillCalendarBundle/Controller/CalendarController.php @@ -227,6 +227,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), ]); } @@ -258,6 +259,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), ]); } 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/listByAccompanyingCourse.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/listByAccompanyingCourse.html.twig index 56c1ba880..d85c5237e 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/listByAccompanyingCourse.html.twig +++ b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/listByAccompanyingCourse.html.twig @@ -27,9 +27,11 @@ {% if calendarItems|length == 0 %}

- {{ "There is no calendar items."|trans }} - + {% if nbIgnored == 0 %} + {{ "There is no calendar items."|trans }} + {% else %} + {{ 'chill_calendar.There are count ignored calendars by date filter'|trans({'nbIgnored': nbIgnored}) }} + {% endif %}

{% else %} {{ include('@ChillCalendar/Calendar/_list.html.twig', {context: 'accompanying_course'}) }} diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/listByPerson.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/listByPerson.html.twig index dc849202e..7c5fdc639 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/listByPerson.html.twig +++ b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/listByPerson.html.twig @@ -26,11 +26,11 @@ {% if calendarItems|length == 0 %}

- {{ "There is no calendar items."|trans }} - - {{ 'Create'|trans }} - + {% if nbIgnored == 0 %} + {{ "There is no calendar items."|trans }} + {% else %} + {{ 'chill_calendar.There are count ignored calendars by date filter'|trans({'nbIgnored': nbIgnored}) }} + {% endif %}

{% else %} {{ include ('@ChillCalendar/Calendar/_list.html.twig', {context: 'person'}) }} diff --git a/src/Bundle/ChillCalendarBundle/translations/messages+intl-icu.fr.yml b/src/Bundle/ChillCalendarBundle/translations/messages+intl-icu.fr.yml new file mode 100644 index 000000000..4bbc105b9 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/translations/messages+intl-icu.fr.yml @@ -0,0 +1,8 @@ +chill_calendar: + There are count ignored calendars by date filter: >- + {nbIgnored, plural, + =0 {Il n'y a aucun rendez-vous ignoré par le filtre de date.} + one {Il y a un rendez-vous ignoré par le filtre de date. Modifiez le filtre de date pour le voir apparaitre.} + few {# rendez-vous sont ignorés par le filtre de date. Modifiez le filtre de date pour les voir apparaitre.} + other {# rendez-vous sont ignorés par le filtre de date. Modifiez le filtre de date pour les voir apparaitre.} + } From 74673380aa2be4284d206c5ea1f2968810e48408 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 28 Nov 2022 12:22:58 +0100 Subject: [PATCH 2/5] Fixed: [calendar] refactor ACL on calendar --- .../Controller/CalendarController.php | 13 +++- .../ChillCalendarExtension.php | 17 +++++- .../ChillCalendarBundle/Entity/Calendar.php | 17 +++++- .../Security/Voter/CalendarDocVoter.php | 61 +++++++++++++++++++ .../Security/Voter/CalendarVoter.php | 56 ++++++++--------- .../Authorization/AuthorizationHelper.php | 2 +- 6 files changed, 130 insertions(+), 36 deletions(-) create mode 100644 src/Bundle/ChillCalendarBundle/Security/Voter/CalendarDocVoter.php diff --git a/src/Bundle/ChillCalendarBundle/Controller/CalendarController.php b/src/Bundle/ChillCalendarBundle/Controller/CalendarController.php index e35a9619b..d0336d254 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; @@ -146,6 +147,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()); } @@ -207,6 +210,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'); @@ -239,6 +244,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'); @@ -308,7 +315,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()]); @@ -318,6 +325,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); @@ -437,6 +446,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/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/Security/Voter/CalendarDocVoter.php b/src/Bundle/ChillCalendarBundle/Security/Voter/CalendarDocVoter.php new file mode 100644 index 000000000..452286e85 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Security/Voter/CalendarDocVoter.php @@ -0,0 +1,61 @@ +security = $security; + } + + protected function supports($attribute, $subject): bool + { + return in_array($attribute, self::ALL, true) && $subject instanceof CalendarDoc; + } + + /** + * @param CalendarDoc $subject + * @param mixed $attribute + */ + protected function voteOnAttribute($attribute, $subject, TokenInterface $token): bool + { + switch ($attribute) { + case self::EDIT: + return $this->security->isGranted(CalendarVoter::EDIT, $subject->getCalendar()); + + case self::SEE: + return $this->security->isGranted(CalendarVoter::SEE, $subject->getCalendar()); + + default: + throw new UnexpectedValueException('Attribute not supported: ' . $attribute); + } + } +} diff --git a/src/Bundle/ChillCalendarBundle/Security/Voter/CalendarVoter.php b/src/Bundle/ChillCalendarBundle/Security/Voter/CalendarVoter.php index 996b5ebfb..0e2be1d0f 100644 --- a/src/Bundle/ChillCalendarBundle/Security/Voter/CalendarVoter.php +++ b/src/Bundle/ChillCalendarBundle/Security/Voter/CalendarVoter.php @@ -20,13 +20,14 @@ namespace Chill\CalendarBundle\Security\Voter; use Chill\CalendarBundle\Entity\Calendar; use Chill\MainBundle\Security\Authorization\AbstractChillVoter; +use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface; use Chill\MainBundle\Security\Authorization\VoterHelperFactoryInterface; use Chill\MainBundle\Security\Authorization\VoterHelperInterface; use Chill\MainBundle\Security\ProvideRoleHierarchyInterface; +use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter; -use Chill\PersonBundle\Security\Authorization\PersonVoter; use LogicException; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Security; @@ -41,6 +42,10 @@ class CalendarVoter extends AbstractChillVoter implements ProvideRoleHierarchyIn public const SEE = 'CHILL_CALENDAR_CALENDAR_SEE'; + private AuthorizationHelperInterface $authorizationHelper; + + private CenterResolverManagerInterface $centerResolverManager; + private Security $security; private VoterHelperInterface $voterHelper; @@ -52,8 +57,8 @@ class CalendarVoter extends AbstractChillVoter implements ProvideRoleHierarchyIn $this->security = $security; $this->voterHelper = $voterHelperFactory ->generate(self::class) - ->addCheckFor(AccompanyingPeriod::class, [self::SEE]) - ->addCheckFor(Person::class, [self::SEE]) + ->addCheckFor(AccompanyingPeriod::class, [self::SEE, self::CREATE]) + ->addCheckFor(Person::class, [self::SEE, self::CREATE]) ->addCheckFor(Calendar::class, [self::SEE, self::CREATE, self::EDIT, self::DELETE]) ->build(); } @@ -93,48 +98,37 @@ class CalendarVoter extends AbstractChillVoter implements ProvideRoleHierarchyIn if ($subject instanceof AccompanyingPeriod) { switch ($attribute) { case self::SEE: + case self::CREATE: if ($subject->getStep() === AccompanyingPeriod::STEP_DRAFT) { return false; } // we first check here that the user has read access to the period - return $this->security->isGranted(AccompanyingPeriodVoter::SEE, $subject); + if (!$this->security->isGranted(AccompanyingPeriodVoter::SEE, $subject)) { + return false; + } - default: - throw new LogicException('subject not implemented'); + // There is no scope on Calendar, but there are some on accompanying period + // so, to ignore AccompanyingPeriod's scopes, we create a blank Calendar + // linked with an accompanying period. + return $this->voterHelper->voteOnAttribute($attribute, (new Calendar())->setAccompanyingPeriod($subject), $token); } } elseif ($subject instanceof Person) { switch ($attribute) { case self::SEE: - return $this->security->isGranted(PersonVoter::SEE, $subject); - - default: - throw new LogicException('subject not implemented'); + case self::CREATE: + return $this->voterHelper->voteOnAttribute($attribute, $subject, $token); } } elseif ($subject instanceof Calendar) { - if (null !== $period = $subject->getAccompanyingPeriod()) { - switch ($attribute) { - case self::SEE: - case self::EDIT: - case self::CREATE: - return $this->security->isGranted(AccompanyingPeriodVoter::SEE, $period); - - case self::DELETE: - 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); - } + switch ($attribute) { + case self::SEE: + case self::EDIT: + case self::CREATE: + case self::DELETE: + return $this->voterHelper->voteOnAttribute($attribute, $subject, $token); } } - throw new LogicException('attribute not implemented'); + throw new LogicException('attribute or not implemented'); } } diff --git a/src/Bundle/ChillMainBundle/Security/Authorization/AuthorizationHelper.php b/src/Bundle/ChillMainBundle/Security/Authorization/AuthorizationHelper.php index 930a2c4d1..33e9a3db6 100644 --- a/src/Bundle/ChillMainBundle/Security/Authorization/AuthorizationHelper.php +++ b/src/Bundle/ChillMainBundle/Security/Authorization/AuthorizationHelper.php @@ -309,7 +309,7 @@ class AuthorizationHelper implements AuthorizationHelperInterface if ($this->isRoleReached($attribute, $roleScope->getRole())) { //if yes, we have a right on something... // perform check on scope if necessary - if ($this->scopeResolverDispatcher->isConcerned($entity)) { + if ($this->scopeResolverDispatcher->isConcerned($entity)) {// here, we should also check that the role need a scope $scope = $this->scopeResolverDispatcher->resolveScope($entity); if (null === $scope) { From fc15b85d11628e9506b9d66293cd32b04ef94183 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 28 Nov 2022 12:24:10 +0100 Subject: [PATCH 3/5] DX: [accompanying period] refactor the getCenters method Use more portable spl_object_hash for listing different centers --- .../ChillPersonBundle/Entity/AccompanyingPeriod.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php index 0b6efe328..ae7b927a6 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php @@ -53,7 +53,7 @@ use Symfony\Component\Validator\GroupSequenceProviderInterface; use UnexpectedValueException; -use function in_array; +use function array_key_exists; use const SORT_REGULAR; /** @@ -644,16 +644,18 @@ class AccompanyingPeriod implements public function getCenters(): ?iterable { + $centers = []; + foreach ($this->getPersons() as $person) { if ( - !in_array($person->getCenter(), $centers ?? [], true) - && null !== $person->getCenter() + null !== $person->getCenter() + && !array_key_exists(spl_object_hash($person->getCenter()), $centers) ) { - $centers[] = $person->getCenter(); + $centers[spl_object_hash($person->getCenter())] = $person->getCenter(); } } - return $centers ?? null; + return array_values($centers); } /** From 39f410dc8f9beddc2346a93936a121e3ed795e4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 28 Nov 2022 14:33:06 +0100 Subject: [PATCH 4/5] Feature: [Calendar doc] complete CRUD for associating documents within Calendar --- .../Controller/CalendarDocController.php | 200 +++++++++++++++++- .../Entity/CalendarDoc.php | 22 +- .../CalendarDoc/CalendarDocCreateDTO.php | 30 +++ .../Entity/CalendarDoc/CalendarDocEditDTO.php | 35 +++ .../Form/CalendarDocCreateType.php | 42 ++++ .../Form/CalendarDocEditType.php | 41 ++++ .../views/Calendar/_documents.twig.html | 46 ++-- .../Resources/views/Calendar/_list.html.twig | 38 +++- .../delete_accompanying_period.html.twig | 19 ++ .../views/CalendarDoc/delete_person.html.twig | 18 ++ .../edit_accompanying_period.html.twig | 40 ++++ .../views/CalendarDoc/edit_person.html.twig | 39 ++++ .../new_accompanying_period.html.twig | 40 ++++ .../views/CalendarDoc/new_person.html.twig | 39 ++++ .../Tests/Entity/CalendarDocTest.php | 63 ++++++ .../translations/messages.fr.yml | 7 + 16 files changed, 688 insertions(+), 31 deletions(-) create mode 100644 src/Bundle/ChillCalendarBundle/Entity/CalendarDoc/CalendarDocCreateDTO.php create mode 100644 src/Bundle/ChillCalendarBundle/Entity/CalendarDoc/CalendarDocEditDTO.php create mode 100644 src/Bundle/ChillCalendarBundle/Form/CalendarDocCreateType.php create mode 100644 src/Bundle/ChillCalendarBundle/Form/CalendarDocEditType.php create mode 100644 src/Bundle/ChillCalendarBundle/Resources/views/CalendarDoc/delete_accompanying_period.html.twig create mode 100644 src/Bundle/ChillCalendarBundle/Resources/views/CalendarDoc/delete_person.html.twig create mode 100644 src/Bundle/ChillCalendarBundle/Resources/views/CalendarDoc/edit_accompanying_period.html.twig create mode 100644 src/Bundle/ChillCalendarBundle/Resources/views/CalendarDoc/edit_person.html.twig create mode 100644 src/Bundle/ChillCalendarBundle/Resources/views/CalendarDoc/new_accompanying_period.html.twig create mode 100644 src/Bundle/ChillCalendarBundle/Resources/views/CalendarDoc/new_person.html.twig create mode 100644 src/Bundle/ChillCalendarBundle/Tests/Entity/CalendarDocTest.php 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/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/Resources/views/Calendar/_documents.twig.html b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/_documents.twig.html index 187f36dd7..e05711422 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/_documents.twig.html +++ b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/_documents.twig.html @@ -17,27 +17,37 @@ {% for d in calendar.documents %} - - -
    -
  • - {{ mm.mimeIcon(d.storedObject.type) }} - {{ d.storedObject.title }} - -
      - {% if chill_document_is_editable(d.storedObject) %} -
    • - {{ d.storedObject|chill_document_edit_button }} -
    • - {% endif %} + {% if is_granted('CHILL_CALENDAR_DOC_SEE', d) %} + + +
      • - {{ m.download_button(d.storedObject, d.storedObject.title) }} + {{ mm.mimeIcon(d.storedObject.type) }} + {{ d.storedObject.title }} + +
          + {% if chill_document_is_editable(d.storedObject) and is_granted('CHILL_CALENDAR_DOC_EDIT', d) %} +
        • + +
        • +
        • + {{ d.storedObject|chill_document_edit_button }} +
        • + {% endif %} + {% if is_granted('CHILL_CALENDAR_DOC_EDIT', d) %} +
        • + +
        • + {% endif %} +
        • + {{ m.download_button(d.storedObject, d.storedObject.title) }} +
        • +
      - -
    - - + + + {% endif %} {% endfor %} diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/_list.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/_list.html.twig index af22a96ec..f2c94e53f 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/_list.html.twig +++ b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/_list.html.twig @@ -112,13 +112,37 @@
      - {% if is_granted('CHILL_CALENDAR_CALENDAR_SEE', calendar) and hasDocs %} -
    • - - {{ 'chill_calendar.Add a document'|trans }} - -
    • + {% if is_granted('CHILL_CALENDAR_CALENDAR_SEE', calendar) %} + {% if not hasDocs %} +
    • + + {{ 'chill_calendar.Add a document'|trans }} + +
    • + {% else %} +
    • + +
    • + {% endif %} {% endif %} {% if accompanyingCourse is defined and is_granted('CHILL_ACTIVITY_CREATE', accompanyingCourse) and calendar.activity is null %}
    • diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/CalendarDoc/delete_accompanying_period.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/CalendarDoc/delete_accompanying_period.html.twig new file mode 100644 index 000000000..b26b280ac --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/views/CalendarDoc/delete_accompanying_period.html.twig @@ -0,0 +1,19 @@ +{% extends "@ChillPerson/AccompanyingCourse/layout.html.twig" %} + +{% set activeRouteKey = 'chill_calendar_calendar_list' %} + +{% block title %}{{ 'chill_calendar.Remove a calendar document' |trans }}{% endblock title %} + +{% set accompanyingCourse = calendar_doc.calendar.accompanyingPeriod %} +{% set accompanyingCourseId = accompanyingCourse.id %} + +{% block content %} + {{ include('@ChillMain/Util/confirmation_template.html.twig', + { + 'title' : 'chill_calendar.Remove a calendar document'|trans, + 'confirm_question' : 'chill_calendar.Are you sure you want to remove the doc?'|trans, + 'cancel_route' : 'chill_calendar_calendar_list_by_period', + 'cancel_parameters' : { 'id' : accompanyingCourse.id }, + 'form' : form + } ) }} +{% endblock %} diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/CalendarDoc/delete_person.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/CalendarDoc/delete_person.html.twig new file mode 100644 index 000000000..64e03d37e --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/views/CalendarDoc/delete_person.html.twig @@ -0,0 +1,18 @@ +{% extends "@ChillPerson/Person/layout.html.twig" %} + +{% set activeRouteKey = 'chill_calendar_calendar_list' %} + +{% block title %}{{ 'chill_calendar.Edit a document' |trans }}{% endblock title %} + +{% set person = calendar_doc.calendar.person %} + +{% block content %} + {{ include('@ChillMain/Util/confirmation_template.html.twig', + { + 'title' : 'chill_calendar.Remove a calendar document'|trans, + 'confirm_question' : 'chill_calendar.Are you sure you want to remove the doc?'|trans, + 'cancel_route' : 'chill_calendar_calendar_list_by_person', + 'cancel_parameters' : { 'id' : person.id }, + 'form' : form + } ) }} +{% endblock %} diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/CalendarDoc/edit_accompanying_period.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/CalendarDoc/edit_accompanying_period.html.twig new file mode 100644 index 000000000..9941b000a --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/views/CalendarDoc/edit_accompanying_period.html.twig @@ -0,0 +1,40 @@ +{% extends "@ChillPerson/AccompanyingCourse/layout.html.twig" %} + +{% set activeRouteKey = 'chill_calendar_calendar_list' %} + +{% block title %}{{ 'chill_calendar.Edit a document' |trans }}{% endblock title %} + +{% set accompanyingCourse = calendar_doc.calendar.accompanyingPeriod %} +{% set accompanyingCourseId = accompanyingCourse.id %} + +{% block js %} + {{ parent() }} + {{ encore_entry_script_tags('mod_async_upload') }} +{% endblock %} + +{% block css %} + {{ parent() }} + {{ encore_entry_link_tags('mod_async_upload') }} +{% endblock %} + +{% block content %} + +

      {{ 'chill_calendar.Edit a document'|trans }}

      + + {{ form_start(form) }} + + {{ form_row(form.title) }} + {{ form_row(form.doc) }} + + + + {{ form_end(form) }} +{% endblock %} diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/CalendarDoc/edit_person.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/CalendarDoc/edit_person.html.twig new file mode 100644 index 000000000..8ed218d29 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/views/CalendarDoc/edit_person.html.twig @@ -0,0 +1,39 @@ +{% extends "@ChillPerson/Person/layout.html.twig" %} + +{% set activeRouteKey = 'chill_calendar_calendar_list' %} + +{% block title %}{{ 'chill_calendar.Edit a document' |trans }}{% endblock title %} + +{% set person = calendar_doc.calendar.person %} + +{% block js %} + {{ parent() }} + {{ encore_entry_script_tags('mod_async_upload') }} +{% endblock %} + +{% block css %} + {{ parent() }} + {{ encore_entry_link_tags('mod_async_upload') }} +{% endblock %} + +{% block content %} + +

      {{ 'chill_calendar.Edit a document'|trans }}

      + + {{ form_start(form) }} + + {{ form_row(form.title) }} + {{ form_row(form.doc) }} + + + + {{ form_end(form) }} +{% endblock %} diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/CalendarDoc/new_accompanying_period.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/CalendarDoc/new_accompanying_period.html.twig new file mode 100644 index 000000000..d397a9132 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/views/CalendarDoc/new_accompanying_period.html.twig @@ -0,0 +1,40 @@ +{% extends "@ChillPerson/AccompanyingCourse/layout.html.twig" %} + +{% set activeRouteKey = 'chill_calendar_calendar_list' %} + +{% block title %}{{ 'chill_calendar.Add a document' |trans }}{% endblock title %} + +{% set accompanyingCourse = calendar_doc.calendar.accompanyingPeriod %} +{% set accompanyingCourseId = accompanyingCourse.id %} + +{% block js %} + {{ parent() }} + {{ encore_entry_script_tags('mod_async_upload') }} +{% endblock %} + +{% block css %} + {{ parent() }} + {{ encore_entry_link_tags('mod_async_upload') }} +{% endblock %} + +{% block content %} + +

      {{ 'chill_calendar.Add a document'|trans }}

      + + {{ form_start(form) }} + + {{ form_row(form.title) }} + {{ form_row(form.doc) }} + + + + {{ form_end(form) }} +{% endblock %} diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/CalendarDoc/new_person.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/CalendarDoc/new_person.html.twig new file mode 100644 index 000000000..0a5003aed --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/views/CalendarDoc/new_person.html.twig @@ -0,0 +1,39 @@ +{% extends "@ChillPerson/Person/layout.html.twig" %} + +{% set activeRouteKey = 'chill_calendar_calendar_list' %} + +{% block title %}{{ 'chill_calendar.Add a document' |trans }}{% endblock title %} + +{% set person = calendar_doc.calendar.person %} + +{% block js %} + {{ parent() }} + {{ encore_entry_script_tags('mod_async_upload') }} +{% endblock %} + +{% block css %} + {{ parent() }} + {{ encore_entry_link_tags('mod_async_upload') }} +{% endblock %} + +{% block content %} + +

      {{ 'chill_calendar.Add a document'|trans }}

      + + {{ form_start(form) }} + + {{ form_row(form.title) }} + {{ form_row(form.doc) }} + + + + {{ form_end(form) }} +{% endblock %} diff --git a/src/Bundle/ChillCalendarBundle/Tests/Entity/CalendarDocTest.php b/src/Bundle/ChillCalendarBundle/Tests/Entity/CalendarDocTest.php new file mode 100644 index 000000000..fa6132b03 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Tests/Entity/CalendarDocTest.php @@ -0,0 +1,63 @@ +title = 'tagada'; + $create->doc = $obj1 = new StoredObject(); + + $doc->createFromDTO($create); + + $this->assertSame($obj1, $doc->getStoredObject()); + $this->assertEquals('tagada', $doc->getStoredObject()->getTitle()); + + $edit = new CalendarDoc\CalendarDocEditDTO($doc); + $edit->title = 'tsointsoin'; + + $doc->editFromDTO($edit); + + $this->assertSame($obj1, $doc->getStoredObject()); + $this->assertEquals('tsointsoin', $doc->getStoredObject()->getTitle()); + + $edit2 = new CalendarDoc\CalendarDocEditDTO($doc); + $edit2->doc = $obj2 = new StoredObject(); + + $doc->editFromDTO($edit2); + + $this->assertSame($obj2, $doc->getStoredObject()); + $this->assertEquals('tsointsoin', $doc->getStoredObject()->getTitle()); + + $edit3 = new CalendarDoc\CalendarDocEditDTO($doc); + $edit3->doc = $obj3 = new StoredObject(); + $edit3->title = 'tagada'; + + $doc->editFromDTO($edit3); + + $this->assertSame($obj3, $doc->getStoredObject()); + $this->assertEquals('tagada', $doc->getStoredObject()->getTitle()); + } +} diff --git a/src/Bundle/ChillCalendarBundle/translations/messages.fr.yml b/src/Bundle/ChillCalendarBundle/translations/messages.fr.yml index e5922ada3..7174e4ff9 100644 --- a/src/Bundle/ChillCalendarBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillCalendarBundle/translations/messages.fr.yml @@ -55,6 +55,13 @@ chill_calendar: Create and add a document: Créer et ajouter un document Save and add a document: Enregistrer et ajouter un document Create for me: Créer un rendez-vous pour moi-même + Edit a document: Modifier un document + Document title: Titre + Document object: Document + Add a document from template: Ajouter un document depuis un gabarit + Upload a document: Téléverser un document + Remove a calendar document: Supprimer un document d'un rendez-vous + Are you sure you want to remove the doc?: Êtes-vous sûr·e de vouloir supprimer le document associé ? remote_ms_graph: From 6cac2987240228945d284108f2edddcc3176c8ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 28 Nov 2022 14:40:33 +0100 Subject: [PATCH 5/5] Feature: [Calendar doc] show an alert when document is linked to a previous datetime version of the calendar --- .../Resources/views/Calendar/_documents.twig.html | 8 +++----- .../ChillCalendarBundle/translations/messages.fr.yml | 1 + 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/_documents.twig.html b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/_documents.twig.html index e05711422..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 %} - -
      @@ -24,6 +19,9 @@
    • {{ mm.mimeIcon(d.storedObject.type) }} {{ d.storedObject.title }} + {% if d.dateTimeVersion < d.calendar.dateTimeVersion %} + {{ 'chill_calendar.Document outdated'|trans }} + {% endif %}
        {% if chill_document_is_editable(d.storedObject) and is_granted('CHILL_CALENDAR_DOC_EDIT', d) %} diff --git a/src/Bundle/ChillCalendarBundle/translations/messages.fr.yml b/src/Bundle/ChillCalendarBundle/translations/messages.fr.yml index 7174e4ff9..4bbf27e7a 100644 --- a/src/Bundle/ChillCalendarBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillCalendarBundle/translations/messages.fr.yml @@ -62,6 +62,7 @@ chill_calendar: Upload a document: Téléverser un document Remove a calendar document: Supprimer un document d'un rendez-vous Are you sure you want to remove the doc?: Êtes-vous sûr·e de vouloir supprimer le document associé ? + Document outdated: La date et l'heure du rendez-vous ont été modifiés après la création du document remote_ms_graph: