From 2cb9dfc250af3e453cd82dcde8ff5c323b664909 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 26 May 2021 17:50:54 +0200 Subject: [PATCH] add tasks to global timeline --- ....twig => single_task_transition.html.twig} | 15 ++ ...TaskTaskLifeCycleEventTimelineProvider.php | 11 +- .../TaskLifeCycleEventTimelineProvider.php | 224 +++++++++++++----- .../config/services/timeline.yaml | 11 +- 4 files changed, 189 insertions(+), 72 deletions(-) rename src/Bundle/ChillTaskBundle/Resources/views/Timeline/{single_task_transition_person_context.html.twig => single_task_transition.html.twig} (68%) diff --git a/src/Bundle/ChillTaskBundle/Resources/views/Timeline/single_task_transition_person_context.html.twig b/src/Bundle/ChillTaskBundle/Resources/views/Timeline/single_task_transition.html.twig similarity index 68% rename from src/Bundle/ChillTaskBundle/Resources/views/Timeline/single_task_transition_person_context.html.twig rename to src/Bundle/ChillTaskBundle/Resources/views/Timeline/single_task_transition.html.twig index 234bbc134..fe65a1f1f 100644 --- a/src/Bundle/ChillTaskBundle/Resources/views/Timeline/single_task_transition_person_context.html.twig +++ b/src/Bundle/ChillTaskBundle/Resources/views/Timeline/single_task_transition.html.twig @@ -7,6 +7,9 @@ {% else %} {{ '%user% has created the task'|trans({ '%user%': event.author.username }) }} {% endif %} + {% if 'person' != context %} + / {{ task.person|chill_entity_render_box({'addLink': true}) }} + {% endif %}
@@ -29,5 +32,17 @@ {% endif %}
+ diff --git a/src/Bundle/ChillTaskBundle/Timeline/SingleTaskTaskLifeCycleEventTimelineProvider.php b/src/Bundle/ChillTaskBundle/Timeline/SingleTaskTaskLifeCycleEventTimelineProvider.php index 6e40cb4a3..57a3832af 100644 --- a/src/Bundle/ChillTaskBundle/Timeline/SingleTaskTaskLifeCycleEventTimelineProvider.php +++ b/src/Bundle/ChillTaskBundle/Timeline/SingleTaskTaskLifeCycleEventTimelineProvider.php @@ -18,6 +18,7 @@ namespace Chill\TaskBundle\Timeline; use Chill\MainBundle\Timeline\TimelineProviderInterface; +use Chill\MainBundle\Timeline\TimelineSingleQuery; use Doctrine\ORM\EntityManagerInterface; use Chill\TaskBundle\Entity\Task\SingleTaskPlaceEvent; use Chill\TaskBundle\Entity\SingleTask; @@ -25,9 +26,8 @@ use Symfony\Component\Workflow\Registry; use Symfony\Component\Workflow\Workflow; /** + * Provide timeline elements related to tasks, in tasks context * - * - * @author Julien Fastré */ class SingleTaskTaskLifeCycleEventTimelineProvider implements TimelineProviderInterface { @@ -63,7 +63,7 @@ class SingleTaskTaskLifeCycleEventTimelineProvider implements TimelineProviderIn $singleTaskMetadata = $this->em ->getClassMetadata(SingleTask::class); - return [ + return TimelineSingleQuery::fromArray([ 'id' => sprintf('%s.%s.%s', $metadata->getSchemaName(), $metadata->getTableName(), $metadata->getColumnName('id')), 'type' => self::TYPE, 'date' => $metadata->getColumnName('datetime'), @@ -77,8 +77,9 @@ class SingleTaskTaskLifeCycleEventTimelineProvider implements TimelineProviderIn sprintf('%s.%s', $singleTaskMetadata->getSchemaName(), $singleTaskMetadata->getTableName()), $singleTaskMetadata->getColumnName('id'), $args['task']->getId() - ) - ]; + ), + 'parameters' => [], + ]); } diff --git a/src/Bundle/ChillTaskBundle/Timeline/TaskLifeCycleEventTimelineProvider.php b/src/Bundle/ChillTaskBundle/Timeline/TaskLifeCycleEventTimelineProvider.php index db099a3e1..fe5a8f78d 100644 --- a/src/Bundle/ChillTaskBundle/Timeline/TaskLifeCycleEventTimelineProvider.php +++ b/src/Bundle/ChillTaskBundle/Timeline/TaskLifeCycleEventTimelineProvider.php @@ -21,43 +21,30 @@ use Chill\MainBundle\Timeline\TimelineProviderInterface; use Doctrine\ORM\EntityManagerInterface; use Chill\TaskBundle\Entity\Task\SingleTaskPlaceEvent; use Chill\TaskBundle\Entity\SingleTask; +use Chill\PersonBundle\Entity\Person; +use Symfony\Component\Security\Core\Security; use Symfony\Component\Workflow\Registry; use Symfony\Component\Workflow\Workflow; use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Chill\ActivityBundle\Security\Authorization\ActivityVoter; use Symfony\Component\Security\Core\Role\Role; +use Chill\MainBundle\Timeline\TimelineSingleQuery; /** - * + * Provide element for timeline for 'person' and 'center' context * - * @author Julien Fastré */ class TaskLifeCycleEventTimelineProvider implements TimelineProviderInterface { - /** - * - * @var EntityManagerInterface - */ - protected $em; + protected EntityManagerInterface $em; - /** - * - * @var Registry - */ - protected $registry; + protected Registry $registry; - /** - * - * @var AuthorizationHelper - */ - protected $authorizationHelper; + protected AuthorizationHelper $authorizationHelper; + + protected Security $security; - /** - * - * @var TokenStorageInterface - */ - protected $tokenStorage; const TYPE = 'chill_task.transition'; @@ -65,60 +52,172 @@ class TaskLifeCycleEventTimelineProvider implements TimelineProviderInterface EntityManagerInterface $em, Registry $registry, AuthorizationHelper $authorizationHelper, - TokenStorageInterface $tokenStorage + Security $security ) { $this->em = $em; $this->registry = $registry; $this->authorizationHelper = $authorizationHelper; - $this->tokenStorage = $tokenStorage; + $this->security = $security; } public function fetchQuery($context, $args) { - if ($context !== 'person') { - throw new \LogicException(sprintf('%s is not able ' - . 'to render context %s', self::class, $context)); - } - $metadata = $this->em ->getClassMetadata(SingleTaskPlaceEvent::class); - $singleTaskMetadata = $this->em - ->getClassMetadata(SingleTask::class); - $user = $this->tokenStorage->getToken()->getUser(); - $circles = $this->authorizationHelper->getReachableCircles( - $user, new Role(ActivityVoter::SEE_DETAILS), $args['person']->getCenter()); - - if (count($circles) > 0) { - $circlesId = \array_map(function($c) { return $c->getId(); }, $circles); - $circleRestriction = sprintf('%s.%s.%s IN (%s)', - $singleTaskMetadata->getSchemaName(), // chill_task schema - $singleTaskMetadata->getTableName(), // single_task table name - $singleTaskMetadata->getAssociationMapping('circle')['joinColumns'][0]['name'], - \implode(', ', $circlesId) - ); - } else { - $circleRestriction = 'FALSE = TRUE'; + switch ($context) { + case 'person': + [ $where, $parameters ] = $this->getWhereClauseForPerson($args['person']); + break; + case 'center': + [ $where, $parameters ] = $this->getWhereClauseForCenter($args['centers']); + break; + default: + throw new \UnexpectedValueException("context {$context} is not supported"); } - - - return [ + + return TimelineSingleQuery::fromArray([ 'id' => sprintf('%s.%s.%s', $metadata->getSchemaName(), $metadata->getTableName(), $metadata->getColumnName('id')), 'type' => self::TYPE, 'date' => $metadata->getColumnName('datetime'), - 'FROM' => sprintf('%s JOIN %s ON %s = %s', - sprintf('%s.%s', $metadata->getSchemaName(), $metadata->getTableName()), - sprintf('%s.%s', $singleTaskMetadata->getSchemaName(), $singleTaskMetadata->getTableName()), - $metadata->getAssociationMapping('task')['joinColumns'][0]['name'], - sprintf('%s.%s.%s', $singleTaskMetadata->getSchemaName(), $singleTaskMetadata->getTableName(), $singleTaskMetadata->getColumnName('id')) - ), - 'WHERE' => sprintf('%s.%s = %d and %s', - sprintf('%s.%s', $singleTaskMetadata->getSchemaName(), $singleTaskMetadata->getTableName()), - $singleTaskMetadata->getAssociationMapping('person')['joinColumns'][0]['name'], - $args['person']->getId(), - $circleRestriction - ) + 'FROM' => $this->getFromClause($context), + 'WHERE' => $where, + 'parameters' => $parameters + ]); + } + + private function getWhereClauseForCenter(array $centers): array + { + $taskEvent = $this->em->getClassMetadata(SingleTaskPlaceEvent::class); + $singleTask = $this->em->getClassMetadata(SingleTask::class); + $person = $this->em->getClassMetadata(Person::class); + $personFkCenter = $person->getAssociationMapping('center')['joinColumns'][0]['name']; + $taskFkCircle = $singleTask->getAssociationMapping('circle')['joinColumns'][0]['name']; + + // the parameters + $parameters = []; + + // the clause that we will repeat for each center, joined by 'OR' + $clause = "{person}.{center_id} = ? AND {task}.{circle} IN ({circle_ids})"; + + // array to gather clauses + $clauses = []; + + // loop over centers + foreach ($this->authorizationHelper->getReachableCenters( + $this->security->getUser(), new Role(ActivityVoter::SEE_DETAILS)) as $center) { + + if (FALSE === \in_array($center, $centers)) { + continue; + } + + // fill center parameter + $parameters[] = $center->getId(); + + // we loop over circles + $circles = $this->authorizationHelper->getReachableCircles( + $this->security->getUser(), new Role(ActivityVoter::SEE_DETAILS), $center); + $circleIds = []; + + foreach ($circles as $circle) { + $parameters[] = $circleIds[] = $circle->getId(); + } + + $clauses[] = \strtr( + $clause, + [ + '{person}' => $person->getTableName(), + '{center_id}' => $personFkCenter, + '{task}' => $singleTask->getSchemaName().".".$singleTask->getTableName(), + '{circle}' => $taskFkCircle, + '{circle_ids}' => \implode(', ', \array_fill(0, count($circleIds), '?')) + ] + ); + } + + if (0 === \count($clauses)) { + return [ 'FALSE = TRUE' , [] ]; + } + + return [ + \implode(' OR ', $clauses), + $parameters ]; + } + + private function getWhereClauseForPerson(Person $personArg): array + { + $taskEvent = $this->em->getClassMetadata(SingleTaskPlaceEvent::class); + $singleTask = $this->em->getClassMetadata(SingleTask::class); + $person = $this->em->getClassMetadata(Person::class); + $eventFkTask = $taskEvent->getAssociationMapping('task')['joinColumns'][0]['name']; + $taskFkPerson = $singleTask->getAssociationMapping('person')['joinColumns'][0]['name']; + $personPk = $singleTask->getAssociationMapping('person')['joinColumns'][0]['referencedColumnName']; + $taskFkCircle = $singleTask->getAssociationMapping('circle')['joinColumns'][0]['name']; + + + // the parameters + $parameters = []; + + // the clause that we will fill + $clause = "{person}.{person_id} = ? AND {task}.{circle} IN ({circle_ids})"; + + // person is the first parameter + $parameters[] = $personArg->getId(); + + // we loop over circles + $circles = $this->authorizationHelper->getReachableCircles( + $this->security->getUser(), new Role(ActivityVoter::SEE_DETAILS), $personArg->getCenter()); + + if (0 === count($circles)) { + // go fast to block access to every tasks + return [ "FALSE = TRUE", [] ]; + } + + foreach ($circles as $circle) { + $parameters[] = $circleIds[] = $circle->getId(); + } + + return [ + \strtr( + $clause, + [ + '{person}' => $person->getTableName(), + '{person_id}' => $person->getColumnName('id'), + '{task}' => $singleTask->getSchemaName().".".$singleTask->getTableName(), + '{circle}' => $taskFkCircle, + '{circle_ids}' => \implode(', ', \array_fill(0, count($circleIds), '?')) + ] + ), + $parameters + ]; + } + + private function getFromClause(string $context) + { + $taskEvent = $this->em->getClassMetadata(SingleTaskPlaceEvent::class); + $singleTask = $this->em->getClassMetadata(SingleTask::class); + $person = $this->em->getClassMetadata(Person::class); + $eventFkTask = $taskEvent->getAssociationMapping('task')['joinColumns'][0]['name']; + $taskFkPerson = $singleTask->getAssociationMapping('person')['joinColumns'][0]['name']; + $personPk = $singleTask->getAssociationMapping('person')['joinColumns'][0]['referencedColumnName']; + + $from = "{single_task_event} ". + "JOIN {single_task} ON {single_task}.{task_pk} = {single_task_event}.{event_fk_task} ". + "JOIN {person} ON {single_task}.{task_person_fk} = {person}.{person_pk}"; + + return \strtr( + $from, + [ + '{single_task}' => sprintf('%s.%s', $singleTask->getSchemaName(), $singleTask->getTableName()), + '{single_task_event}' => sprintf('%s.%s', $taskEvent->getSchemaName(), $taskEvent->getTableName()), + '{task_pk}' => $singleTask->getColumnName('id'), + '{event_fk_task}' => $eventFkTask, + '{person}' => $person->getTableName(), + '{task_person_fk}' => $taskFkPerson, + '{person_pk}' => $personPk + ] + ); } public function getEntities(array $ids) @@ -147,10 +246,11 @@ class TaskLifeCycleEventTimelineProvider implements TimelineProviderInterface $transition = $this->getTransitionByName($entity->getTransition(), $workflow); return [ - 'template' => 'ChillTaskBundle:Timeline:single_task_transition_person_context.html.twig', + 'template' => 'ChillTaskBundle:Timeline:single_task_transition.html.twig', 'template_data' => [ - 'person' => $args['person'], + 'context' => $context, 'event' => $entity, + 'task' => $entity->getTask(), 'transition' => $transition ] ]; diff --git a/src/Bundle/ChillTaskBundle/config/services/timeline.yaml b/src/Bundle/ChillTaskBundle/config/services/timeline.yaml index 0962866be..04ef218b5 100644 --- a/src/Bundle/ChillTaskBundle/config/services/timeline.yaml +++ b/src/Bundle/ChillTaskBundle/config/services/timeline.yaml @@ -4,14 +4,15 @@ services: $em: '@Doctrine\ORM\EntityManagerInterface' $registry: '@Symfony\Component\Workflow\Registry' $authorizationHelper: '@Chill\MainBundle\Security\Authorization\AuthorizationHelper' - $tokenStorage: '@Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface' + $security: '@Symfony\Component\Security\Core\Security' public: true - # tags: - # - { name: 'chill.timeline', context: 'person' } + tags: + - { name: 'chill.timeline', context: 'person' } + - { name: 'chill.timeline', context: 'center' } Chill\TaskBundle\Timeline\SingleTaskTaskLifeCycleEventTimelineProvider: arguments: $em: '@Doctrine\ORM\EntityManagerInterface' $registry: '@Symfony\Component\Workflow\Registry' - # tags: - #- { name: 'chill.timeline', context: 'task' } + tags: + - { name: 'chill.timeline', context: 'task' }