diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 00000000..12ff2fdb
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "restructuredtext.confPath": "${workspaceFolder}/docs"
+}
\ No newline at end of file
diff --git a/docs/reference/symfony.rst b/docs/reference/symfony.rst
index 5238fe0d..5f8bee46 100644
--- a/docs/reference/symfony.rst
+++ b/docs/reference/symfony.rst
@@ -46,6 +46,7 @@ This service can be configured throught the following parameters:
* ``sonata.exporter.writer.csv.escape``: defaults to ``\\``
* ``sonata.exporter.writer.csv.show_headers``: defaults to ``true``
* ``sonata.exporter.writer.csv.with_bom``: defaults to ``false``
+* ``sonata.exporter.writer.csv.safe_cells``: defaults to ``false``
The JSON writer service
~~~~~~~~~~~~~~~~~~~~~~~
@@ -60,6 +61,7 @@ This service can be configured throught the following parameters:
* ``sonata.exporter.writer.xls.filename``: defaults to ``php://output``
* ``sonata.exporter.writer.xls.show_headers``: defaults to ``true``
+* ``sonata.exporter.writer.xls.safe_cells``: defaults to ``false``
The XML writer service
~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/src/Bridge/Symfony/DependencyInjection/Configuration.php b/src/Bridge/Symfony/DependencyInjection/Configuration.php
index dff87d2d..9947f37d 100644
--- a/src/Bridge/Symfony/DependencyInjection/Configuration.php
+++ b/src/Bridge/Symfony/DependencyInjection/Configuration.php
@@ -69,6 +69,10 @@ public function getConfigTreeBuilder(): TreeBuilder
->defaultValue(false)
->info('include the byte order mark')
->end()
+ ->booleanNode('safe_cells')
+ ->defaultValue(false)
+ ->info('escapes data cells that that may be interpreted as formulas in spreadsheet software')
+ ->end()
->end()
->end()
->arrayNode('json')
@@ -91,6 +95,10 @@ public function getConfigTreeBuilder(): TreeBuilder
->defaultValue(true)
->info('add column names as the first line')
->end()
+ ->booleanNode('safe_cells')
+ ->defaultValue(false)
+ ->info('escapes data cells that that may be interpreted as formulas in spreadsheet software')
+ ->end()
->end()
->end()
->arrayNode('xml')
diff --git a/src/Bridge/Symfony/Resources/config/services.xml b/src/Bridge/Symfony/Resources/config/services.xml
index 1cc45450..d0ac330d 100644
--- a/src/Bridge/Symfony/Resources/config/services.xml
+++ b/src/Bridge/Symfony/Resources/config/services.xml
@@ -8,6 +8,7 @@
%sonata.exporter.writer.csv.escape%
%sonata.exporter.writer.csv.show_headers%
%sonata.exporter.writer.csv.with_bom%
+ %sonata.exporter.writer.csv.safe_cells%
%sonata.exporter.writer.json.filename%
@@ -15,6 +16,7 @@
%sonata.exporter.writer.xls.filename%
%sonata.exporter.writer.xls.show_headers%
+ %sonata.exporter.writer.xls.safe_cells%
%sonata.exporter.writer.xml.filename%
diff --git a/src/Writer/CsvWriter.php b/src/Writer/CsvWriter.php
index d0c72496..354b8dd9 100644
--- a/src/Writer/CsvWriter.php
+++ b/src/Writer/CsvWriter.php
@@ -65,6 +65,11 @@ final class CsvWriter implements TypedWriterInterface
*/
private $terminate;
+ /**
+ * @var bool
+ */
+ private $safeCells;
+
/**
* @throws \RuntimeException
*/
@@ -75,7 +80,8 @@ public function __construct(
string $escape = '\\',
bool $showHeaders = true,
bool $withBom = false,
- string $terminate = "\n"
+ string $terminate = "\n",
+ bool $safeCells = false
) {
$this->filename = $filename;
$this->delimiter = $delimiter;
@@ -85,6 +91,7 @@ public function __construct(
$this->terminate = $terminate;
$this->position = 0;
$this->withBom = $withBom;
+ $this->safeCells = $safeCells;
if (is_file($filename)) {
throw new \RuntimeException(sprintf('The file %s already exist', $filename));
@@ -138,12 +145,14 @@ public function write(array $data): void
}
// prevent csv injection
- foreach ($data as $key => $value) {
- $data[$key] = preg_replace(
- ['/^=/', '/^\+/', '/^-/', '/^@/'],
- ['\'=', '\'+', '\'-', '\'@'],
- $value
- );
+ if (true === $this->safeCells) {
+ foreach ($data as $key => $value) {
+ $data[$key] = preg_replace(
+ ['/^=/', '/^\+/', '/^-/', '/^@/'],
+ ['\'=', '\'+', '\'-', '\'@'],
+ $value
+ );
+ }
}
$result = @fputcsv($this->file, $data, $this->delimiter, $this->enclosure, $this->escape);
diff --git a/src/Writer/XlsWriter.php b/src/Writer/XlsWriter.php
index 4a0ab464..e6311274 100644
--- a/src/Writer/XlsWriter.php
+++ b/src/Writer/XlsWriter.php
@@ -38,13 +38,19 @@ final class XlsWriter implements TypedWriterInterface
*/
private $position = 0;
+ /**
+ * @var bool
+ */
+ private $safeCells;
+
/**
* @throws \RuntimeException
*/
- public function __construct(string $filename, bool $showHeaders = true)
+ public function __construct(string $filename, bool $showHeaders = true, bool $safeCells = false)
{
$this->filename = $filename;
$this->showHeaders = $showHeaders;
+ $this->safeCells = $safeCells;
if (is_file($filename)) {
throw new \RuntimeException(sprintf('The file %s already exists', $filename));
@@ -79,12 +85,18 @@ public function write(array $data): void
fwrite($this->file, '');
// prevent xls injection
- foreach ($data as $value) {
- fwrite($this->file, sprintf('%s | ', preg_replace(
- ['/^=/', '/^\+/', '/^-/', '/^@/'],
- ['\'=', '\'+', '\'-', '\'@'],
- $value
- )));
+ if (true === $this->safeCells) {
+ foreach ($data as $value) {
+ fwrite($this->file, sprintf('%s | ', preg_replace(
+ ['/^=/', '/^\+/', '/^-/', '/^@/'],
+ ['\'=', '\'+', '\'-', '\'@'],
+ $value
+ )));
+ }
+ } else {
+ foreach ($data as $value) {
+ fwrite($this->file, sprintf('%s | ', $value));
+ }
}
fwrite($this->file, '
');
diff --git a/tests/Bridge/Symfony/DependencyInjection/ConfigurationTest.php b/tests/Bridge/Symfony/DependencyInjection/ConfigurationTest.php
index 3b1533fe..9041dbc9 100644
--- a/tests/Bridge/Symfony/DependencyInjection/ConfigurationTest.php
+++ b/tests/Bridge/Symfony/DependencyInjection/ConfigurationTest.php
@@ -42,6 +42,7 @@ public function testDefault(): void
'escape' => '\\',
'show_headers' => true,
'with_bom' => false,
+ 'safe_cells' => false,
],
'json' => [
'filename' => 'php://output',
@@ -49,6 +50,7 @@ public function testDefault(): void
'xls' => [
'filename' => 'php://output',
'show_headers' => true,
+ 'safe_cells' => false,
],
'xml' => [
'filename' => 'php://output',