translator = $translator; $this->exportManager = $manager; } /** * @uses appendAggregatorForm * * @param mixed $exportAlias */ public function buildForm(FormBuilderInterface $builder, $exportAlias, array $aggregatorAliases) { $aggregators = $this->exportManager->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 getFormDefaultData(array $aggregatorAliases): array { return []; } public function gatherFiltersDescriptions() { $descriptions = []; foreach ($this->filtersData as $key => $filterData) { $statement = $this->exportManager ->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() { 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->exportManager->getExport($exportAlias); $this->aggregators = iterator_to_array($this->exportManager ->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() { 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) * * @throws RuntimeException * * @return string[] */ 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; } }