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