diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 5ef5522f..024f1fbd 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -2860,11 +2860,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/Spreadsheet.php - - - message: "#^Comparison operation \"\\<\\=\" between int\\ and 1000 is always true\\.$#" - count: 1 - path: src/PhpSpreadsheet/Spreadsheet.php - - message: "#^Parameter \\#1 \\$worksheet of method PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet\\:\\:getIndex\\(\\) expects PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet, PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\|null given\\.$#" count: 1 @@ -2875,21 +2870,11 @@ parameters: count: 1 path: src/PhpSpreadsheet/Spreadsheet.php - - - message: "#^Result of \\|\\| is always true\\.$#" - count: 1 - path: src/PhpSpreadsheet/Spreadsheet.php - - message: "#^Strict comparison using \\=\\=\\= between PhpOffice\\\\PhpSpreadsheet\\\\Spreadsheet and null will always evaluate to false\\.$#" count: 1 path: src/PhpSpreadsheet/Spreadsheet.php - - - message: "#^Strict comparison using \\=\\=\\= between string and null will always evaluate to false\\.$#" - count: 1 - path: src/PhpSpreadsheet/Spreadsheet.php - - message: "#^Unreachable statement \\- code above always terminates\\.$#" count: 1 diff --git a/src/PhpSpreadsheet/Spreadsheet.php b/src/PhpSpreadsheet/Spreadsheet.php index 33b4fe0c..4bb93987 100644 --- a/src/PhpSpreadsheet/Spreadsheet.php +++ b/src/PhpSpreadsheet/Spreadsheet.php @@ -3,10 +3,13 @@ namespace PhpOffice\PhpSpreadsheet; use PhpOffice\PhpSpreadsheet\Calculation\Calculation; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx as XlsxReader; +use PhpOffice\PhpSpreadsheet\Shared\File; use PhpOffice\PhpSpreadsheet\Shared\StringHelper; use PhpOffice\PhpSpreadsheet\Style\Style; use PhpOffice\PhpSpreadsheet\Worksheet\Iterator; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; +use PhpOffice\PhpSpreadsheet\Writer\Xlsx as XlsxWriter; class Spreadsheet { @@ -1120,28 +1123,24 @@ class Spreadsheet */ public function copy() { - $copied = clone $this; + $filename = File::temporaryFilename(); + $writer = new XlsxWriter($this); + $writer->setIncludeCharts(true); + $writer->save($filename); - $worksheetCount = count($this->workSheetCollection); - for ($i = 0; $i < $worksheetCount; ++$i) { - $this->workSheetCollection[$i] = $this->workSheetCollection[$i]->copy(); - $this->workSheetCollection[$i]->rebindParent($this); - } + $reader = new XlsxReader(); + $reader->setIncludeCharts(true); + $reloadedSpreadsheet = $reader->load($filename); + unlink($filename); - return $copied; + return $reloadedSpreadsheet; } - /** - * Implement PHP __clone to create a deep clone, not just a shallow copy. - */ public function __clone() { - // @phpstan-ignore-next-line - foreach ($this as $key => $val) { - if (is_object($val) || (is_array($val))) { - $this->{$key} = unserialize(serialize($val)); - } - } + throw new Exception( + 'Do not use clone on spreadsheet. Use spreadsheet->copy() instead.' + ); } /** @@ -1562,7 +1561,7 @@ class Spreadsheet * Workbook window is hidden and cannot be shown in the * user interface. * - * @param string $visibility visibility status of the workbook + * @param null|string $visibility visibility status of the workbook */ public function setVisibility($visibility): void { @@ -1596,7 +1595,7 @@ class Spreadsheet */ public function setTabRatio($tabRatio): void { - if ($tabRatio >= 0 || $tabRatio <= 1000) { + if ($tabRatio >= 0 && $tabRatio <= 1000) { $this->tabRatio = (int) $tabRatio; } else { throw new Exception('Tab ratio must be between 0 and 1000.'); diff --git a/tests/PhpSpreadsheetTests/DefinedNameTest.php b/tests/PhpSpreadsheetTests/DefinedNameTest.php index 43eddc8a..82950880 100644 --- a/tests/PhpSpreadsheetTests/DefinedNameTest.php +++ b/tests/PhpSpreadsheetTests/DefinedNameTest.php @@ -85,6 +85,18 @@ class DefinedNameTest extends TestCase self::assertCount(1, $this->spreadsheet->getDefinedNames()); } + public function testRemoveGlobalDefinedName(): void + { + $this->spreadsheet->addDefinedName( + DefinedName::createInstance('Any', $this->spreadsheet->getActiveSheet(), '=A1') + ); + self::assertCount(1, $this->spreadsheet->getDefinedNames()); + + $this->spreadsheet->removeDefinedName('Any'); + self::assertCount(0, $this->spreadsheet->getDefinedNames()); + $this->spreadsheet->removeDefinedName('Other'); + } + public function testRemoveGlobalDefinedNameWhenDuplicateNames(): void { $this->spreadsheet->addDefinedName( diff --git a/tests/PhpSpreadsheetTests/NamedFormulaTest.php b/tests/PhpSpreadsheetTests/NamedFormulaTest.php index 4c4a6b11..02e9d818 100644 --- a/tests/PhpSpreadsheetTests/NamedFormulaTest.php +++ b/tests/PhpSpreadsheetTests/NamedFormulaTest.php @@ -133,4 +133,10 @@ class NamedFormulaTest extends TestCase $formula->getValue() ); } + + public function testRemoveNonExistentNamedFormula(): void + { + self::assertCount(0, $this->spreadsheet->getNamedFormulae()); + $this->spreadsheet->removeNamedFormula('Any'); + } } diff --git a/tests/PhpSpreadsheetTests/NamedRangeTest.php b/tests/PhpSpreadsheetTests/NamedRangeTest.php index c72b7b73..402e7eba 100644 --- a/tests/PhpSpreadsheetTests/NamedRangeTest.php +++ b/tests/PhpSpreadsheetTests/NamedRangeTest.php @@ -133,4 +133,10 @@ class NamedRangeTest extends TestCase $range->getValue() ); } + + public function testRemoveNonExistentNamedRange(): void + { + self::assertCount(0, $this->spreadsheet->getNamedRanges()); + $this->spreadsheet->removeNamedRange('Any'); + } } diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/RibbonTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/RibbonTest.php index 197ad47f..ab304e7b 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xlsx/RibbonTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/RibbonTest.php @@ -44,4 +44,34 @@ class RibbonTest extends AbstractFunctional self::assertNull($reloadedSpreadsheet->getRibbonBinObjects()); $reloadedSpreadsheet->disconnectWorksheets(); } + + /** + * Same as above but discard macros. + */ + public function testDiscardMacros(): void + { + $filename = 'tests/data/Reader/XLSX/ribbon.donotopen.zip'; + $reader = IOFactory::createReader('Xlsx'); + $spreadsheet = $reader->load($filename); + self::assertTrue($spreadsheet->hasRibbon()); + $target = $spreadsheet->getRibbonXMLData('target'); + self::assertSame('customUI/customUI.xml', $target); + $data = $spreadsheet->getRibbonXMLData('data'); + self::assertIsString($data); + self::assertSame(1522, strlen($data)); + $vbaCode = (string) $spreadsheet->getMacrosCode(); + self::assertSame(13312, strlen($vbaCode)); + $spreadsheet->discardMacros(); + + $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx'); + $spreadsheet->disconnectWorksheets(); + self::assertTrue($reloadedSpreadsheet->hasRibbon()); + $ribbonData = $reloadedSpreadsheet->getRibbonXmlData(); + self::assertIsArray($ribbonData); + self::assertSame($target, $ribbonData['target'] ?? ''); + self::assertSame($data, $ribbonData['data'] ?? ''); + self::assertNull($reloadedSpreadsheet->getMacrosCode()); + self::assertNull($reloadedSpreadsheet->getRibbonBinObjects()); + $reloadedSpreadsheet->disconnectWorksheets(); + } } diff --git a/tests/PhpSpreadsheetTests/SpreadsheetCoverageTest.php b/tests/PhpSpreadsheetTests/SpreadsheetCoverageTest.php new file mode 100644 index 00000000..584c53fe --- /dev/null +++ b/tests/PhpSpreadsheetTests/SpreadsheetCoverageTest.php @@ -0,0 +1,209 @@ +getProperties(); + $properties->setCreator('Anyone'); + $properties->setTitle('Description'); + $spreadsheet2 = new Spreadsheet(); + self::assertNotEquals($properties, $spreadsheet2->getProperties()); + $properties2 = clone $properties; + $spreadsheet2->setProperties($properties2); + self::assertEquals($properties, $spreadsheet2->getProperties()); + $spreadsheet->disconnectWorksheets(); + $spreadsheet2->disconnectWorksheets(); + } + + public function testDocumentSecurity(): void + { + $spreadsheet = new Spreadsheet(); + $security = $spreadsheet->getSecurity(); + $security->setLockRevision(true); + $revisionsPassword = 'revpasswd'; + $security->setRevisionsPassword($revisionsPassword); + $spreadsheet2 = new Spreadsheet(); + self::assertNotEquals($security, $spreadsheet2->getSecurity()); + $security2 = clone $security; + $spreadsheet2->setSecurity($security2); + self::assertEquals($security, $spreadsheet2->getSecurity()); + $spreadsheet->disconnectWorksheets(); + $spreadsheet2->disconnectWorksheets(); + } + + public function testCellXfCollection(): void + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->getStyle('A1')->getFont()->setName('font1'); + $sheet->getStyle('A2')->getFont()->setName('font2'); + $sheet->getStyle('A3')->getFont()->setName('font3'); + $sheet->getStyle('B1')->getFont()->setName('font1'); + $sheet->getStyle('B2')->getFont()->setName('font2'); + $collection = $spreadsheet->getCellXfCollection(); + self::assertCount(4, $collection); + $font1Style = $collection[1]; + self::assertTrue($spreadsheet->cellXfExists($font1Style)); + self::assertSame('font1', $spreadsheet->getCellXfCollection()[1]->getFont()->getName()); + self::assertSame('font1', $sheet->getStyle('A1')->getFont()->getName()); + self::assertSame('font2', $sheet->getStyle('A2')->getFont()->getName()); + self::assertSame('font3', $sheet->getStyle('A3')->getFont()->getName()); + self::assertSame('font1', $sheet->getStyle('B1')->getFont()->getName()); + self::assertSame('font2', $sheet->getStyle('B2')->getFont()->getName()); + + $spreadsheet->removeCellXfByIndex(1); + self::assertFalse($spreadsheet->cellXfExists($font1Style)); + self::assertSame('font2', $spreadsheet->getCellXfCollection()[1]->getFont()->getName()); + self::assertSame('Calibri', $sheet->getStyle('A1')->getFont()->getName()); + self::assertSame('font2', $sheet->getStyle('A2')->getFont()->getName()); + self::assertSame('font3', $sheet->getStyle('A3')->getFont()->getName()); + self::assertSame('Calibri', $sheet->getStyle('B1')->getFont()->getName()); + self::assertSame('font2', $sheet->getStyle('B2')->getFont()->getName()); + $spreadsheet->disconnectWorksheets(); + } + + public function testInvalidRemoveCellXfByIndex(): void + { + $this->expectException(SSException::class); + $this->expectExceptionMessage('CellXf index is out of bounds.'); + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->getStyle('A1')->getFont()->setName('font1'); + $sheet->getStyle('A2')->getFont()->setName('font2'); + $sheet->getStyle('A3')->getFont()->setName('font3'); + $sheet->getStyle('B1')->getFont()->setName('font1'); + $sheet->getStyle('B2')->getFont()->setName('font2'); + $spreadsheet->removeCellXfByIndex(5); + $spreadsheet->disconnectWorksheets(); + } + + public function testInvalidRemoveDefaultStyle(): void + { + $this->expectException(SSException::class); + $this->expectExceptionMessage('No default style found for this workbook'); + // Removing default style probably should be disallowed. + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $spreadsheet->removeCellXfByIndex(0); + $style = $spreadsheet->getDefaultStyle(); + $spreadsheet->disconnectWorksheets(); + } + + public function testCellStyleXF(): void + { + $spreadsheet = new Spreadsheet(); + $collection = $spreadsheet->getCellStyleXfCollection(); + self::assertCount(1, $collection); + $styleXf = $collection[0]; + self::assertSame($styleXf, $spreadsheet->getCellStyleXfByIndex(0)); + $hash = $styleXf->getHashCode(); + self::assertSame($styleXf, $spreadsheet->getCellStyleXfByHashCode($hash)); + self::assertFalse($spreadsheet->getCellStyleXfByHashCode($hash . 'x')); + $spreadsheet->disconnectWorksheets(); + } + + public function testInvalidRemoveCellStyleXfByIndex(): void + { + $this->expectException(SSException::class); + $this->expectExceptionMessage('CellStyleXf index is out of bounds.'); + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $spreadsheet->removeCellStyleXfByIndex(5); + $spreadsheet->disconnectWorksheets(); + } + + public function testInvalidFirstSheetIndex(): void + { + $this->expectException(SSException::class); + $this->expectExceptionMessage('First sheet index must be a positive integer.'); + $spreadsheet = new Spreadsheet(); + $spreadsheet->setFirstSheetIndex(-1); + $spreadsheet->disconnectWorksheets(); + } + + public function testInvalidVisibility(): void + { + $this->expectException(SSException::class); + $this->expectExceptionMessage('Invalid visibility value.'); + $spreadsheet = new Spreadsheet(); + $spreadsheet->setVisibility(Spreadsheet::VISIBILITY_HIDDEN); + self::assertSame(Spreadsheet::VISIBILITY_HIDDEN, $spreadsheet->getVisibility()); + $spreadsheet->setVisibility(null); + self::assertSame(Spreadsheet::VISIBILITY_VISIBLE, $spreadsheet->getVisibility()); + $spreadsheet->setVisibility('badvalue'); + $spreadsheet->disconnectWorksheets(); + } + + public function testInvalidTabRatio(): void + { + $this->expectException(SSException::class); + $this->expectExceptionMessage('Tab ratio must be between 0 and 1000.'); + $spreadsheet = new Spreadsheet(); + $spreadsheet->setTabRatio(2000); + $spreadsheet->disconnectWorksheets(); + } + + public function testCopy(): void + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->getStyle('A1')->getFont()->setName('font1'); + $sheet->getStyle('A2')->getFont()->setName('font2'); + $sheet->getStyle('A3')->getFont()->setName('font3'); + $sheet->getStyle('B1')->getFont()->setName('font1'); + $sheet->getStyle('B2')->getFont()->setName('font2'); + $sheet->getCell('A1')->setValue('this is a1'); + $sheet->getCell('A2')->setValue('this is a2'); + $sheet->getCell('A3')->setValue('this is a3'); + $sheet->getCell('B1')->setValue('this is b1'); + $sheet->getCell('B2')->setValue('this is b2'); + $copied = $spreadsheet->copy(); + $copysheet = $copied->getActiveSheet(); + $copysheet->getStyle('A2')->getFont()->setName('font12'); + $copysheet->getCell('A2')->setValue('this was a2'); + + self::assertSame('font1', $sheet->getStyle('A1')->getFont()->getName()); + self::assertSame('font2', $sheet->getStyle('A2')->getFont()->getName()); + self::assertSame('font3', $sheet->getStyle('A3')->getFont()->getName()); + self::assertSame('font1', $sheet->getStyle('B1')->getFont()->getName()); + self::assertSame('font2', $sheet->getStyle('B2')->getFont()->getName()); + self::assertSame('this is a1', $sheet->getCell('A1')->getValue()); + self::assertSame('this is a2', $sheet->getCell('A2')->getValue()); + self::assertSame('this is a3', $sheet->getCell('A3')->getValue()); + self::assertSame('this is b1', $sheet->getCell('B1')->getValue()); + self::assertSame('this is b2', $sheet->getCell('B2')->getValue()); + + self::assertSame('font1', $copysheet->getStyle('A1')->getFont()->getName()); + self::assertSame('font12', $copysheet->getStyle('A2')->getFont()->getName()); + self::assertSame('font3', $copysheet->getStyle('A3')->getFont()->getName()); + self::assertSame('font1', $copysheet->getStyle('B1')->getFont()->getName()); + self::assertSame('font2', $copysheet->getStyle('B2')->getFont()->getName()); + self::assertSame('this is a1', $copysheet->getCell('A1')->getValue()); + self::assertSame('this was a2', $copysheet->getCell('A2')->getValue()); + self::assertSame('this is a3', $copysheet->getCell('A3')->getValue()); + self::assertSame('this is b1', $copysheet->getCell('B1')->getValue()); + self::assertSame('this is b2', $copysheet->getCell('B2')->getValue()); + + $spreadsheet->disconnectWorksheets(); + $copied->disconnectWorksheets(); + } + + public function testClone(): void + { + $this->expectException(SSException::class); + $this->expectExceptionMessage('Do not use clone on spreadsheet. Use spreadsheet->copy() instead.'); + $spreadsheet = new Spreadsheet(); + $clone = clone $spreadsheet; + $spreadsheet->disconnectWorksheets(); + $clone->disconnectWorksheets(); + } +} diff --git a/tests/PhpSpreadsheetTests/Style/FontTest.php b/tests/PhpSpreadsheetTests/Style/FontTest.php index 02814afa..6cd4d950 100644 --- a/tests/PhpSpreadsheetTests/Style/FontTest.php +++ b/tests/PhpSpreadsheetTests/Style/FontTest.php @@ -3,6 +3,7 @@ namespace PhpOffice\PhpSpreadsheetTests\Style; use PhpOffice\PhpSpreadsheet\Spreadsheet; +use PhpOffice\PhpSpreadsheet\Style\Font; use PHPUnit\Framework\TestCase; class FontTest extends TestCase @@ -88,4 +89,20 @@ class FontTest extends TestCase self::assertEquals('Calibri', $font->getName(), 'Null string changed to default'); $spreadsheet->disconnectWorksheets(); } + + public function testUnderlineHash(): void + { + $font1 = new Font(); + $font2 = new Font(); + $font2aHash = $font2->getHashCode(); + self::assertSame($font1->getHashCode(), $font2aHash); + $font2->setUnderlineColor( + [ + 'type' => 'srgbClr', + 'value' => 'FF0000', + ] + ); + $font2bHash = $font2->getHashCode(); + self::assertNotEquals($font1->getHashCode(), $font2bHash); + } }