* * 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\ReportBundle\Timeline; use Chill\MainBundle\Timeline\TimelineProviderInterface; use Doctrine\ORM\EntityManager; use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Role\Role; use Doctrine\ORM\Mapping\ClassMetadata; use Chill\PersonBundle\Entity\Person; use Chill\MainBundle\Entity\Scope; use Chill\CustomFieldsBundle\Service\CustomFieldsHelper; use Chill\ReportBundle\Entity\Report; use Symfony\Component\Security\Core\Security; use Chill\MainBundle\Timeline\TimelineSingleQuery; /** * Provide report for inclusion in timeline */ class TimelineReportProvider implements TimelineProviderInterface { protected EntityManager $em; protected AuthorizationHelper $helper; protected CustomFieldsHelper $customFieldsHelper; protected $showEmptyValues; public function __construct( EntityManager $em, AuthorizationHelper $helper, Security $security, CustomFieldsHelper $customFieldsHelper, $showEmptyValues ) { $this->em = $em; $this->helper = $helper; $this->security = $security; $this->customFieldsHelper = $customFieldsHelper; $this->showEmptyValues = $showEmptyValues; } /** * * {@inheritDoc} */ public function fetchQuery($context, array $args) { $this->checkContext($context); $report = $this->em->getClassMetadata(Report::class); [$where, $parameters] = $this->getWhereClause($context, $args); return TimelineSingleQuery::fromArray([ 'id' => $report->getTableName() .'.'.$report->getColumnName('id'), 'type' => 'report', 'date' => $report->getTableName() .'.'.$report->getColumnName('date'), 'FROM' => $this->getFromClause($context), 'WHERE' => $where, 'parameters' => $parameters ]); } private function getWhereClause(string $context, array $args): array { switch ($context) { case 'person': return $this->getWhereClauseForPerson($context, $args); case 'center': return $this->getWhereClauseForCenter($context, $args); default: throw new \UnexpectedValueException("This context $context is not implemented"); } } private function getWhereClauseForCenter(string $context, array $args): array { $report = $this->em->getClassMetadata(Report::class); $person = $this->em->getClassMetadata(Person::class); $role = new Role('CHILL_REPORT_SEE'); $reachableCenters = $this->helper->getReachableCenters($this->security->getUser(), $role); $reportPersonId = $report->getAssociationMapping('person')['joinColumns'][0]['name']; $reportScopeId = $report->getAssociationMapping('scope')['joinColumns'][0]['name']; $personCenterId = $person->getAssociationMapping('center')['joinColumns'][0]['name']; // parameters for the query, will be filled later $parameters = []; // the clause, that will be joined with an "OR" $centerScopesClause = "({person}.{center_id} = ? ". "AND {report}.{scopes_id} IN ({scopes_ids}))"; // container for formatted clauses $formattedClauses = []; $askedCenters = $args['centers']; foreach ($reachableCenters as $center) { if (FALSE === \in_array($center, $askedCenters)) { continue; } // add the center id to the parameters $parameters[] = $center->getId(); // loop over scopes $scopeIds = []; foreach ($this->helper->getReachableScopes($this->security->getUser(), $role, $center) as $scope) { if (\in_array($scope->getId(), $scopeIds)) { continue; } $scopeIds[] = $scope->getId(); } $formattedClauses[] = \strtr($centerScopesClause, [ '{scopes_ids}' => \implode(', ', \array_fill(0, \count($scopeIds), '?')) ]); // append $scopeIds to parameters $parameters = \array_merge($parameters, $scopeIds); } if (0 === count($formattedClauses)) { return [ 'FALSE = TRUE', [] ]; } return [ \strtr( \implode(' OR ', $formattedClauses), [ '{person}' => $person->getTableName(), '{center_id}' => $personCenterId, '{report}' => $report->getTableName(), '{scopes_id}' => $reportScopeId, ] ), $parameters ]; } private function getWhereClauseForPerson(string $context, array $args): array { $report = $this->em->getClassMetadata(Report::class); $person = $this->em->getClassMetadata(Person::class); $role = new Role('CHILL_REPORT_SEE'); $reportPersonId = $report->getAssociationMapping('person')['joinColumns'][0]['name']; $reportScopeId = $report->getAssociationMapping('scope')['joinColumns'][0]['name']; $personCenterId = $person->getAssociationMapping('center')['joinColumns'][0]['name']; // parameters for the query, will be filled later $parameters = [ $args['person']->getId() ]; // this is the final clause that we are going to fill $clause = "{report}.{person_id} = ? AND {report}.{scopes_id} IN ({scopes_ids})"; // iterate over reachable scopes $scopes = $this->helper->getReachableScopes($this->security->getUser(), $role, $args['person']->getCenter()); foreach ($scopes as $scope) { if (\in_array($scope->getId(), $parameters)) { continue; } $parameters[] = $scope->getId(); } if (1 === count($parameters)) { // nothing change, we simplify the clause $clause = "{report}.{person_id} = ? AND FALSE = TRUE"; } return [ \strtr( $clause, [ '{report}' => $report->getTableName(), '{person_id}' => $reportPersonId, '{scopes_id}' => $reportScopeId, '{scopes_ids}' => \implode(', ', \array_fill(0, \count($parameters)-1, '?')) ] ), $parameters ]; } private function getFromClause(string $context): string { $report = $this->em->getClassMetadata(Report::class); $person = $this->em->getClassMetadata(Person::class); $reportPersonId = $report ->getAssociationMapping('person')['joinColumns'][0]['name'] ; $personId = $report ->getAssociationMapping('person')['joinColumns'][0]['referencedColumnName'] ; $clause = "{report} ". "JOIN {person} ON {report}.{person_id} = {person}.{id_person} "; return \strtr($clause, [ '{report}' => $report->getTableName(), '{person}' => $person->getTableName(), '{person_id}' => $reportPersonId, '{id_person}' => $personId ] ); } /** * * {@inheritDoc} */ public function getEntities(array $ids) { $reports = $this->em->getRepository('ChillReportBundle:Report') ->findBy(array('id' => $ids)); $result = array(); foreach($reports as $report) { $result[$report->getId()] = $report; } return $result; } /** * * {@inheritDoc} */ public function getEntityTemplate($entity, $context, array $args) { $this->checkContext($context); return array( 'template' => 'ChillReportBundle:Timeline:report.html.twig', 'template_data' => array( 'report' => $entity, 'context' => $context, 'custom_fields_in_summary' => $this->getFieldsToRender($entity, $context), ) ); } protected function getFieldsToRender(Report $entity, $context, array $args = array()) { //gather all custom fields which should appears in summary $gatheredFields = array(); if (array_key_exists('summary_fields', $entity->getCFGroup()->getOptions())) { // keep in memory title $title = null; $subtitle = null; foreach ($entity->getCFGroup()->getCustomFields() as $customField) { if (in_array($customField->getSlug(), $entity->getCFGroup()->getOptions()['summary_fields'])) { // if we do not want to show empty values if ($this->showEmptyValues === false) { if ($customField->getType() === 'title') { $options = $customField->getOptions(); switch($options['type']) { case 'title': $title = $customField; break; case 'subtitle': $subtitle = $customField; break; } } else { if ($this->customFieldsHelper->isEmptyValue($entity->getCFData(), $customField) === false) { if ($title !== NULL) { $gatheredFields[] = $title; $title = null; } if ($subtitle !== NULL) { $gatheredFields[] = $subtitle; $subtitle = null; } $gatheredFields[] = $customField; } } } else { $gatheredFields[] = $customField; } } } } return $gatheredFields; } /** * * {@inheritDoc} */ public function supportsType($type) { return $type === 'report'; } /** * check if the context is supported * * @param string $context * @throws \LogicException if the context is not supported */ private function checkContext($context) { if ($context !== 'person' && $context !== 'center') { throw new \LogicException("The context '$context' is not " . "supported. Currently only 'person' and 'center' is supported"); } } }