* * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ namespace Chill\TaskBundle\Timeline; 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 * */ class TaskLifeCycleEventTimelineProvider implements TimelineProviderInterface { protected EntityManagerInterface $em; protected Registry $registry; protected AuthorizationHelper $authorizationHelper; protected Security $security; const TYPE = 'chill_task.transition'; public function __construct( EntityManagerInterface $em, Registry $registry, AuthorizationHelper $authorizationHelper, Security $security ) { $this->em = $em; $this->registry = $registry; $this->authorizationHelper = $authorizationHelper; $this->security = $security; } public function fetchQuery($context, $args) { $metadata = $this->em ->getClassMetadata(SingleTaskPlaceEvent::class); 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 TimelineSingleQuery::fromArray([ 'id' => sprintf('%s.%s.%s', $metadata->getSchemaName(), $metadata->getTableName(), $metadata->getColumnName('id')), 'type' => self::TYPE, 'date' => $metadata->getColumnName('datetime'), '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 = $circleIds = []; // 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) { $events = $this->em ->getRepository(SingleTaskPlaceEvent::class) ->findBy([ 'id' => $ids ]) ; return \array_combine( \array_map(function($e) { return $e->getId(); }, $events ), $events ); } public function getEntityTemplate($entity, $context, array $args) { $workflow = $this->registry->get($entity->getTask(), (isset($entity->getData()['workflow'])) ? $entity->getData()['workflow'] : null ); // sf4 check: prevent error message: // `Notice: Undefined property: Chill\TaskBundle\Entity\Task\SingleTaskPlaceEvent::$getData` // * fix syntax error on $entity->getData['workflow'] // * return null if not set $transition = $this->getTransitionByName($entity->getTransition(), $workflow); return [ 'template' => 'ChillTaskBundle:Timeline:single_task_transition.html.twig', 'template_data' => [ 'context' => $context, 'event' => $entity, 'task' => $entity->getTask(), 'transition' => $transition ] ]; } /** * * @param string $name * @param Workflow $workflow * @return \Symfony\Component\Workflow\Transition */ protected function getTransitionByName($name, Workflow $workflow) { foreach ($workflow->getDefinition()->getTransitions() as $transition) { if ($transition->getName() === $name) { return $transition; } } } public function supportsType($type): bool { return $type === self::TYPE; } }