From d9251239f742f0cc7a5e2cdde9a4f2fcad5e7d3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 8 Apr 2025 10:38:07 +0200 Subject: [PATCH] Remove unused CSVFormatter and improve translations handling CSVFormatter was deprecated and is no longer in use, so it was removed. Additionally, translation handling was enhanced across multiple formatters, supporting `TranslatableInterface` to ensure better localization compatibility. --- .../Export/Formatter/CSVFormatter.php | 452 ------------------ .../Export/Formatter/CSVListFormatter.php | 12 +- .../Formatter/CSVPivotedListFormatter.php | 2 - .../Export/Formatter/SpreadSheetFormatter.php | 32 +- .../Export/SortExportElement.php | 14 +- .../Export/Export/ReportList.php | 3 + 6 files changed, 40 insertions(+), 475 deletions(-) delete mode 100644 src/Bundle/ChillMainBundle/Export/Formatter/CSVFormatter.php diff --git a/src/Bundle/ChillMainBundle/Export/Formatter/CSVFormatter.php b/src/Bundle/ChillMainBundle/Export/Formatter/CSVFormatter.php deleted file mode 100644 index 771bf058a..000000000 --- a/src/Bundle/ChillMainBundle/Export/Formatter/CSVFormatter.php +++ /dev/null @@ -1,452 +0,0 @@ -getExportManager()->getAggregators($aggregatorAliases); - $nb = \count($aggregatorAliases); - - foreach ($aggregators as $alias => $aggregator) { - $builderAggregator = $builder->create($alias, FormType::class, [ - 'label' => $aggregator->getTitle(), - 'block_name' => '_aggregator_placement_csv_formatter', - ]); - $this->appendAggregatorForm($builderAggregator, $nb); - $builder->add($builderAggregator); - } - } - - public function getNormalizationVersion(): int - { - return 1; - } - - public function normalizeFormData(array $formData): array - { - return []; - } - - public function denormalizeFormData(array $formData, int $fromVersion): array - { - return []; - } - - public function getFormDefaultData(array $aggregatorAliases): array - { - return []; - } - - public function gatherFiltersDescriptions() - { - $descriptions = []; - - foreach ($this->filtersData as $key => $filterData) { - $statement = $this->getExportManager() - ->getFilter($key) - ->describeAction($filterData); - - if (null === $statement) { - continue; - } - - if (\is_array($statement)) { - $descriptions[] = $this->translator->trans( - $statement[0], - $statement[1], - $statement[2] ?? null, - $statement[3] ?? null - ); - } else { - $descriptions[] = $statement; - } - } - - return $descriptions; - } - - public function getName(): string|\Symfony\Contracts\Translation\TranslatableInterface - { - return 'Comma separated values (CSV)'; - } - - public function getResponse( - $result, - $formatterData, - $exportAlias, - array $exportData, - array $filtersData, - array $aggregatorsData, - ) { - $this->result = $result; - $this->orderingHeaders($formatterData); - $this->export = $this->getExportManager()->getExport($exportAlias); - $this->aggregators = iterator_to_array($this->getExportManager() - ->getAggregators(array_keys($aggregatorsData))); - $this->exportData = $exportData; - $this->aggregatorsData = $aggregatorsData; - $this->labels = $this->gatherLabels(); - $this->filtersData = $filtersData; - - $response = new Response(); - $response->setStatusCode(200); - $response->headers->set('Content-Type', 'text/csv; charset=utf-8'); - // $response->headers->set('Content-Disposition','attachment; filename="export.csv"'); - - $response->setContent($this->generateContent()); - - return $response; - } - - public function getType(): string - { - return 'tabular'; - } - - protected function gatherLabels() - { - return array_merge( - $this->gatherLabelsFromAggregators(), - $this->gatherLabelsFromExport() - ); - } - - protected function gatherLabelsFromAggregators() - { - $labels = []; - /* @var $aggretator \Chill\MainBundle\Export\AggregatorInterface */ - foreach ($this->aggregators as $alias => $aggregator) { - $keys = $aggregator->getQueryKeys($this->aggregatorsData[$alias]); - - // gather data in an array - foreach ($keys as $key) { - $values = array_map(static function ($row) use ($key, $alias) { - if (!\array_key_exists($key, $row)) { - throw new \LogicException("the key '".$key."' is declared by the aggregator with alias '".$alias."' but is not ".'present in results'); - } - - return $row[$key]; - }, $this->result); - $labels[$key] = $aggregator->getLabels( - $key, - array_unique($values), - $this->aggregatorsData[$alias] - ); - } - } - - return $labels; - } - - protected function gatherLabelsFromExport() - { - $labels = []; - $export = $this->export; - $keys = $this->export->getQueryKeys($this->exportData); - - foreach ($keys as $key) { - $values = array_map(static function ($row) use ($key, $export) { - if (!\array_key_exists($key, $row)) { - throw new \LogicException("the key '".$key."' is declared by the export with title '".$export->getTitle()."' but is not ".'present in results'); - } - - return $row[$key]; - }, $this->result); - $labels[$key] = $this->export->getLabels( - $key, - array_unique($values), - $this->exportData - ); - } - - return $labels; - } - - protected function generateContent() - { - $line = null; - $rowKeysNb = \count($this->getRowHeaders()); - $columnKeysNb = \count($this->getColumnHeaders()); - $resultsKeysNb = \count($this->export->getQueryKeys($this->exportData)); - $results = $this->getOrderedResults(); - /** @var string[] $columnHeaders the column headers associations */ - $columnHeaders = []; - /** @var string[] $data the data of the csv file */ - $contentData = []; - $content = []; - - // create a file pointer connected to the output stream - $output = fopen('php://output', 'wb'); - - // title - fputcsv($output, [$this->translator->trans($this->export->getTitle())]); - // blank line - fputcsv($output, ['']); - - // add filtering description - foreach ($this->gatherFiltersDescriptions() as $desc) { - fputcsv($output, [$desc]); - } - // blank line - fputcsv($output, ['']); - - // iterate on result to : 1. populate row headers, 2. populate column headers, 3. add result - foreach ($results as $row) { - $rowHeaders = \array_slice($row, 0, $rowKeysNb); - - // first line : we create line and adding them row headers - if (!isset($line)) { - $line = \array_slice($row, 0, $rowKeysNb); - } - - // do we have to create a new line ? if the rows are equals, continue on the line, else create a next line - if (\array_slice($line, 0, $rowKeysNb) !== $rowHeaders) { - $contentData[] = $line; - $line = \array_slice($row, 0, $rowKeysNb); - } - - // add the column headers - /** @var string[] $columns the column for this row */ - $columns = \array_slice($row, $rowKeysNb, $columnKeysNb); - $columnPosition = $this->findColumnPosition($columnHeaders, $columns); - - // fill with blank at the position given by the columnPosition + nbRowHeaders - for ($i = 0; $i < $columnPosition; ++$i) { - if (!isset($line[$rowKeysNb + $i])) { - $line[$rowKeysNb + $i] = ''; - } - } - - $resultData = \array_slice($row, $resultsKeysNb * -1); - - foreach ($resultData as $data) { - $line[] = $data; - } - } - - // we add the last line - $contentData[] = $line; - - // column title headers - for ($i = 0; $i < $columnKeysNb; ++$i) { - $line = array_fill(0, $rowKeysNb, ''); - - foreach ($columnHeaders as $set) { - $line[] = $set[$i]; - } - - $content[] = $line; - } - - // row title headers - $headerLine = []; - - foreach ($this->getRowHeaders() as $headerKey) { - $headerLine[] = \array_key_exists('_header', $this->labels[$headerKey]) ? - $this->labels[$headerKey]['_header'] : ''; - } - - foreach ($this->export->getQueryKeys($this->exportData) as $key) { - $headerLine[] = \array_key_exists('_header', $this->labels[$key]) ? - $this->labels[$key]['_header'] : ''; - } - fputcsv($output, $headerLine); - unset($headerLine); // free memory - - // generate CSV - foreach ($content as $line) { - fputcsv($output, $line); - } - - foreach ($contentData as $line) { - fputcsv($output, $line); - } - - $text = stream_get_contents($output); - fclose($output); - - return $text; - } - - protected function getColumnHeaders() - { - return $this->getPositionnalHeaders('c'); - } - - protected function getRowHeaders() - { - return $this->getPositionnalHeaders('r'); - } - - /** - * ordering aggregators, preserving key association. - * - * This function do not mind about position. - * - * If two aggregators have the same order, the second given will be placed - * after. This is not significant for the first ordering. - */ - protected function orderingHeaders(array $formatterData) - { - $this->formatterData = $formatterData; - uasort( - $this->formatterData, - static fn (array $a, array $b): int => ($a['order'] <= $b['order'] ? -1 : 1) - ); - } - - /** - * append a form line by aggregator on the formatter form. - * - * This form allow to choose the aggregator position (row or column) and - * the ordering - * - * @param string $nbAggregators - */ - private function appendAggregatorForm(FormBuilderInterface $builder, $nbAggregators) - { - $builder->add('order', ChoiceType::class, [ - 'choices' => array_combine( - range(1, $nbAggregators), - range(1, $nbAggregators) - ), - 'multiple' => false, - 'expanded' => false, - ]); - - $builder->add('position', ChoiceType::class, [ - 'choices' => [ - 'row' => 'r', - 'column' => 'c', - ], - 'multiple' => false, - 'expanded' => false, - ]); - } - - private function findColumnPosition(&$columnHeaders, $columnToFind): int - { - $i = 0; - - foreach ($columnHeaders as $set) { - if ($set === $columnToFind) { - return $i; - } - ++$i; - } - - // we didn't find it, adding the column - $columnHeaders[] = $columnToFind; - - return $i++; - } - - private function getOrderedResults() - { - $r = []; - $results = $this->result; - $labels = $this->labels; - $rowKeys = $this->getRowHeaders(); - $columnKeys = $this->getColumnHeaders(); - $resultsKeys = $this->export->getQueryKeys($this->exportData); - $headers = array_merge($rowKeys, $columnKeys); - - foreach ($results as $row) { - $line = []; - - foreach ($headers as $key) { - $line[] = \call_user_func($labels[$key], $row[$key]); - } - - // append result - foreach ($resultsKeys as $key) { - $line[] = \call_user_func($labels[$key], $row[$key]); - } - - $r[] = $line; - } - - array_multisort($r); - - return $r; - } - - /** - * @param string $position may be 'c' (column) or 'r' (row) - * - * @return string[] - * - * @throws \RuntimeException - */ - private function getPositionnalHeaders($position) - { - $headers = []; - - foreach ($this->formatterData as $alias => $data) { - if (!\array_key_exists($alias, $this->aggregatorsData)) { - throw new \RuntimeException('the formatter wants to use the '."aggregator with alias {$alias}, but the export do not ".'contains data about it'); - } - - $aggregator = $this->aggregators[$alias]; - - if ($data['position'] === $position) { - $headers = array_merge($headers, $aggregator->getQueryKeys($this->aggregatorsData[$alias])); - } - } - - return $headers; - } -} diff --git a/src/Bundle/ChillMainBundle/Export/Formatter/CSVListFormatter.php b/src/Bundle/ChillMainBundle/Export/Formatter/CSVListFormatter.php index fea419755..4d07abafb 100644 --- a/src/Bundle/ChillMainBundle/Export/Formatter/CSVListFormatter.php +++ b/src/Bundle/ChillMainBundle/Export/Formatter/CSVListFormatter.php @@ -17,6 +17,7 @@ use Chill\MainBundle\Export\Helper\ExportManagerAwareTrait; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\HttpFoundation\Response; +use Symfony\Contracts\Translation\TranslatableInterface; use Symfony\Contracts\Translation\TranslatorInterface; use function count; @@ -174,8 +175,6 @@ class CSVListFormatter implements FormatterInterface, ExportManagerAwareInterfac * @param string $key * @param string $value * - * @return string - * * @throws \LogicException if the label is not found */ protected function getLabel($key, $value) @@ -225,9 +224,12 @@ class CSVListFormatter implements FormatterInterface, ExportManagerAwareInterfac } foreach ($first_row as $key => $value) { - $header_line[] = $this->translator->trans( - $this->getLabel($key, '_header') - ); + $content = $this->getLabel($key, '_header'); + if ($content instanceof TranslatableInterface) { + $header_line[] = $content->trans($this->translator, $this->translator->getLocale()); + } else { + $header_line[] = $this->translator->trans($this->getLabel($key, '_header')); + } } if (\count($header_line) > 0) { diff --git a/src/Bundle/ChillMainBundle/Export/Formatter/CSVPivotedListFormatter.php b/src/Bundle/ChillMainBundle/Export/Formatter/CSVPivotedListFormatter.php index 6d0a9d180..36937eee2 100644 --- a/src/Bundle/ChillMainBundle/Export/Formatter/CSVPivotedListFormatter.php +++ b/src/Bundle/ChillMainBundle/Export/Formatter/CSVPivotedListFormatter.php @@ -176,8 +176,6 @@ class CSVPivotedListFormatter implements FormatterInterface, ExportManagerAwareI * @param string $key * @param string $value * - * @return string - * * @throws \LogicException if the label is not found */ protected function getLabel($key, $value) diff --git a/src/Bundle/ChillMainBundle/Export/Formatter/SpreadSheetFormatter.php b/src/Bundle/ChillMainBundle/Export/Formatter/SpreadSheetFormatter.php index b2f1176f1..e5bfab68f 100644 --- a/src/Bundle/ChillMainBundle/Export/Formatter/SpreadSheetFormatter.php +++ b/src/Bundle/ChillMainBundle/Export/Formatter/SpreadSheetFormatter.php @@ -19,6 +19,7 @@ use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\HttpFoundation\Response; +use Symfony\Contracts\Translation\TranslatableInterface; use Symfony\Contracts\Translation\TranslatorInterface; class SpreadSheetFormatter implements FormatterInterface, ExportManagerAwareInterface @@ -71,7 +72,7 @@ class SpreadSheetFormatter implements FormatterInterface, ExportManagerAwareInte * * replaced when `getResponse` is called. * - * @var type + * @var array */ protected $result; @@ -248,14 +249,15 @@ class SpreadSheetFormatter implements FormatterInterface, ExportManagerAwareInte foreach ($this->filtersData as $alias => $data) { $filter = $this->getExportManager()->getFilter($alias); - $description = $filter->describeAction($data, 'string'); - + $description = $filter->describeAction($data); if (\is_array($description)) { $description = $this->translator ->trans( $description[0], - $description[1] ?? [] + $description[1] ?? [], ); + } elseif ($description instanceof TranslatableInterface) { + $description = $description->trans($this->translator, $this->translator->getLocale()); } $worksheet->setCellValue('A'.$line, $description); @@ -284,9 +286,13 @@ class SpreadSheetFormatter implements FormatterInterface, ExportManagerAwareInte $displayables = []; foreach ($globalKeys as $key) { - $displayables[] = $this->translator->trans( - $this->getDisplayableResult($key, '_header') - ); + $displayable = $this->getDisplayableResult($key, '_header'); + + if ($displayable instanceof TranslatableInterface) { + $displayables[] = $displayable->trans($this->translator, $this->translator->getLocale()); + } else { + $displayables[] = $this->translator->trans($this->getDisplayableResult($key, '_header')); + } } // add headers on worksheet @@ -418,8 +424,6 @@ class SpreadSheetFormatter implements FormatterInterface, ExportManagerAwareInte * Get the displayable result. * * @param string $key - * - * @return string */ protected function getDisplayableResult($key, mixed $value) { @@ -432,15 +436,15 @@ class SpreadSheetFormatter implements FormatterInterface, ExportManagerAwareInte return \call_user_func($this->cacheDisplayableResult[$key], $value); } - protected function getTitle() + protected function getTitle(): string { - $title = $this->translator->trans($this->export->getTitle()); + $original = $this->export->getTitle(); - if (30 < strlen($title)) { - return substr($title, 0, 30).'…'; + if ($original instanceof TranslatableInterface) { + return $original->trans($this->translator, $this->translator->getLocale()); } - return $title; + return $this->translator->trans($original); } protected function initializeCache($key) diff --git a/src/Bundle/ChillMainBundle/Export/SortExportElement.php b/src/Bundle/ChillMainBundle/Export/SortExportElement.php index 6228109ed..7fdac9be1 100644 --- a/src/Bundle/ChillMainBundle/Export/SortExportElement.php +++ b/src/Bundle/ChillMainBundle/Export/SortExportElement.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\MainBundle\Export; +use Symfony\Contracts\Translation\TranslatableInterface; use Symfony\Contracts\Translation\TranslatorInterface; final readonly class SortExportElement @@ -19,12 +20,21 @@ final readonly class SortExportElement private TranslatorInterface $translator, ) {} + private function trans(string|TranslatableInterface $message): string + { + if ($message instanceof TranslatableInterface) { + return $message->trans($this->translator, $this->translator->getLocale()); + } + + return $this->translator->trans($message); + } + /** * @param array $elements */ public function sortFilters(array &$elements): void { - uasort($elements, fn (FilterInterface $a, FilterInterface $b) => $this->translator->trans($a->getTitle()) <=> $this->translator->trans($b->getTitle())); + uasort($elements, fn (FilterInterface $a, FilterInterface $b) => $this->trans($a->getTitle()) <=> $this->trans($b->getTitle())); } /** @@ -32,6 +42,6 @@ final readonly class SortExportElement */ public function sortAggregators(array &$elements): void { - uasort($elements, fn (AggregatorInterface $a, AggregatorInterface $b) => $this->translator->trans($a->getTitle()) <=> $this->translator->trans($b->getTitle())); + uasort($elements, fn (AggregatorInterface $a, AggregatorInterface $b) => $this->trans($a->getTitle()) <=> $this->trans($b->getTitle())); } } diff --git a/src/Bundle/ChillReportBundle/Export/Export/ReportList.php b/src/Bundle/ChillReportBundle/Export/Export/ReportList.php index ab2a45bc9..75f1d6e8a 100644 --- a/src/Bundle/ChillReportBundle/Export/Export/ReportList.php +++ b/src/Bundle/ChillReportBundle/Export/Export/ReportList.php @@ -33,6 +33,9 @@ use Symfony\Component\Validator\Constraints\Callback; use Symfony\Component\Validator\Context\ExecutionContextInterface; use Symfony\Contracts\Translation\TranslatorInterface; +/** + * @template-implements ListInterface + */ class ReportList implements ExportElementValidatedInterface, ListInterface { use \Chill\MainBundle\Export\ExportDataNormalizerTrait;