From 8e41445fbd75b22a278b75b7b074987372a9d902 Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Thu, 3 Jun 2021 19:11:31 +0200 Subject: [PATCH] Allow more control over what non-string datatypes are converted to strings in the StringValueBinder --- src/PhpSpreadsheet/Cell/StringValueBinder.php | 78 +++++- .../Cell/StringValueBinderTest.php | 225 ++++++++++++++++++ 2 files changed, 301 insertions(+), 2 deletions(-) create mode 100644 tests/PhpSpreadsheetTests/Cell/StringValueBinderTest.php diff --git a/src/PhpSpreadsheet/Cell/StringValueBinder.php b/src/PhpSpreadsheet/Cell/StringValueBinder.php index 346d0253..1c4e579c 100644 --- a/src/PhpSpreadsheet/Cell/StringValueBinder.php +++ b/src/PhpSpreadsheet/Cell/StringValueBinder.php @@ -2,10 +2,58 @@ namespace PhpOffice\PhpSpreadsheet\Cell; +use DateTimeInterface; +use PhpOffice\PhpSpreadsheet\RichText\RichText; use PhpOffice\PhpSpreadsheet\Shared\StringHelper; class StringValueBinder implements IValueBinder { + protected $convertNull = true; + + protected $convertBoolean = true; + + protected $convertNumeric = true; + + protected $convertFormula = true; + + public function setNullConversion(bool $suppressConversion = false): self + { + $this->convertNull = $suppressConversion; + + return $this; + } + + public function setBooleanConversion(bool $suppressConversion = false): self + { + $this->convertBoolean = $suppressConversion; + + return $this; + } + + public function setNumericConversion(bool $suppressConversion = false): self + { + $this->convertNumeric = $suppressConversion; + + return $this; + } + + public function setFormulaConversion(bool $suppressConversion = false): self + { + $this->convertFormula = $suppressConversion; + + return $this; + } + + public function setConversionForAllValueTypes(bool $suppressConversion = false): self + { + $this->convertNull = $suppressConversion; + $this->convertBoolean = $suppressConversion; + $this->convertNumeric = $suppressConversion; + $this->convertFormula = $suppressConversion; + + return $this; + } + /** * Bind value to a cell. * @@ -21,9 +69,35 @@ class StringValueBinder implements IValueBinder $value = StringHelper::sanitizeUTF8($value); } - $cell->setValueExplicit((string) $value, DataType::TYPE_STRING); + if (is_object($value)) { + // Handle any objects that might be injected + if ($value instanceof DateTimeInterface) { + $value = $value->format('Y-m-d H:i:s'); + } elseif ($value instanceof RichText) { + $cell->setValueExplicit((string) $value, DataType::TYPE_INLINE); + + return true; + } else { + // Attempt to cast any unexpected objects to string + $value = (string) $value; + } + } + + if ($value === null && $this->convertNull === false) { + $cell->setValueExplicit($value, DataType::TYPE_NULL); + } elseif (is_bool($value) && $this->convertBoolean === false) { + $cell->setValueExplicit($value, DataType::TYPE_BOOL); + } elseif ((is_int($value) || is_float($value)) && $this->convertNumeric === false) { + $cell->setValueExplicit($value, DataType::TYPE_NUMERIC); + } elseif (is_string($value) && strlen($value) > 1 && $value[0] === '=' && $this->convertFormula === false) { + $cell->setValueExplicit($value, DataType::TYPE_FORMULA); + } else { + if (is_string($value) && strlen($value) > 1 && $value[0] === '=') { + $cell->getStyle()->setQuotePrefix(true); + } + $cell->setValueExplicit((string) $value, DataType::TYPE_STRING); + } - // Done! return true; } } diff --git a/tests/PhpSpreadsheetTests/Cell/StringValueBinderTest.php b/tests/PhpSpreadsheetTests/Cell/StringValueBinderTest.php new file mode 100644 index 00000000..0f9b2f39 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Cell/StringValueBinderTest.php @@ -0,0 +1,225 @@ +getMockBuilder(Style::class) + ->disableOriginalConstructor() + ->getMock(); + + /** @var Cell&MockObject $cellStub */ + $cellStub = $this->getMockBuilder(Cell::class) + ->disableOriginalConstructor() + ->getMock(); + + // Configure the stub. + $cellStub->expects(self::once()) + ->method('setValueExplicit') + ->with($expectedValue, $expectedDataType) + ->willReturn(true); + $cellStub->expects($quotePrefix ? self::once() : self::never()) + ->method('getStyle') + ->willReturn($styleStub); + + return $cellStub; + } + + /** + * @dataProvider providerDataValuesDefault + */ + public function testStringValueBinderDefaultBehaviour( + $value, + $expectedValue, + string $expectedDataType, + bool $quotePrefix = false + ): void { + $cellStub = $this->createCellStub($expectedValue, $expectedDataType, $quotePrefix); + + $binder = new StringValueBinder(); + $binder->bindValue($cellStub, $value); + } + + public function providerDataValuesDefault(): array + { + return [ + [null, '', DataType::TYPE_STRING], + [true, '1', DataType::TYPE_STRING], + [false, '', DataType::TYPE_STRING], + ['', '', DataType::TYPE_STRING], + ['123', '123', DataType::TYPE_STRING], + ['123.456', '123.456', DataType::TYPE_STRING], + ['0.123', '0.123', DataType::TYPE_STRING], + ['.123', '.123', DataType::TYPE_STRING], + ['-0.123', '-0.123', DataType::TYPE_STRING], + ['-.123', '-.123', DataType::TYPE_STRING], + ['1.23e-4', '1.23e-4', DataType::TYPE_STRING], + ['ABC', 'ABC', DataType::TYPE_STRING], + ['=SUM(A1:C3)', '=SUM(A1:C3)', DataType::TYPE_STRING, true], + [123, '123', DataType::TYPE_STRING], + [123.456, '123.456', DataType::TYPE_STRING], + [0.123, '0.123', DataType::TYPE_STRING], + [.123, '0.123', DataType::TYPE_STRING], + [-0.123, '-0.123', DataType::TYPE_STRING], + [-.123, '-0.123', DataType::TYPE_STRING], + [1.23e-4, '0.000123', DataType::TYPE_STRING], + [1.23e-24, '1.23E-24', DataType::TYPE_STRING], + [new DateTime('2021-06-01 00:00:00', new DateTimeZone('UTC')), '2021-06-01 00:00:00', DataType::TYPE_STRING], + ]; + } + + /** + * @dataProvider providerDataValuesSuppressNullConversion + */ + public function testStringValueBinderSuppressNullConversion( + $value, + $expectedValue, + string $expectedDataType, + bool $quotePrefix = false + ): void { + $cellStub = $this->createCellStub($expectedValue, $expectedDataType, $quotePrefix); + + $binder = new StringValueBinder(); + $binder->setNullConversion(false); + $binder->bindValue($cellStub, $value); + } + + public function providerDataValuesSuppressNullConversion(): array + { + return [ + [null, null, DataType::TYPE_NULL], + ]; + } + + /** + * @dataProvider providerDataValuesSuppressBooleanConversion + */ + public function testStringValueBinderSuppressBooleanConversion( + $value, + $expectedValue, + string $expectedDataType, + bool $quotePrefix = false + ): void { + $cellStub = $this->createCellStub($expectedValue, $expectedDataType, $quotePrefix); + + $binder = new StringValueBinder(); + $binder->setBooleanConversion(false); + $binder->bindValue($cellStub, $value); + } + + public function providerDataValuesSuppressBooleanConversion(): array + { + return [ + [true, true, DataType::TYPE_BOOL], + [false, false, DataType::TYPE_BOOL], + ]; + } + + /** + * @dataProvider providerDataValuesSuppressNumericConversion + */ + public function testStringValueBinderSuppressNumericConversion( + $value, + $expectedValue, + string $expectedDataType, + bool $quotePrefix = false + ): void { + $cellStub = $this->createCellStub($expectedValue, $expectedDataType, $quotePrefix); + + $binder = new StringValueBinder(); + $binder->setNumericConversion(false); + $binder->bindValue($cellStub, $value); + } + + public function providerDataValuesSuppressNumericConversion(): array + { + return [ + [123, 123, DataType::TYPE_NUMERIC], + [123.456, 123.456, DataType::TYPE_NUMERIC], + [0.123, 0.123, DataType::TYPE_NUMERIC], + [.123, 0.123, DataType::TYPE_NUMERIC], + [-0.123, -0.123, DataType::TYPE_NUMERIC], + [-.123, -0.123, DataType::TYPE_NUMERIC], + [1.23e-4, 0.000123, DataType::TYPE_NUMERIC], + [1.23e-24, 1.23E-24, DataType::TYPE_NUMERIC], + ]; + } + + /** + * @dataProvider providerDataValuesSuppressFormulaConversion + */ + public function testStringValueBinderSuppressFormulaConversion( + $value, + $expectedValue, + string $expectedDataType, + bool $quotePrefix = false + ): void { + $cellStub = $this->createCellStub($expectedValue, $expectedDataType, $quotePrefix); + + $binder = new StringValueBinder(); + $binder->setFormulaConversion(false); + $binder->bindValue($cellStub, $value); + } + + public function providerDataValuesSuppressFormulaConversion(): array + { + return [ + ['=SUM(A1:C3)', '=SUM(A1:C3)', DataType::TYPE_FORMULA, false], + ]; + } + + /** + * @dataProvider providerDataValuesSuppressAllConversion + */ + public function testStringValueBinderSuppressAllConversion( + $value, + $expectedValue, + string $expectedDataType, + bool $quotePrefix = false + ): void { + $cellStub = $this->createCellStub($expectedValue, $expectedDataType, $quotePrefix); + + $binder = new StringValueBinder(); + $binder->setConversionForAllValueTypes(false); + $binder->bindValue($cellStub, $value); + } + + public function providerDataValuesSuppressAllConversion(): array + { + return [ + [null, null, DataType::TYPE_NULL], + [true, true, DataType::TYPE_BOOL], + [false, false, DataType::TYPE_BOOL], + ['', '', DataType::TYPE_STRING], + ['123', '123', DataType::TYPE_STRING], + ['123.456', '123.456', DataType::TYPE_STRING], + ['0.123', '0.123', DataType::TYPE_STRING], + ['.123', '.123', DataType::TYPE_STRING], + ['-0.123', '-0.123', DataType::TYPE_STRING], + ['-.123', '-.123', DataType::TYPE_STRING], + ['1.23e-4', '1.23e-4', DataType::TYPE_STRING], + ['ABC', 'ABC', DataType::TYPE_STRING], + ['=SUM(A1:C3)', '=SUM(A1:C3)', DataType::TYPE_FORMULA, false], + [123, 123, DataType::TYPE_NUMERIC], + [123.456, 123.456, DataType::TYPE_NUMERIC], + [0.123, 0.123, DataType::TYPE_NUMERIC], + [.123, 0.123, DataType::TYPE_NUMERIC], + [-0.123, -0.123, DataType::TYPE_NUMERIC], + [-.123, -0.123, DataType::TYPE_NUMERIC], + [1.23e-4, 0.000123, DataType::TYPE_NUMERIC], + [1.23e-24, 1.23E-24, DataType::TYPE_NUMERIC], + ]; + } +}