logger = $logger; $this->em = $em; $this->authorizationChecker = $authorizationChecker; $this->authorizationHelper = $authorizationHelper; $this->user = $tokenStorage->getToken()->getUser(); } /** * Return a \Generator containing filter which support type. If `$centers` is * not null, restrict the given filters to the center the user have access to. * * if $centers is null, the function will returns all filters where the user * has access in every centers he can reach (if the user can use the filter F in * center A, but not in center B, the filter F will not be returned) * * @param \Chill\MainBundle\Entity\Center[] $centers the centers where the user have access to * * @return FilterInterface[] a \Generator that contains filters. The key is the filter's alias */ public function &getFiltersApplyingOn(ExportInterface $export, ?array $centers = null) { foreach ($this->filters as $alias => $filter) { if ( in_array($filter->applyOn(), $export->supportsModifiers(), true) && $this->isGrantedForElement($filter, $export, $centers) ) { yield $alias => $filter; } } } /** * Return a \Generator containing aggregators supported by the given export. * * @internal This class check the interface implemented by export, and, if ´ListInterface´ is used, return an empty array * * @return AggregatorInterface[] a \Generator that contains aggretagors. The key is the filter's alias */ public function &getAggregatorsApplyingOn(ExportInterface $export, ?array $centers = null) { if ($export instanceof ListInterface) { return; } foreach ($this->aggregators as $alias => $aggregator) { if ( in_array($aggregator->applyOn(), $export->supportsModifiers(), true) && $this->isGrantedForElement($aggregator, $export, $centers) ) { yield $alias => $aggregator; } } } /** * add an aggregator. * * @internal used by DI * * @param string $alias */ public function addAggregator(AggregatorInterface $aggregator, $alias) { $this->aggregators[$alias] = $aggregator; } /** * add an export. * * @internal used by DI * * @param DirectExportInterface|ExportInterface $export * @param type $alias */ public function addExport($export, $alias) { if ($export instanceof ExportInterface || $export instanceof DirectExportInterface) { $this->exports[$alias] = $export; } else { throw new InvalidArgumentException(sprintf( 'The export with alias %s ' . 'does not implements %s or %s.', $alias, ExportInterface::class, DirectExportInterface::class )); } } public function addExportElementsProvider(ExportElementsProviderInterface $provider, $prefix) { foreach ($provider->getExportElements() as $suffix => $element) { $alias = $prefix . '_' . $suffix; if ($element instanceof ExportInterface) { $this->addExport($element, $alias); } elseif ($element instanceof FilterInterface) { $this->addFilter($element, $alias); } elseif ($element instanceof AggregatorInterface) { $this->addAggregator($element, $alias); } elseif ($element instanceof FormatterInterface) { $this->addFormatter($element, $alias); } else { throw new LogicException('This element ' . get_class($element) . ' ' . 'is not an instance of export element'); } } } /** * add a Filter. * * @internal Normally used by the dependency injection * * @param string $alias */ public function addFilter(FilterInterface $filter, $alias) { $this->filters[$alias] = $filter; } /** * add a formatter. * * @internal used by DI * * @param type $alias */ public function addFormatter(FormatterInterface $formatter, $alias) { $this->formatters[$alias] = $formatter; } /** * Generate a response which contains the requested data. * * @param string $exportAlias * @param mixed[] $data * * @return Response */ public function generate($exportAlias, array $pickedCentersData, array $data, array $formatterData) { $export = $this->getExport($exportAlias); //$qb = $this->em->createQueryBuilder(); $centers = $this->getPickedCenters($pickedCentersData); if ($export instanceof DirectExportInterface) { return $export->generate( $this->buildCenterReachableScopes($centers, $export), $data[ExportType::EXPORT_KEY] ); } $query = $export->initiateQuery( $this->retrieveUsedModifiers($data), $this->buildCenterReachableScopes($centers, $export), $data[ExportType::EXPORT_KEY] ); if ($query instanceof \Doctrine\ORM\NativeQuery) { // throw an error if the export require other modifier, which is // not allowed when the export return a `NativeQuery` if (count($export->supportsModifiers()) > 0) { throw new LogicException("The export with alias `{$exportAlias}` return " . 'a `\\Doctrine\\ORM\\NativeQuery` and supports modifiers, which is not ' . 'allowed. Either the method `supportsModifiers` should return an empty ' . 'array, or return a `Doctrine\\ORM\\QueryBuilder`'); } } elseif ($query instanceof QueryBuilder) { //handle filters $this->handleFilters($export, $query, $data[ExportType::FILTER_KEY], $centers); //handle aggregators $this->handleAggregators($export, $query, $data[ExportType::AGGREGATOR_KEY], $centers); $this->logger->debug('current query is ' . $query->getDQL(), [ 'class' => self::class, 'function' => __FUNCTION__, ]); } else { throw new UnexpectedValueException('The method `intiateQuery` should return ' . 'a `\\Doctrine\\ORM\\NativeQuery` or a `Doctrine\\ORM\\QueryBuilder` ' . 'object.'); } $result = $export->getResult($query, $data[ExportType::EXPORT_KEY]); if (!is_iterable($result)) { throw new UnexpectedValueException( sprintf( 'The result of the export should be an iterable, %s given', gettype($result) ) ); } /** @var FormatterInterface $formatter */ $formatter = $this->getFormatter($this->getFormatterAlias($data)); $filtersData = []; $aggregatorsData = []; if ($query instanceof QueryBuilder) { $aggregators = $this->retrieveUsedAggregators($data[ExportType::AGGREGATOR_KEY]); foreach ($aggregators as $alias => $aggregator) { $aggregatorsData[$alias] = $data[ExportType::AGGREGATOR_KEY][$alias]['form']; } } $filters = $this->retrieveUsedFilters($data[ExportType::FILTER_KEY]); foreach ($filters as $alias => $filter) { $filtersData[$alias] = $data[ExportType::FILTER_KEY][$alias]['form']; } return $formatter->getResponse( $result, $formatterData, $exportAlias, $data[ExportType::EXPORT_KEY], $filtersData, $aggregatorsData ); } /** * @param string $alias * * @throws RuntimeException if the aggregator is not known * * @return AggregatorInterface */ public function getAggregator($alias) { if (!array_key_exists($alias, $this->aggregators)) { throw new RuntimeException("The aggregator with alias {$alias} is not known."); } return $this->aggregators[$alias]; } public function getAggregators(array $aliases) { foreach ($aliases as $alias) { yield $alias => $this->getAggregator($alias); } } /** * @return string[] the existing type for known exports */ public function getExistingExportsTypes() { $existingTypes = []; foreach ($this->exports as $export) { if (!in_array($export->getType(), $existingTypes, true)) { $existingTypes[] = $export->getType(); } } return $existingTypes; } /** * Return an export by his alias. * * @param string $alias * * @throws RuntimeException * * @return ExportInterface */ public function getExport($alias) { if (!array_key_exists($alias, $this->exports)) { throw new RuntimeException("The export with alias {$alias} is not known."); } return $this->exports[$alias]; } /** * Return all exports. The exports's alias are the array's keys. * * @param bool $whereUserIsGranted if true (default), restrict to user which are granted the right to execute the export * * @return ExportInterface[] an array where export's alias are keys */ public function getExports($whereUserIsGranted = true) { foreach ($this->exports as $alias => $export) { if ($whereUserIsGranted) { if ($this->isGrantedForElement($export, null, null)) { yield $alias => $export; } } else { yield $alias => $export; } } } /** * Get all exports grouped in an array. * * @param bool $whereUserIsGranted * * @return array where keys are the groups's name and value is an array of exports */ public function getExportsGrouped($whereUserIsGranted = true): array { $groups = ['_' => []]; foreach ($this->getExports($whereUserIsGranted) as $alias => $export) { if ($export instanceof GroupedExportInterface) { $groups[$export->getGroup()][$alias] = $export; } else { $groups['_'][$alias] = $export; } } return $groups; } /** * @param string $alias * * @throws RuntimeException if the filter is not known * * @return FilterInterface */ public function getFilter($alias) { if (!array_key_exists($alias, $this->filters)) { throw new RuntimeException("The filter with alias {$alias} is not known."); } return $this->filters[$alias]; } /** * get all filters. * * @param Generator $aliases */ public function getFilters(array $aliases) { foreach ($aliases as $alias) { yield $alias => $this->getFilter($alias); } } public function getFormatter($alias) { if (!array_key_exists($alias, $this->formatters)) { throw new RuntimeException("The formatter with alias {$alias} is not known."); } return $this->formatters[$alias]; } /** * get the formatter alias from the form export data. * * @param array $data the data from the export form * @string the formatter alias|null */ public function getFormatterAlias(array $data) { if (array_key_exists(ExportType::PICK_FORMATTER_KEY, $data)) { return $data[ExportType::PICK_FORMATTER_KEY]['alias']; } return null; } /** * Get all formatters which supports one of the given types. * * @return Generator */ public function getFormattersByTypes(array $types) { foreach ($this->formatters as $alias => $formatter) { if (in_array($formatter->getType(), $types, true)) { yield $alias => $formatter; } } } /** * Get the Center picked by the user for this export. The data are * extracted from the PickCenterType data. * * @param array $data the data from a PickCenterType * * @return \Chill\MainBundle\Entity\Center[] the picked center */ public function getPickedCenters(array $data) { return $data; } /** * get the aggregators typse used in the form export data. * * @param array $data the data from the export form * * @return string[] */ public function getUsedAggregatorsAliases(array $data) { $aggregators = $this->retrieveUsedAggregators($data[ExportType::AGGREGATOR_KEY]); return array_keys(iterator_to_array($aggregators)); } /** * Return true if the current user has access to the ExportElement for every * center, false if the user hasn't access to element for at least one center. * * @param \Chill\MainBundle\Export\ExportElementInterface $element * @param DirectExportInterface|ExportInterface $export * * @return bool */ public function isGrantedForElement(ExportElementInterface $element, ?ExportElementInterface $export = null, ?array $centers = null) { if ($element instanceof ExportInterface || $element instanceof DirectExportInterface) { $role = $element->requiredRole(); } elseif ($element instanceof ModifierInterface) { if (null === $element->addRole()) { if (null === $export) { throw new LogicException('The export should not be null: as the ' . 'ModifierInstance element is not an export, we should ' . 'be aware of the export to determine which role is required'); } $role = $export->requiredRole(); } else { $role = $element->addRole(); } } else { throw new LogicException('The element is not an ModifiersInterface or ' . 'an ExportInterface.'); } if (null === $centers) { $centers = $this->authorizationHelper->getReachableCenters( $this->user, $role ); } if (count($centers) === 0) { return false; } foreach ($centers as $center) { if ($this->authorizationChecker->isGranted($role->getRole(), $center) === false) { //debugging $this->logger->debug('user has no access to element', [ 'method' => __METHOD__, 'type' => get_class($element), 'center' => $center->getName(), 'role' => $role->getRole(), ]); ///// Bypasse les autorisations qui empêche d'afficher les nouveaux exports return true; ///// TODO supprimer le return true return false; } } return true; } /** * build the array required for defining centers and circles in the initiate * queries of ExportElementsInterfaces. * * @param \Chill\MainBundle\Entity\Center[] $centers */ private function buildCenterReachableScopes(array $centers, ExportElementInterface $element) { $r = []; foreach ($centers as $center) { $r[] = [ 'center' => $center, 'circles' => $this->authorizationHelper->getReachableScopes( $this->user, $element->requiredRole()->getRole(), $center ), ]; } return $r; } /** * Alter the query with selected aggregators. * * Check for acl. If an user is not authorized to see an aggregator, throw an * UnauthorizedException. * * @param type $data * @throw UnauthorizedHttpException if the user is not authorized */ private function handleAggregators( ExportInterface $export, QueryBuilder $qb, $data, array $center ) { $aggregators = $this->retrieveUsedAggregators($data); foreach ($aggregators as $alias => $aggregator) { $formData = $data[$alias]; $aggregator->alterQuery($qb, $formData['form']); } } /** * alter the query with selected filters. * * This function check the acl. * * @param mixed $data the data under the initial 'filters' data * @param \Chill\MainBundle\Entity\Center[] $centers the picked centers * @throw UnauthorizedHttpException if the user is not authorized */ private function handleFilters( ExportInterface $export, QueryBuilder $qb, $data, array $centers ) { $filters = $this->retrieveUsedFilters($data); foreach ($filters as $alias => $filter) { if ($this->isGrantedForElement($filter, $export, $centers) === false) { throw new UnauthorizedHttpException('You are not authorized to ' . 'use the filter ' . $filter->getTitle()); } $formData = $data[$alias]; $this->logger->debug('alter query by filter ' . $alias, [ 'class' => self::class, 'function' => __FUNCTION__, ]); $filter->alterQuery($qb, $formData['form']); } } /** * @param mixed $data * * @return AggregatorInterface[] */ private function retrieveUsedAggregators($data) { if (null === $data) { return []; } foreach ($data as $alias => $aggregatorData) { if (true === $aggregatorData['enabled']) { yield $alias => $this->getAggregator($alias); } } } /** * @param mixed $data * * @return string[] */ private function retrieveUsedAggregatorsType($data) { if (null === $data) { return []; } $usedTypes = []; foreach ($this->retrieveUsedAggregators($data) as $alias => $aggregator) { if (!in_array($aggregator->applyOn(), $usedTypes, true)) { $usedTypes[] = $aggregator->applyOn(); } } return $usedTypes; } /** * @param type $data the data from the filter key of the ExportType */ private function retrieveUsedFilters($data) { if (null === $data) { return []; } foreach ($data as $alias => $filterData) { if (true === $filterData['enabled']) { yield $alias => $this->getFilter($alias); } } } /** * Retrieve the filter used in this export. * * @param mixed $data the data from the `filters` key of the ExportType * * @return array an array with types */ private function retrieveUsedFiltersType($data) { if (null === $data) { return []; } $usedTypes = []; foreach ($data as $alias => $filterData) { if (true === $filterData['enabled']) { $filter = $this->getFilter($alias); if (!in_array($filter->applyOn(), $usedTypes, true)) { $usedTypes[] = $filter->applyOn(); } } } return $usedTypes; } /** * parse the data to retrieve the used filters and aggregators. * * @param mixed $data * * @return string[] */ private function retrieveUsedModifiers($data) { if (null === $data) { return []; } $usedTypes = array_merge( $this->retrieveUsedFiltersType($data[ExportType::FILTER_KEY]), $this->retrieveUsedAggregatorsType($data[ExportType::AGGREGATOR_KEY]) ); $this->logger->debug( 'Required types are ' . implode(', ', $usedTypes), ['class' => self::class, 'function' => __FUNCTION__] ); return array_unique($usedTypes); } }