From f937e9d12c892cc7bfe5a484a800f043b1ddf558 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 7 Nov 2018 13:23:11 +0100 Subject: [PATCH] adding formatter for list in spreadsheet (ods, xlsx) --- CHANGELOG.md | 2 +- Export/ExportManager.php | 4 +- Export/Formatter/CSVListFormatter.php | 8 + Export/Formatter/SpreadsheetListFormatter.php | 287 ++++++++++++++++++ Form/Type/Export/PickFormatterType.php | 3 +- Resources/config/services/export.yml | 8 + Resources/translations/messages.fr.yml | 2 + 7 files changed, 311 insertions(+), 3 deletions(-) create mode 100644 Export/Formatter/SpreadsheetListFormatter.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ee49ed6e..d3211bcca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,4 +30,4 @@ Master branch ============= - add margin of 0.5rem beyond buttons ; - +- add a spreadsheet formatter (format xlsx, ods, csv) for lists diff --git a/Export/ExportManager.php b/Export/ExportManager.php index a46054352..55fca29ec 100644 --- a/Export/ExportManager.php +++ b/Export/ExportManager.php @@ -380,7 +380,9 @@ class ExportManager //debugging $this->logger->debug('user has no access to element', array( 'method' => __METHOD__, - 'type' => get_class($element), 'center' => $center->getName() + 'type' => get_class($element), + 'center' => $center->getName(), + 'role' => $role->getRole() )); return false; diff --git a/Export/Formatter/CSVListFormatter.php b/Export/Formatter/CSVListFormatter.php index 190c7308f..a6981760a 100644 --- a/Export/Formatter/CSVListFormatter.php +++ b/Export/Formatter/CSVListFormatter.php @@ -206,6 +206,14 @@ class CSVListFormatter implements FormatterInterface $this->prepareCacheLabels(); } + if (!\array_key_exists($key, $this->labelsCache)){ + throw new \OutOfBoundsException(sprintf("The key \"%s\" " + . "is not present in the list of keys handled by " + . "this query. Check your `getKeys` and `getLabels` " + . "methods. Available keys are %s.", $key, + \implode(", ", \array_keys($this->labelsCache)))); + } + return $this->labelsCache[$key]($value); } diff --git a/Export/Formatter/SpreadsheetListFormatter.php b/Export/Formatter/SpreadsheetListFormatter.php new file mode 100644 index 000000000..93ca1c08b --- /dev/null +++ b/Export/Formatter/SpreadsheetListFormatter.php @@ -0,0 +1,287 @@ + + * + * 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\MainBundle\Export\Formatter; + +use Symfony\Component\HttpFoundation\Response; +use Chill\MainBundle\Export\FormatterInterface; +use Symfony\Component\Translation\TranslatorInterface; +use Symfony\Component\Form\FormBuilderInterface; +use Chill\MainBundle\Export\ExportManager; +use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use PhpOffice\PhpSpreadsheet\Spreadsheet; +use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; +use PhpOffice\PhpSpreadsheet\Shared\Date; +use PhpOffice\PhpSpreadsheet\Style\NumberFormat; + +// command to get the report with curl : curl --user "center a_social:password" "http://localhost:8000/fr/exports/generate/count_person?export[filters][person_gender_filter][enabled]=&export[filters][person_nationality_filter][enabled]=&export[filters][person_nationality_filter][form][nationalities]=&export[aggregators][person_nationality_aggregator][order]=1&export[aggregators][person_nationality_aggregator][form][group_by_level]=country&export[submit]=&export[_token]=RHpjHl389GrK-bd6iY5NsEqrD5UKOTHH40QKE9J1edU" --globoff + +/** + * Create a CSV List for the export + * + * @author Champs-Libres + */ +class SpreadsheetListFormatter implements FormatterInterface +{ + /** + * This variable cache the labels internally + * + * @var string[] + */ + protected $labelsCache = null; + + protected $result = null; + + protected $exportAlias = null; + + protected $exportData = null; + + protected $formatterData = null; + + /** + * + * @var ExportManager + */ + protected $exportManager; + + /** + * + * @var TranslatorInterface + */ + protected $translator; + + + + public function __construct(TranslatorInterface $translator, ExportManager $manager) + { + $this->translator = $translator; + $this->exportManager = $manager; + } + + public function getType() + { + return FormatterInterface::TYPE_LIST; + } + + /** + * build a form, which will be used to collect data required for the execution + * of this formatter. + * + * @uses appendAggregatorForm + * @param FormBuilderInterface $builder + * @param type $exportAlias + * @param array $aggregatorAliases + */ + public function buildForm( + FormBuilderInterface $builder, + $exportAlias, + array $aggregatorAliases + ){ + $builder + ->add('format', ChoiceType::class, array( + 'choices' => array( + 'OpenDocument Format (.ods) (LibreOffice, ...)' => 'ods', + 'Microsoft Excel 2007-2013 XML (.xlsx) (Microsoft Excel, LibreOffice)' => 'xlsx' + ), + 'choices_as_values' => true, + 'placeholder' => 'Choose the format' + )) + ->add('numerotation', ChoiceType::class, array( + 'choices' => array( + 'yes' => true, + 'no' => false + ), + 'expanded' => true, + 'multiple' => false, + 'label' => "Add a number on first column", + 'choices_as_values' => true, + 'data' => true + )); + } + + public function getName() + { + return 'Spreadsheet list formatter (.xlsx, .ods)'; + } + + /** + * Generate a response from the data collected on differents ExportElementInterface + * + * @param mixed[] $result The result, as given by the ExportInterface + * @param mixed[] $formatterData collected from the current form + * @param string $exportAlias the id of the current export + * @param array $filtersData an array containing the filters data. The key are the filters id, and the value are the data + * @param array $aggregatorsData an array containing the aggregators data. The key are the filters id, and the value are the data + * @return \Symfony\Component\HttpFoundation\Response The response to be shown + */ + public function getResponse( + $result, + $formatterData, + $exportAlias, + array $exportData, + array $filtersData, + array $aggregatorsData + ) { + $this->result = $result; + $this->exportAlias = $exportAlias; + $this->exportData = $exportData; + $this->formatterData = $formatterData; + + $spreadsheet = new Spreadsheet(); + $worksheet = $spreadsheet->getActiveSheet(); + + $this->prepareHeaders($worksheet); + + $i = 1; + foreach ($result as $row) { + $line = array(); + + if ($this->formatterData['numerotation'] === true) { + $worksheet->setCellValue('A'.($i+1), (string) $i); + } + + $a = $this->formatterData['numerotation'] ? 'B' : 'A'; + foreach ($row as $key => $value) { + $row = $a.($i+1); + if ($value instanceof \DateTimeInterface) { + $worksheet->setCellValue($row, Date::PHPToExcel($value)); + $worksheet->getStyle($row) + ->getNumberFormat() + ->setFormatCode(NumberFormat::FORMAT_DATE_DDMMYYYY); + } else { + $worksheet->setCellValue($row, $this->getLabel($key, $value)); + } + $a ++; + } + + $i++; + } + + switch ($this->formatterData['format']) + { + case 'ods': + $writer = \PhpOffice\PhpSpreadsheet\IOFactory + ::createWriter($spreadsheet, 'Ods'); + $contentType = "application/vnd.oasis.opendocument.spreadsheet"; + break; + case 'xlsx': + $writer = \PhpOffice\PhpSpreadsheet\IOFactory + ::createWriter($spreadsheet, 'Xlsx'); + $contentType = 'application/vnd.openxmlformats-officedocument.' + . 'spreadsheetml.sheet'; + break; + case 'csv': + $writer = \PhpOffice\PhpSpreadsheet\IOFactory + ::createWriter($spreadsheet, 'Csv'); + $contentType = 'text/csv'; + break; + default: + // this should not happen + // throw an exception to ensure that the error is catched + throw new \OutOfBoundsException("The format ".$this->formatterData['format']. + " is not supported"); + } + + $response = new Response(); + $response->headers->set('content-type', $contentType); + + $tempfile = \tempnam(\sys_get_temp_dir(), ''); + $writer->save($tempfile); + + $f = \fopen($tempfile, 'r'); + $response->setContent(\stream_get_contents($f)); + fclose($f); + + // remove the temp file from disk + \unlink($tempfile); + + return $response; + } + + /** + * add the headers to the csv file + * + * @param Worksheet $worksheet + */ + protected function prepareHeaders(Worksheet $worksheet) + { + $keys = $this->exportManager->getExport($this->exportAlias)->getQueryKeys($this->exportData); + // we want to keep the order of the first row. So we will iterate on the first row of the results + $first_row = count($this->result) > 0 ? $this->result[0] : array(); + $header_line = array(); + + if ($this->formatterData['numerotation'] === true) { + $header_line[] = $this->translator->trans('Number'); + } + + foreach ($first_row as $key => $value) { + $header_line[] = $this->translator->trans( + $this->getLabel($key, '_header')); + } + + if (count($header_line) > 0) { + $worksheet->fromArray($header_line, NULL, 'A1'); + } + } + + /** + * Give the label corresponding to the given key and value. + * + * @param string $key + * @param string $value + * @return string + * @throws \LogicException if the label is not found + */ + protected function getLabel($key, $value) + { + + if ($this->labelsCache === null) { + $this->prepareCacheLabels(); + } + + if (!\array_key_exists($key, $this->labelsCache)){ + throw new \OutOfBoundsException(sprintf("The key \"%s\" " + . "is not present in the list of keys handled by " + . "this query. Check your `getKeys` and `getLabels` " + . "methods. Available keys are %s.", $key, + \implode(", ", \array_keys($this->labelsCache)))); + } + + return $this->labelsCache[$key]($value); + } + + /** + * Prepare the label cache which will be used by getLabel. This function + * should be called only once in the generation lifecycle. + */ + protected function prepareCacheLabels() + { + $export = $this->exportManager->getExport($this->exportAlias); + $keys = $export->getQueryKeys($this->exportData); + + foreach($keys as $key) { + // get an array with all values for this key if possible + $values = \array_map(function ($v) use ($key) { return $v[$key]; }, $this->result); + // store the label in the labelsCache property + $this->labelsCache[$key] = $export->getLabels($key, $values, $this->exportData); + } + } + + +} diff --git a/Form/Type/Export/PickFormatterType.php b/Form/Type/Export/PickFormatterType.php index 88a0b7ba8..aee1507f8 100644 --- a/Form/Type/Export/PickFormatterType.php +++ b/Form/Type/Export/PickFormatterType.php @@ -56,7 +56,8 @@ class PickFormatterType extends AbstractType $builder->add('alias', ChoiceType::class, array( 'choices' => $choices, 'choices_as_values' => true, - 'multiple' => false + 'multiple' => false, + 'placeholder' => 'Choose a format' )); //$builder->get('type')->addModelTransformer($transformer); diff --git a/Resources/config/services/export.yml b/Resources/config/services/export.yml index 45ae31181..bd6d730da 100644 --- a/Resources/config/services/export.yml +++ b/Resources/config/services/export.yml @@ -29,6 +29,14 @@ services: tags: - { name: chill.export_formatter, alias: 'csvlist' } + chill.main.export.list_spreadsheet_formatter: + class: Chill\MainBundle\Export\Formatter\SpreadsheetListFormatter + arguments: + - "@translator" + - "@chill.main.export_manager" + tags: + - { name: chill.export_formatter, alias: 'spreadlist' } + chill.main.export.pivoted_list_formatter: class: Chill\MainBundle\Export\Formatter\CSVPivotedListFormatter arguments: diff --git a/Resources/translations/messages.fr.yml b/Resources/translations/messages.fr.yml index 90baaa4dc..d810fcc41 100644 --- a/Resources/translations/messages.fr.yml +++ b/Resources/translations/messages.fr.yml @@ -159,6 +159,7 @@ Export parameters: Paramètres d'export Filters: Filtres Aggregators: Aggrégateurs Go to formatter options: Vers les options de mise en forme +Choose a format: Choisir un format #export creation step 'formatter' : choose formatter option Generate the report: Générer le rapport No options availables. Your report is fully configured.: Pas d'options disponibles. Votre rapport est déjà configuré. @@ -178,6 +179,7 @@ Number: Numéro # the label which appears in the UI CSV vertical list: Liste verticale au format CSV CSV horizontal list: Liste horizontale au format CSV +Spreadsheet list formatter (.xlsx, .ods): Liste au format tableur (.xlsx, .ods) Order: Ordre Position: Position row: ligne