From 79d56b7ba04b215fb211163fdbcb2e494ba9fe2a Mon Sep 17 00:00:00 2001 From: Serhii Petrov Date: Wed, 2 Oct 2024 18:06:24 +0300 Subject: [PATCH 01/16] Test against php 8.4 --- .github/workflows/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4a9c4d22bf..a5e6d357da 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,6 +13,7 @@ jobs: - '8.1' - '8.2' - '8.3' + - '8.4' include: - php-version: 'nightly' From 2f445a1ade488eddfddd1fdd6e1add7abaae7865 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Wed, 30 Oct 2024 17:25:49 -0700 Subject: [PATCH 02/16] Change Hash Code for Worksheet Fix #4192. Although that issue can be dealt with by changing user code, it would be better to fix it within PhpSpreadsheet. A cloned worksheet may have a pointer to a spreadsheet to which it is not attached. Code can assume it does belong to the spreadsheet, and throw an exception when the spreadsheet cannot find the worksheet in question. It may also not throw an exception when it should. In my comments to the issue, I was concerned that adding in the needed protection would add overhead to an extremely common situation (setting a cell's value) in order to avoid a pretty rare problem. However, there are problems with both the accuracy and efficiency of the existing code, and I think any performance losses caused by the additional checks will be offset by the performance gains and accuracy of the new code. Spreadsheet `getIndex` attempts to find the index of a worksheet within its spreadsheet collection. It does so by comparing the hash codes of each sheet in its collection with the hash code of the sheet it is looking for. Its major problem problem is performance-related, namely that it recomputes the hash code of the target sheet with each iteration. A more severe problem is the accuracy of the hash code. It generates this by hashing together the sheet title, the string range of its auto-filter, and a character representation of whether sheet protection is enabled. Title should definitely be part of the calculation (it must be unique for all sheets attached to a spreadsheet), but it is not clear why this subset of the other properties of Worksheet is used. It tries to save some cycles by using a `dirty` property to indicate whether re-hashing is necessary. It sets that property whenever the title changes, or when `setProtection` is called. So, it doesn't set it when auto-filter changes, and you can easily bypass `setProtection` when changing any of the `Protection` properties. Not to mention the many other properties of worksheet that can be changed. Additionally, if you clone a worksheet, the clone and the original will have the same hash code, which can lead to problems: ```php $clone = clone $original; $spreadsheet->getSheet($spreadsheet->getIndex($clone)) ->setCellValue('A1', 100); ``` That code will change the value of A1 in the original, not the clone. The `hash` property in Worksheet will now be calculated immediately when the object is constructed or cloned or unserialized. It will not be recalculated, and there is no longer a need for the `dirty` property, which is removed. Hash will be generated by spl_object_id, which was designed for this purpose. (So was spl_object_hash, but many online references suggest that \_id performs much better than \_hash.) Our problem example above will now throw an Exception, as it should, rather than changing the wrong cell. `setValueExplicit`, the problem in the original issue, will now test that the worksheet is attached to the spreadsheet before doing any style manipulation. In order that this not be a breaking change, `getHashCode` will continue to return string, but it is deprecated in favor of `getHashInt`, and Worksheet will no longer implement IComparable to facilitate the deprecation. I had a vague hope that this change might help with issue #641. It doesn't. --- src/PhpSpreadsheet/Cell/Cell.php | 2 +- src/PhpSpreadsheet/ReferenceHelper.php | 4 +- src/PhpSpreadsheet/Spreadsheet.php | 8 ++- src/PhpSpreadsheet/Worksheet/BaseDrawing.php | 2 +- src/PhpSpreadsheet/Worksheet/Worksheet.php | 34 +++++----- .../Worksheet/CloneTest.php | 66 +++++++++++++++++++ 6 files changed, 92 insertions(+), 24 deletions(-) create mode 100644 tests/PhpSpreadsheetTests/Worksheet/CloneTest.php diff --git a/src/PhpSpreadsheet/Cell/Cell.php b/src/PhpSpreadsheet/Cell/Cell.php index 1e353db913..89321abeda 100644 --- a/src/PhpSpreadsheet/Cell/Cell.php +++ b/src/PhpSpreadsheet/Cell/Cell.php @@ -324,7 +324,7 @@ public function setValueExplicit(mixed $value, string $dataType = DataType::TYPE self::updateIfCellIsTableHeader($this->getParent()?->getParent(), $this, $oldValue, $value); $worksheet = $this->getWorksheet(); $spreadsheet = $worksheet->getParent(); - if (isset($spreadsheet)) { + if (isset($spreadsheet) && $spreadsheet->getIndex($worksheet, true) >= 0) { $originalSelected = $worksheet->getSelectedCells(); $activeSheetIndex = $spreadsheet->getActiveSheetIndex(); $style = $this->getStyle(); diff --git a/src/PhpSpreadsheet/ReferenceHelper.php b/src/PhpSpreadsheet/ReferenceHelper.php index f139a5d53d..afcfa016ec 100644 --- a/src/PhpSpreadsheet/ReferenceHelper.php +++ b/src/PhpSpreadsheet/ReferenceHelper.php @@ -921,7 +921,7 @@ private function updateNamedRange(DefinedName $definedName, Worksheet $worksheet { $cellAddress = $definedName->getValue(); $asFormula = ($cellAddress[0] === '='); - if ($definedName->getWorksheet() !== null && $definedName->getWorksheet()->getHashCode() === $worksheet->getHashCode()) { + if ($definedName->getWorksheet() !== null && $definedName->getWorksheet()->getHashInt() === $worksheet->getHashInt()) { /** * If we delete the entire range that is referenced by a Named Range, MS Excel sets the value to #REF! * PhpSpreadsheet still only does a basic adjustment, so the Named Range will still reference Cells. @@ -940,7 +940,7 @@ private function updateNamedRange(DefinedName $definedName, Worksheet $worksheet private function updateNamedFormula(DefinedName $definedName, Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns, int $numberOfRows): void { - if ($definedName->getWorksheet() !== null && $definedName->getWorksheet()->getHashCode() === $worksheet->getHashCode()) { + if ($definedName->getWorksheet() !== null && $definedName->getWorksheet()->getHashInt() === $worksheet->getHashInt()) { /** * If we delete the entire range that is referenced by a Named Formula, MS Excel sets the value to #REF! * PhpSpreadsheet still only does a basic adjustment, so the Named Formula will still reference Cells. diff --git a/src/PhpSpreadsheet/Spreadsheet.php b/src/PhpSpreadsheet/Spreadsheet.php index 6ec75c33cc..943db95cc3 100644 --- a/src/PhpSpreadsheet/Spreadsheet.php +++ b/src/PhpSpreadsheet/Spreadsheet.php @@ -660,13 +660,17 @@ public function getSheetByNameOrThrow(string $worksheetName): Worksheet * * @return int index */ - public function getIndex(Worksheet $worksheet): int + public function getIndex(Worksheet $worksheet, bool $noThrow = false): int { + $wsHash = $worksheet->getHashInt(); foreach ($this->workSheetCollection as $key => $value) { - if ($value->getHashCode() === $worksheet->getHashCode()) { + if ($value->getHashInt() === $wsHash) { return $key; } } + if ($noThrow) { + return -1; + } throw new Exception('Sheet does not exist.'); } diff --git a/src/PhpSpreadsheet/Worksheet/BaseDrawing.php b/src/PhpSpreadsheet/Worksheet/BaseDrawing.php index abf8c7afbe..326b8d6d23 100644 --- a/src/PhpSpreadsheet/Worksheet/BaseDrawing.php +++ b/src/PhpSpreadsheet/Worksheet/BaseDrawing.php @@ -414,7 +414,7 @@ public function getHashCode(): string return md5( $this->name . $this->description - . (($this->worksheet === null) ? '' : $this->worksheet->getHashCode()) + . (($this->worksheet === null) ? '' : (string) $this->worksheet->getHashInt()) . $this->coordinates . $this->offsetX . $this->offsetY diff --git a/src/PhpSpreadsheet/Worksheet/Worksheet.php b/src/PhpSpreadsheet/Worksheet/Worksheet.php index 80339a8eae..546a6ffe5b 100644 --- a/src/PhpSpreadsheet/Worksheet/Worksheet.php +++ b/src/PhpSpreadsheet/Worksheet/Worksheet.php @@ -20,7 +20,6 @@ use PhpOffice\PhpSpreadsheet\Comment; use PhpOffice\PhpSpreadsheet\DefinedName; use PhpOffice\PhpSpreadsheet\Exception; -use PhpOffice\PhpSpreadsheet\IComparable; use PhpOffice\PhpSpreadsheet\ReferenceHelper; use PhpOffice\PhpSpreadsheet\RichText\RichText; use PhpOffice\PhpSpreadsheet\Shared; @@ -32,7 +31,7 @@ use PhpOffice\PhpSpreadsheet\Style\Protection as StyleProtection; use PhpOffice\PhpSpreadsheet\Style\Style; -class Worksheet implements IComparable +class Worksheet { // Break types public const BREAK_NONE = 0; @@ -305,15 +304,10 @@ class Worksheet implements IComparable */ private ?Color $tabColor = null; - /** - * Dirty flag. - */ - private bool $dirty = true; - /** * Hash. */ - private string $hash; + private int $hash; /** * CodeName. @@ -355,6 +349,7 @@ public function __construct(?Spreadsheet $parent = null, string $title = 'Worksh $this->autoFilter = new AutoFilter('', $this); // Table collection $this->tableCollection = new ArrayObject(); + $this->hash = spl_object_id($this); } /** @@ -383,6 +378,12 @@ public function __destruct() unset($this->rowDimensions, $this->columnDimensions, $this->tableCollection, $this->drawingCollection, $this->chartCollection, $this->autoFilter); } + public function __wakeup(): void + { + $this->hash = spl_object_id($this); + $this->parent = null; + } + /** * Return the cell collection. */ @@ -897,7 +898,6 @@ public function setTitle(string $title, bool $updateFormulaCellReferences = true // Set title $this->title = $title; - $this->dirty = true; if ($this->parent && $this->parent->getCalculationEngine()) { // New title @@ -1032,7 +1032,6 @@ public function getProtection(): Protection public function setProtection(Protection $protection): static { $this->protection = $protection; - $this->dirty = true; return $this; } @@ -3014,7 +3013,7 @@ private function validateNamedRange(string $definedName, bool $returnNullIfInval if ($namedRange->getLocalOnly()) { $worksheet = $namedRange->getWorksheet(); - if ($worksheet === null || $this->getHashCode() !== $worksheet->getHashCode()) { + if ($worksheet === null || $this->hash !== $worksheet->getHashInt()) { if ($returnNullIfInvalid) { return null; } @@ -3154,17 +3153,15 @@ public function garbageCollect(): static } /** - * Get hash code. - * - * @return string Hash code + * @deprecated 3.5.0 use getHashInt instead. */ public function getHashCode(): string { - if ($this->dirty) { - $this->hash = md5($this->title . $this->autoFilter . ($this->protection->isProtectionEnabled() ? 't' : 'f') . __CLASS__); - $this->dirty = false; - } + return (string) $this->hash; + } + public function getHashInt(): int + { return $this->hash; } @@ -3493,6 +3490,7 @@ public function __clone() } } } + $this->hash = spl_object_id($this); } /** diff --git a/tests/PhpSpreadsheetTests/Worksheet/CloneTest.php b/tests/PhpSpreadsheetTests/Worksheet/CloneTest.php new file mode 100644 index 0000000000..d722c0554b --- /dev/null +++ b/tests/PhpSpreadsheetTests/Worksheet/CloneTest.php @@ -0,0 +1,66 @@ +getActiveSheet(); + $sheet1->getCell('A1')->setValue(10); + $sheet2 = clone $sheet1; + $sheet2->getCell('A1')->setValue(20); + self::assertSame(0, $spreadsheet->getIndex($sheet1)); + $idx = $spreadsheet->getIndex($sheet2, true); + self::assertSame(-1, $idx); + $sheet2->setTitle('clone'); + $spreadsheet->addSheet($sheet2); + $idx = $spreadsheet->getIndex($sheet2, true); + self::assertSame(1, $idx); + self::assertSame(10, $spreadsheet->getSheet(0)->getCell('A1')->getValue()); + self::assertSame(20, $spreadsheet->getSheet(1)->getCell('A1')->getValue()); + $spreadsheet->disconnectWorksheets(); + } + + public function testGetCloneIndex(): void + { + $this->expectException(SpreadsheetException::class); + $this->expectExceptionMessage('Sheet does not exist'); + $spreadsheet = new Spreadsheet(); + $sheet1 = $spreadsheet->getActiveSheet(); + $sheet1->getCell('A1')->setValue(10); + $sheet2 = clone $sheet1; + $spreadsheet->getSheet($spreadsheet->getIndex($sheet2)) + ->setCellValue('A1', 100); + } + + public function testSerialize1(): void + { + // If worksheet attached to spreadsheet, can't serialize it. + $this->expectException(SpreadsheetException::class); + $this->expectExceptionMessage('cannot be serialized'); + $spreadsheet = new Spreadsheet(); + $sheet1 = $spreadsheet->getActiveSheet(); + serialize($sheet1); + } + + #[\PHPUnit\Framework\Attributes\RunInSeparateProcess] + public function testSerialize2(): void + { + $sheet1 = new Worksheet(); + $sheet1->getCell('A1')->setValue(10); + $serialized = serialize($sheet1); + /** @var Worksheet */ + $newSheet = unserialize($serialized); + self::assertSame(10, $newSheet->getCell('A1')->getValue()); + self::assertNotEquals($newSheet->getHashInt(), $sheet1->getHashInt()); + } +} From 3d4b4b0957177679587c91df156d74de014d8395 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Wed, 30 Oct 2024 17:57:26 -0700 Subject: [PATCH 03/16] Scrutinizer See if typehint helps. --- src/PhpSpreadsheet/Writer/Xlsx/Style.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Style.php b/src/PhpSpreadsheet/Writer/Xlsx/Style.php index daac5d020c..a31b8e2498 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Style.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Style.php @@ -142,6 +142,7 @@ public function writeStyles(Spreadsheet $spreadsheet): string // dxf for ($i = 0; $i < $this->getParentWriter()->getStylesConditionalHashTable()->count(); ++$i) { + /** @var ?Conditional */ $thisstyle = $this->getParentWriter()->getStylesConditionalHashTable()->getByIndex($i); if ($thisstyle !== null) { $this->writeCellStyleDxf($objWriter, $thisstyle->getStyle()); From b99d061726334cf3632bc17af4c721b9e3eaa4b0 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Thu, 7 Nov 2024 21:47:56 -0800 Subject: [PATCH 04/16] Clean Up Some Tests --- tests/PhpSpreadsheetTests/Shared/DateTest.php | 4 +++- .../Shared/StringHelperInvalidCharTest.php | 1 + .../Style/BorderRangeTest.php | 2 ++ .../PhpSpreadsheetTests/Style/BorderTest.php | 9 +++++++++ .../Wizard/WizardFactoryTest.php | 1 + tests/PhpSpreadsheetTests/Style/StyleTest.php | 9 +++++++++ .../Worksheet/AutoSizeTest.php | 20 +++++++++++-------- .../Worksheet/CloneTest.php | 1 - 8 files changed, 37 insertions(+), 10 deletions(-) diff --git a/tests/PhpSpreadsheetTests/Shared/DateTest.php b/tests/PhpSpreadsheetTests/Shared/DateTest.php index c276cf34b2..c770ac260e 100644 --- a/tests/PhpSpreadsheetTests/Shared/DateTest.php +++ b/tests/PhpSpreadsheetTests/Shared/DateTest.php @@ -9,6 +9,7 @@ use DateTimeZone; use PhpOffice\PhpSpreadsheet\Exception; use PhpOffice\PhpSpreadsheet\Shared\Date; +use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Style\NumberFormat; use PHPUnit\Framework\TestCase; @@ -207,7 +208,7 @@ public function testVarious(): void $date = Date::PHPToExcel('2020-01-01'); self::assertEquals(43831.0, $date); - $spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet(); + $spreadsheet = new Spreadsheet(); $sheet = $spreadsheet->getActiveSheet(); $sheet->setCellValue('B1', 'x'); /** @var float|int|string */ @@ -249,6 +250,7 @@ public function testVarious(): void ->getNumberFormat() ->setFormatCode('yyyy-mm-dd'); self::assertFalse(Date::isDateTime($cella4)); + $spreadsheet->disconnectWorksheets(); } public function testRoundMicroseconds(): void diff --git a/tests/PhpSpreadsheetTests/Shared/StringHelperInvalidCharTest.php b/tests/PhpSpreadsheetTests/Shared/StringHelperInvalidCharTest.php index eb46beaf39..13a58dfe19 100644 --- a/tests/PhpSpreadsheetTests/Shared/StringHelperInvalidCharTest.php +++ b/tests/PhpSpreadsheetTests/Shared/StringHelperInvalidCharTest.php @@ -43,5 +43,6 @@ public function testInvalidChar(): void $sheet->getCell("A$row")->getValue() ); } + $spreadsheet->disconnectWorksheets(); } } diff --git a/tests/PhpSpreadsheetTests/Style/BorderRangeTest.php b/tests/PhpSpreadsheetTests/Style/BorderRangeTest.php index 844fa3fb50..8c50ddc17d 100644 --- a/tests/PhpSpreadsheetTests/Style/BorderRangeTest.php +++ b/tests/PhpSpreadsheetTests/Style/BorderRangeTest.php @@ -62,6 +62,7 @@ public function testBorderRangeInAction(): void } } } + $spreadsheet->disconnectWorksheets(); } public function testBorderRangeDirectly(): void @@ -71,5 +72,6 @@ public function testBorderRangeDirectly(): void $sheet = $spreadsheet->getActiveSheet(); $style = $sheet->getStyle('A1:C1')->getBorders()->getTop()->setBorderStyle(Border::BORDER_THIN); self::assertSame('A1:C1', $style->getSelectedCells(), 'getSelectedCells should not change after a style operation on a border range'); + $spreadsheet->disconnectWorksheets(); } } diff --git a/tests/PhpSpreadsheetTests/Style/BorderTest.php b/tests/PhpSpreadsheetTests/Style/BorderTest.php index 9f18f07668..dfe7032130 100644 --- a/tests/PhpSpreadsheetTests/Style/BorderTest.php +++ b/tests/PhpSpreadsheetTests/Style/BorderTest.php @@ -31,6 +31,7 @@ public function testAllBorders(): void self::assertSame(Border::BORDER_THIN, $borders->getRight()->getBorderStyle()); self::assertSame(Border::BORDER_THIN, $borders->getLeft()->getBorderStyle()); self::assertSame(Border::BORDER_NONE, $borders->getDiagonal()->getBorderStyle()); + $spreadsheet->disconnectWorksheets(); } public function testAllBordersArray(): void @@ -45,6 +46,7 @@ public function testAllBordersArray(): void self::assertSame(Border::BORDER_THIN, $borders->getRight()->getBorderStyle()); self::assertSame(Border::BORDER_THIN, $borders->getLeft()->getBorderStyle()); self::assertSame(Border::BORDER_NONE, $borders->getDiagonal()->getBorderStyle()); + $spreadsheet->disconnectWorksheets(); } public function testAllBordersArrayNotSupervisor(): void @@ -86,6 +88,7 @@ public function testOutline(): void self::assertSame(Border::BORDER_THIN, $sheet->getCell('B2')->getStyle()->getBorders()->getBottom()->getBorderStyle()); self::assertSame(Border::BORDER_NONE, $sheet->getCell('B2')->getStyle()->getBorders()->getLeft()->getBorderStyle()); self::assertSame(Border::BORDER_NONE, $sheet->getCell('B2')->getStyle()->getBorders()->getTop()->getBorderStyle()); + $spreadsheet->disconnectWorksheets(); } public function testInside(): void @@ -115,6 +118,7 @@ public function testInside(): void self::assertSame(Border::BORDER_NONE, $sheet->getCell('B2')->getStyle()->getBorders()->getBottom()->getBorderStyle()); self::assertSame(Border::BORDER_THIN, $sheet->getCell('B2')->getStyle()->getBorders()->getLeft()->getBorderStyle()); self::assertSame(Border::BORDER_THIN, $sheet->getCell('B2')->getStyle()->getBorders()->getTop()->getBorderStyle()); + $spreadsheet->disconnectWorksheets(); } public function testHorizontal(): void @@ -144,6 +148,7 @@ public function testHorizontal(): void self::assertSame(Border::BORDER_NONE, $sheet->getCell('B2')->getStyle()->getBorders()->getBottom()->getBorderStyle()); self::assertSame(Border::BORDER_NONE, $sheet->getCell('B2')->getStyle()->getBorders()->getLeft()->getBorderStyle()); self::assertSame(Border::BORDER_THIN, $sheet->getCell('B2')->getStyle()->getBorders()->getTop()->getBorderStyle()); + $spreadsheet->disconnectWorksheets(); } public function testVertical(): void @@ -173,6 +178,7 @@ public function testVertical(): void self::assertSame(Border::BORDER_NONE, $sheet->getCell('B2')->getStyle()->getBorders()->getBottom()->getBorderStyle()); self::assertSame(Border::BORDER_THIN, $sheet->getCell('B2')->getStyle()->getBorders()->getLeft()->getBorderStyle()); self::assertSame(Border::BORDER_NONE, $sheet->getCell('B2')->getStyle()->getBorders()->getTop()->getBorderStyle()); + $spreadsheet->disconnectWorksheets(); } public function testNoSupervisorAllBorders(): void @@ -213,6 +219,7 @@ public function testNoSupervisorHorizontal(): void public function testGetSharedComponentPseudo(): void { $this->expectException(PhpSpreadsheetException::class); + $this->expectExceptionMessage('pseudo-border'); $spreadsheet = new Spreadsheet(); $sheet = $spreadsheet->getActiveSheet(); $sheet->getStyle('A1')->getBorders()->getHorizontal()->setBorderStyle(Border::BORDER_MEDIUM); @@ -233,6 +240,7 @@ public function testBorderStyle(): void $border->setBorderStyle(Border::BORDER_THIN)->setColor(new Color('FFFF0000')); self::assertEquals('FFFF0000', $border->getColor()->getARGB()); self::assertEquals(Border::BORDER_THIN, $border->getBorderStyle()); + $spreadsheet->disconnectWorksheets(); } public function testDiagonalDirection(): void @@ -245,5 +253,6 @@ public function testDiagonalDirection(): void self::assertSame(Border::BORDER_MEDIUM, $borders->getDiagonal()->getBorderStyle()); self::assertSame(Borders::DIAGONAL_BOTH, $borders->getDiagonalDirection()); + $spreadsheet->disconnectWorksheets(); } } diff --git a/tests/PhpSpreadsheetTests/Style/ConditionalFormatting/Wizard/WizardFactoryTest.php b/tests/PhpSpreadsheetTests/Style/ConditionalFormatting/Wizard/WizardFactoryTest.php index 04681b39a0..5002bcaa1a 100644 --- a/tests/PhpSpreadsheetTests/Style/ConditionalFormatting/Wizard/WizardFactoryTest.php +++ b/tests/PhpSpreadsheetTests/Style/ConditionalFormatting/Wizard/WizardFactoryTest.php @@ -66,6 +66,7 @@ public function testWizardFromConditional(string $sheetName, string $cellAddress $wizard = Wizard::fromConditional($conditional); self::assertEquals($expectedWizads[$index], $wizard::class); } + $spreadsheet->disconnectWorksheets(); } public static function conditionalProvider(): array diff --git a/tests/PhpSpreadsheetTests/Style/StyleTest.php b/tests/PhpSpreadsheetTests/Style/StyleTest.php index be67a5e792..6446bcf991 100644 --- a/tests/PhpSpreadsheetTests/Style/StyleTest.php +++ b/tests/PhpSpreadsheetTests/Style/StyleTest.php @@ -24,6 +24,7 @@ public function testStyleOddMethods(): void $styleArray = ['alignment' => ['textRotation' => 45]]; $outArray = $cell1style->getStyleArray($styleArray); self::assertEquals($styleArray, $outArray['quotePrefix']); + $spreadsheet->disconnectWorksheets(); } public function testStyleColumn(): void @@ -58,6 +59,7 @@ public function testStyleColumn(): void self::assertTrue($sheet->getStyle('A1')->getFont()->getItalic()); self::assertTrue($sheet->getStyle('B2')->getFont()->getItalic()); self::assertFalse($sheet->getStyle('C3')->getFont()->getItalic()); + $spreadsheet->disconnectWorksheets(); } public function testStyleIsReused(): void @@ -81,6 +83,7 @@ public function testStyleIsReused(): void $spreadsheet->garbageCollect(); self::assertCount(3, $spreadsheet->getCellXfCollection()); + $spreadsheet->disconnectWorksheets(); } public function testStyleRow(): void @@ -115,6 +118,7 @@ public function testStyleRow(): void self::assertFalse($sheet->getStyle('A1')->getFont()->getItalic()); self::assertTrue($sheet->getStyle('B2')->getFont()->getItalic()); self::assertTrue($sheet->getStyle('C3')->getFont()->getItalic()); + $spreadsheet->disconnectWorksheets(); } public function testIssue1712A(): void @@ -137,6 +141,7 @@ public function testIssue1712A(): void ->setRGB($rgb); self::assertEquals($rgb, $sheet->getCell('A1')->getStyle()->getFill()->getStartColor()->getRGB()); self::assertEquals($rgb, $sheet->getCell('B1')->getStyle()->getFill()->getStartColor()->getRGB()); + $spreadsheet->disconnectWorksheets(); } public function testIssue1712B(): void @@ -159,6 +164,7 @@ public function testIssue1712B(): void $sheet->fromArray(['OK', 'KO']); self::assertEquals($rgb, $sheet->getCell('A1')->getStyle()->getFill()->getStartColor()->getRGB()); self::assertEquals($rgb, $sheet->getCell('B1')->getStyle()->getFill()->getStartColor()->getRGB()); + $spreadsheet->disconnectWorksheets(); } public function testStyleLoopUpwards(): void @@ -184,6 +190,7 @@ public function testStyleLoopUpwards(): void self::assertFalse($sheet->getStyle('A1')->getFont()->getBold()); self::assertFalse($sheet->getStyle('B2')->getFont()->getBold()); self::assertTrue($sheet->getStyle('C3')->getFont()->getBold()); + $spreadsheet->disconnectWorksheets(); } public function testStyleCellAddressObject(): void @@ -195,6 +202,7 @@ public function testStyleCellAddressObject(): void $style->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_DATE_YYYYMMDDSLASH); self::assertSame(NumberFormat::FORMAT_DATE_YYYYMMDDSLASH, $style->getNumberFormat()->getFormatCode()); + $spreadsheet->disconnectWorksheets(); } public function testStyleCellRangeObject(): void @@ -208,5 +216,6 @@ public function testStyleCellRangeObject(): void $style->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_DATE_YYYYMMDDSLASH); self::assertSame(NumberFormat::FORMAT_DATE_YYYYMMDDSLASH, $style->getNumberFormat()->getFormatCode()); + $spreadsheet->disconnectWorksheets(); } } diff --git a/tests/PhpSpreadsheetTests/Worksheet/AutoSizeTest.php b/tests/PhpSpreadsheetTests/Worksheet/AutoSizeTest.php index 4a4f488bb3..558590bfb3 100644 --- a/tests/PhpSpreadsheetTests/Worksheet/AutoSizeTest.php +++ b/tests/PhpSpreadsheetTests/Worksheet/AutoSizeTest.php @@ -13,16 +13,14 @@ class AutoSizeTest extends TestCase { - protected Spreadsheet $spreadsheet; + private Spreadsheet $spreadsheet; - protected Worksheet $worksheet; + private Worksheet $worksheet; protected function setUp(): void { - parent::setUp(); - - $spreadsheet = new Spreadsheet(); - $this->worksheet = $spreadsheet->getActiveSheet(); + $this->spreadsheet = new Spreadsheet(); + $this->worksheet = $this->spreadsheet->getActiveSheet(); $this->worksheet->setCellValue('A1', 'YEAR') ->setCellValue('B1', 'QUARTER') @@ -44,7 +42,13 @@ protected function setUp(): void } } - protected function setTable(): Table + protected function tearDown(): void + { + $this->spreadsheet->disconnectWorksheets(); + unset($this->spreadsheet, $this->worksheet); + } + + private function setTable(): Table { $table = new Table('A1:D5', 'Sales_Data'); $tableStyle = new TableStyle(); @@ -55,7 +59,7 @@ protected function setTable(): Table return $table; } - protected function readColumnSizes(): array + private function readColumnSizes(): array { $columnSizes = []; $toColumn = $this->worksheet->getHighestColumn(); diff --git a/tests/PhpSpreadsheetTests/Worksheet/CloneTest.php b/tests/PhpSpreadsheetTests/Worksheet/CloneTest.php index d722c0554b..b66a9dd8de 100644 --- a/tests/PhpSpreadsheetTests/Worksheet/CloneTest.php +++ b/tests/PhpSpreadsheetTests/Worksheet/CloneTest.php @@ -52,7 +52,6 @@ public function testSerialize1(): void serialize($sheet1); } - #[\PHPUnit\Framework\Attributes\RunInSeparateProcess] public function testSerialize2(): void { $sheet1 = new Worksheet(); From 72d5fdcdbe4167ad52233b17807818e6b4c5f408 Mon Sep 17 00:00:00 2001 From: m7913d Date: Sat, 9 Nov 2024 15:36:57 +0100 Subject: [PATCH 05/16] Improve contributing guidelines for composer newbies To allow running `composer versions`, it is necessary to call `composer install` to install the (development) dependencies of this project. This may not be obvious to contributors unfamiliar with `composer'. --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 67aa525c60..2eb106f271 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,6 +4,7 @@ If you would like to contribute, here are some notes and guidelines: - All new development should be on feature/fix branches, which are then merged to the `master` branch once stable and approved; so the `master` branch is always the most up-to-date, working code - If you are going to submit a pull request, please fork from `master`, and submit your pull request back as a fix/feature branch referencing the GitHub issue number + - Install (development) dependencies by running `composer install` inside your PhpSpreadsheet clone. - The code must work with all PHP versions that we support. - You can call `composer versions` to test version compatibility. - Code style should be maintained. From 26e59cdfc2b9b947f902772185369d335f98506e Mon Sep 17 00:00:00 2001 From: m7913d Date: Sat, 9 Nov 2024 14:48:28 +0100 Subject: [PATCH 06/16] Fix incorrect versions error (ForbiddenThisUseContexts) PHPCompatibility erroneously flags the use of $this in enumerations. Commit 5e248cf moved disabling of this error check to .github/workflows/main.yml Hence, `composer versions` and `composer check` should also exclude this check. --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index a823d2378d..58da821636 100644 --- a/composer.json +++ b/composer.json @@ -47,7 +47,7 @@ "check": [ "./bin/check-phpdoc-types", "phpcs samples/ src/ tests/ --report=checkstyle", - "phpcs samples/ src/ tests/ --standard=PHPCompatibility --runtime-set testVersion 8.0- -n", + "phpcs samples/ src/ tests/ --standard=PHPCompatibility --runtime-set testVersion 8.0- --exclude=PHPCompatibility.Variables.ForbiddenThisUseContexts -n", "php-cs-fixer fix --ansi --dry-run --diff", "phpunit --color=always", "phpstan analyse --ansi --memory-limit=2048M" @@ -61,7 +61,7 @@ "php-cs-fixer fix" ], "versions": [ - "phpcs samples/ src/ tests/ --standard=PHPCompatibility --runtime-set testVersion 8.0- -n" + "phpcs samples/ src/ tests/ --standard=PHPCompatibility --runtime-set testVersion 8.0- --exclude=PHPCompatibility.Variables.ForbiddenThisUseContexts -n" ] }, "require": { From c1c12720a43b5b43ccdfa852d23811f3f97ff1af Mon Sep 17 00:00:00 2001 From: m7913d Date: Sat, 9 Nov 2024 17:15:33 +0100 Subject: [PATCH 07/16] Add support for tag when converting HTML to RichText Fixes #4223 --- src/PhpSpreadsheet/Helper/Html.php | 2 ++ tests/PhpSpreadsheetTests/Helper/HtmlTest.php | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/src/PhpSpreadsheet/Helper/Html.php b/src/PhpSpreadsheet/Helper/Html.php index e690ae6525..f9fc6e3dc2 100644 --- a/src/PhpSpreadsheet/Helper/Html.php +++ b/src/PhpSpreadsheet/Helper/Html.php @@ -561,6 +561,7 @@ class Html 'u' => [self::class, 'startUnderlineTag'], 'ins' => [self::class, 'startUnderlineTag'], 'del' => [self::class, 'startStrikethruTag'], + 's' => [self::class, 'startStrikethruTag'], 'sup' => [self::class, 'startSuperscriptTag'], 'sub' => [self::class, 'startSubscriptTag'], ]; @@ -575,6 +576,7 @@ class Html 'u' => [self::class, 'endUnderlineTag'], 'ins' => [self::class, 'endUnderlineTag'], 'del' => [self::class, 'endStrikethruTag'], + 's' => [self::class, 'endStrikethruTag'], 'sup' => [self::class, 'endSuperscriptTag'], 'sub' => [self::class, 'endSubscriptTag'], 'br' => [self::class, 'breakTag'], diff --git a/tests/PhpSpreadsheetTests/Helper/HtmlTest.php b/tests/PhpSpreadsheetTests/Helper/HtmlTest.php index 784eb4c551..398adff597 100644 --- a/tests/PhpSpreadsheetTests/Helper/HtmlTest.php +++ b/tests/PhpSpreadsheetTests/Helper/HtmlTest.php @@ -48,4 +48,23 @@ public function testLiTag(): void self::assertSame($expected, $actual->getPlainText()); } + + public function testSTag(): void + { + $html = new Html(); + $input = 'Hello testworld'; + $richText = $html->toRichTextObject($input); + $elements = $richText->getRichTextElements(); + + self::assertSame(count($elements), 3); + + self::assertSame($elements[0]->getText(), 'Hello '); + self::assertTrue($elements[0]->getFont() === null || !$elements[0]->getFont()->getStrikethrough()); + + self::assertSame($elements[1]->getText(), 'test'); + self::assertTrue($elements[1]->getFont() !== null && $elements[1]->getFont()->getStrikethrough()); + + self::assertSame($elements[2]->getText(), 'world'); + self::assertTrue($elements[2]->getFont() === null || !$elements[2]->getFont()->getStrikethrough()); + } } From 9d7fe28b04068d0573901710e5a95a04f7952b7b Mon Sep 17 00:00:00 2001 From: m7913d Date: Sat, 9 Nov 2024 18:37:26 +0100 Subject: [PATCH 08/16] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55151c66b8..0da305c50b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ and this project adheres to [Semantic Versioning](https://semver.org). - Changes to ROUNDDOWN/ROUNDUP/TRUNC. [Issue #4213](https://github.com/PHPOffice/PhpSpreadsheet/issues/4213) [PR #4214](https://github.com/PHPOffice/PhpSpreadsheet/pull/4214) - Writer Xlsx ignoredErrors Before Drawings. [Issue #4200](https://github.com/PHPOffice/PhpSpreadsheet/issues/4200) [Issue #4145](https://github.com/PHPOffice/PhpSpreadsheet/issues/4145) [PR #4212](https://github.com/PHPOffice/PhpSpreadsheet/pull/4212) - Allow ANCHORARRAY as Data Validation list. [Issue #4197](https://github.com/PHPOffice/PhpSpreadsheet/issues/4197) [PR #4203](https://github.com/PHPOffice/PhpSpreadsheet/pull/4203) +- Add support for `` tag when converting HTML to RichText. [Issue #4223](https://github.com/PHPOffice/PhpSpreadsheet/issues/4223) [PR #4224](https://github.com/PHPOffice/PhpSpreadsheet/pull/4224) ## 2024-09-29 - 3.3.0 (no 3.0.\*, 3.1.\*, 3.2.\*) From d1c1316517175293392d921318ab3178a92e6191 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sun, 10 Nov 2024 03:54:01 -0800 Subject: [PATCH 09/16] Start Changelog for 3.5.0 --- CHANGELOG.md | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1321822885..cd6c12ac01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,28 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com) and this project adheres to [Semantic Versioning](https://semver.org). +## TBD - 3.5.0 + +### Added + +- Nothing yet. + +### Changed + +- Nothing yet. + +### Moved + +- Nothing yet. + +### Deprecated + +- Nothing yet. + +### Fixed + +- Nothing yet. + ## 2024-11-10 - 3.4.0 ### Security Fix @@ -15,7 +37,7 @@ and this project adheres to [Semantic Versioning](https://semver.org). - Add Dynamic valueBinder Property to Spreadsheet and Readers. [Issue #1395](https://github.com/PHPOffice/PhpSpreadsheet/issues/1395) [PR #4185](https://github.com/PHPOffice/PhpSpreadsheet/pull/4185) - Allow Omitting Chart Border. [Issue #562](https://github.com/PHPOffice/PhpSpreadsheet/issues/562) [PR #4188](https://github.com/PHPOffice/PhpSpreadsheet/pull/4188) -- Method to Test Whether Csv Will Be Affected by Php0. [PR #4189](https://github.com/PHPOffice/PhpSpreadsheet/pull/4189) +- Method to Test Whether Csv Will Be Affected by Php9. [PR #4189](https://github.com/PHPOffice/PhpSpreadsheet/pull/4189) ### Changed From de6bcaae665f5964303f02f1402b4bb25e7ce884 Mon Sep 17 00:00:00 2001 From: m7913d Date: Sun, 10 Nov 2024 13:04:32 +0100 Subject: [PATCH 10/16] Assert font of parsed HTML is not null --- tests/PhpSpreadsheetTests/Helper/HtmlTest.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/PhpSpreadsheetTests/Helper/HtmlTest.php b/tests/PhpSpreadsheetTests/Helper/HtmlTest.php index 398adff597..26f22847a1 100644 --- a/tests/PhpSpreadsheetTests/Helper/HtmlTest.php +++ b/tests/PhpSpreadsheetTests/Helper/HtmlTest.php @@ -59,12 +59,15 @@ public function testSTag(): void self::assertSame(count($elements), 3); self::assertSame($elements[0]->getText(), 'Hello '); - self::assertTrue($elements[0]->getFont() === null || !$elements[0]->getFont()->getStrikethrough()); + self::assertNotNull($elements[0]->getFont()); + self::assertFalse($elements[0]->getFont()->getStrikethrough()); self::assertSame($elements[1]->getText(), 'test'); - self::assertTrue($elements[1]->getFont() !== null && $elements[1]->getFont()->getStrikethrough()); + self::assertNotNull($elements[1]->getFont()); + self::assertTrue($elements[1]->getFont()->getStrikethrough()); self::assertSame($elements[2]->getText(), 'world'); - self::assertTrue($elements[2]->getFont() === null || !$elements[2]->getFont()->getStrikethrough()); + self::assertNotNull($elements[2]->getFont()); + self::assertFalse($elements[2]->getFont()->getStrikethrough()); } } From 1ec119a2bc061200a110bf1792937006f44f76f0 Mon Sep 17 00:00:00 2001 From: m7913d Date: Sun, 10 Nov 2024 13:23:49 +0100 Subject: [PATCH 11/16] Fix `composer check` on Windows --- composer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 58da821636..6acb8333c5 100644 --- a/composer.json +++ b/composer.json @@ -45,12 +45,12 @@ ], "scripts": { "check": [ - "./bin/check-phpdoc-types", + "php ./bin/check-phpdoc-types", "phpcs samples/ src/ tests/ --report=checkstyle", "phpcs samples/ src/ tests/ --standard=PHPCompatibility --runtime-set testVersion 8.0- --exclude=PHPCompatibility.Variables.ForbiddenThisUseContexts -n", "php-cs-fixer fix --ansi --dry-run --diff", - "phpunit --color=always", - "phpstan analyse --ansi --memory-limit=2048M" + "phpstan analyse --ansi --memory-limit=2048M", + "phpunit --color=always" ], "style": [ "phpcs samples/ src/ tests/ --report=checkstyle", From 830baa14b259be800120bd349f064aeef9cf0977 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sun, 10 Nov 2024 10:55:42 -0800 Subject: [PATCH 12/16] Update "Contributing" Notes and One Github Action Replace one deprecated action, and update "How to release". --- .github/workflows/main.yml | 2 +- CONTRIBUTING.md | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4a9c4d22bf..e4c0da31ad 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -210,7 +210,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 67aa525c60..e144a11e21 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,6 +4,7 @@ If you would like to contribute, here are some notes and guidelines: - All new development should be on feature/fix branches, which are then merged to the `master` branch once stable and approved; so the `master` branch is always the most up-to-date, working code - If you are going to submit a pull request, please fork from `master`, and submit your pull request back as a fix/feature branch referencing the GitHub issue number + - Install (development) dependencies by running `composer install` inside your PhpSpreadsheet clone. - The code must work with all PHP versions that we support. - You can call `composer versions` to test version compatibility. - Code style should be maintained. @@ -39,7 +40,10 @@ This makes it easier to see exactly what is being tested when reviewing the PR. 2. Tag subject must be the version number, eg: `1.2.3` 3. Tag body must be a copy-paste of the changelog entries. 3. Push the tag with `git push --tags`, GitHub Actions will create a GitHub release automatically, and the release details will automatically be sent to packagist. -4. Github seems to remove markdown headings in the Release Notes, so you should edit to restore these. +4. By default, Github remove markdown headings in the Release Notes. You can either edit to restore these, or, probably preferably, change the default comment character on your system - `git config core.commentChar ';'`. -> **Note:** Tagged releases are made from the `master` branch. Only in an emergency should a tagged release be made from the `release` branch. (i.e. cherry-picked hot-fixes.) +> **Note:** Tagged releases are made from the `master` branch. Only in an emergency should a tagged release be made from the `release` branch. (i.e. cherry-picked hot-fixes.) However, there are 3 branches which have been updated to apply security patches, and those may be tagged if future security updates are needed. +- release1291 +- release210 +- release222 From e78e7ea9a7c3aadc985b858311b0b5f3f0d32767 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sun, 10 Nov 2024 15:41:01 -0800 Subject: [PATCH 13/16] Update CHANGELOG.md --- CHANGELOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bdac0d711f..fd987eae6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,7 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org). ### Fixed -- Nothing yet. +- Add support for `` tag when converting HTML to RichText. [Issue #4223](https://github.com/PHPOffice/PhpSpreadsheet/issues/4223) [PR #4224](https://github.com/PHPOffice/PhpSpreadsheet/pull/4224) ## 2024-11-10 - 3.4.0 @@ -66,7 +66,6 @@ and this project adheres to [Semantic Versioning](https://semver.org). - Changes to ROUNDDOWN/ROUNDUP/TRUNC. [Issue #4213](https://github.com/PHPOffice/PhpSpreadsheet/issues/4213) [PR #4214](https://github.com/PHPOffice/PhpSpreadsheet/pull/4214) - Writer Xlsx ignoredErrors Before Drawings. [Issue #4200](https://github.com/PHPOffice/PhpSpreadsheet/issues/4200) [Issue #4145](https://github.com/PHPOffice/PhpSpreadsheet/issues/4145) [PR #4212](https://github.com/PHPOffice/PhpSpreadsheet/pull/4212) - Allow ANCHORARRAY as Data Validation list. [Issue #4197](https://github.com/PHPOffice/PhpSpreadsheet/issues/4197) [PR #4203](https://github.com/PHPOffice/PhpSpreadsheet/pull/4203) -- Add support for `` tag when converting HTML to RichText. [Issue #4223](https://github.com/PHPOffice/PhpSpreadsheet/issues/4223) [PR #4224](https://github.com/PHPOffice/PhpSpreadsheet/pull/4224) ## 2024-09-29 - 3.3.0 (no 3.0.\*, 3.1.\*, 3.2.\*) From 1419febbba5afd298ad70871a2aa09ea382c4525 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Tue, 12 Nov 2024 06:53:22 -0800 Subject: [PATCH 14/16] Catch Phpstan Up Its last few updates have been irksome. I need to eliminate more of the new problems with annotations rather than code because I can't figure out what it's objecting to. Nevertheless, it provides a very valuable service, so ... Its latest release is flagging calling abs with a string, even when the string is defined as numeric-string in a doc block. Php generally allows it, though not with strict types. Changed to add 0 in those situations. --- composer.lock | 10 +++++----- src/PhpSpreadsheet/Calculation/Calculation.php | 6 +++--- .../Calculation/Engineering/BitWise.php | 2 +- .../Financial/CashFlow/Variable/NonPeriodic.php | 3 ++- src/PhpSpreadsheet/Reader/Xls.php | 2 +- .../Shared/OLE/ChainedBlockStream.php | 2 +- .../Shared/Trend/PolynomialBestFit.php | 2 +- src/PhpSpreadsheet/Style/NumberFormat/Formatter.php | 6 +++--- src/PhpSpreadsheet/Writer/Xls/Worksheet.php | 13 ++++++++----- 9 files changed, 25 insertions(+), 21 deletions(-) diff --git a/composer.lock b/composer.lock index aa0082c227..f25527b470 100644 --- a/composer.lock +++ b/composer.lock @@ -1789,16 +1789,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.12.0", + "version": "1.12.9", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "384af967d35b2162f69526c7276acadce534d0e1" + "reference": "ceb937fb39a92deabc02d20709cf14b2c452502c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/384af967d35b2162f69526c7276acadce534d0e1", - "reference": "384af967d35b2162f69526c7276acadce534d0e1", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/ceb937fb39a92deabc02d20709cf14b2c452502c", + "reference": "ceb937fb39a92deabc02d20709cf14b2c452502c", "shasum": "" }, "require": { @@ -1843,7 +1843,7 @@ "type": "github" } ], - "time": "2024-08-27T09:18:05+00:00" + "time": "2024-11-10T17:10:04+00:00" }, { "name": "phpstan/phpstan-phpunit", diff --git a/src/PhpSpreadsheet/Calculation/Calculation.php b/src/PhpSpreadsheet/Calculation/Calculation.php index 05b5ef52b8..0cc64c32e8 100644 --- a/src/PhpSpreadsheet/Calculation/Calculation.php +++ b/src/PhpSpreadsheet/Calculation/Calculation.php @@ -4149,9 +4149,9 @@ private function internalParseFormula(string $formula, ?Cell $cell = null): bool $expectedArgumentCountString = null; if (is_numeric($expectedArgumentCount)) { if ($expectedArgumentCount < 0) { - if ($argumentCount > abs($expectedArgumentCount)) { + if ($argumentCount > abs($expectedArgumentCount + 0)) { $argumentCountError = true; - $expectedArgumentCountString = 'no more than ' . abs($expectedArgumentCount); + $expectedArgumentCountString = 'no more than ' . abs($expectedArgumentCount + 0); } } else { if ($argumentCount != $expectedArgumentCount) { @@ -4236,7 +4236,7 @@ private function internalParseFormula(string $formula, ?Cell $cell = null): bool // do we now have a function/variable/number? $expectingOperator = true; $expectingOperand = false; - $val = $match[1] ?? ''; + $val = $match[1] ?? ''; //* @phpstan-ignore-line $length = strlen($val); if (preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/miu', $val, $matches)) { diff --git a/src/PhpSpreadsheet/Calculation/Engineering/BitWise.php b/src/PhpSpreadsheet/Calculation/Engineering/BitWise.php index c861c21a01..47d94eccfe 100644 --- a/src/PhpSpreadsheet/Calculation/Engineering/BitWise.php +++ b/src/PhpSpreadsheet/Calculation/Engineering/BitWise.php @@ -221,7 +221,7 @@ private static function validateShiftAmount(mixed $value): int $value = self::nullFalseTrueToNumber($value); if (is_numeric($value)) { - if (abs($value) > 53) { + if (abs($value + 0) > 53) { throw new Exception(ExcelError::NAN()); } diff --git a/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php b/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php index 8c6f615b87..15f9e8957b 100644 --- a/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php +++ b/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php @@ -239,6 +239,7 @@ private static function xirrBisection(array $values, array $dates, float $x1, fl return $rslt; } + /** @param array $values> */ private static function xnpvOrdered(mixed $rate, mixed $values, mixed $dates, bool $ordered = true, bool $capAtNegative1 = false): float|string { $rate = Functions::flattenSingleValue($rate); @@ -276,7 +277,7 @@ private static function xnpvOrdered(mixed $rate, mixed $values, mixed $dates, bo return $dif; } if ($rate <= -1.0) { - $xnpv += -abs($values[$i]) / (-1 - $rate) ** ($dif / 365); + $xnpv += -abs($values[$i] + 0) / (-1 - $rate) ** ($dif / 365); } else { $xnpv += $values[$i] / (1 + $rate) ** ($dif / 365); } diff --git a/src/PhpSpreadsheet/Reader/Xls.php b/src/PhpSpreadsheet/Reader/Xls.php index 5883b20dad..7fa2bbf4a4 100644 --- a/src/PhpSpreadsheet/Reader/Xls.php +++ b/src/PhpSpreadsheet/Reader/Xls.php @@ -234,7 +234,7 @@ class Xls extends XlsBase * The current MD5 context state. * It is never set in the program, so code which uses it is suspect. */ - private string $md5Ctxt; // @phpstan-ignore-line + private string $md5Ctxt = ''; protected int $textObjRef; diff --git a/src/PhpSpreadsheet/Shared/OLE/ChainedBlockStream.php b/src/PhpSpreadsheet/Shared/OLE/ChainedBlockStream.php index 61bd6acb01..52102161f2 100644 --- a/src/PhpSpreadsheet/Shared/OLE/ChainedBlockStream.php +++ b/src/PhpSpreadsheet/Shared/OLE/ChainedBlockStream.php @@ -85,7 +85,7 @@ public function stream_open(string $path, string $mode, int $options, ?string &$ } } if (isset($this->params['size'])) { - $this->data = substr($this->data, 0, $this->params['size']); + $this->data = substr($this->data, 0, $this->params['size']); //* @phpstan-ignore-line } if ($options & STREAM_USE_PATH) { diff --git a/src/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php b/src/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php index 911a9c34f3..188c2cedb5 100644 --- a/src/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php +++ b/src/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php @@ -159,7 +159,7 @@ private function polynomialRegression(int $order, array $yValues, array $xValues $coefficients = []; for ($i = 0; $i < $C->rows; ++$i) { $r = $C->getValue($i + 1, 1); // row and column are origin-1 - if (!is_numeric($r) || abs($r) <= 10 ** (-9)) { + if (!is_numeric($r) || abs($r + 0) <= 10 ** (-9)) { $r = 0; } else { $r += 0; diff --git a/src/PhpSpreadsheet/Style/NumberFormat/Formatter.php b/src/PhpSpreadsheet/Style/NumberFormat/Formatter.php index 64db544fe4..8ae33354ae 100644 --- a/src/PhpSpreadsheet/Style/NumberFormat/Formatter.php +++ b/src/PhpSpreadsheet/Style/NumberFormat/Formatter.php @@ -42,7 +42,7 @@ private static function splitFormatComparison( }; } - /** @param float|int|string $value value to be formatted */ + /** @param float|int|numeric-string $value value to be formatted */ private static function splitFormatForSectionSelection(array $sections, mixed $value): array { // Extract the relevant section depending on whether number is positive, negative, or zero? @@ -79,7 +79,7 @@ private static function splitFormatForSectionSelection(array $sections, mixed $v $absval = $value; switch ($sectionCount) { case 2: - $absval = abs($value); + $absval = abs($value + 0); if (!self::splitFormatComparison($value, $conditionOperations[0], $conditionComparisonValues[0], '>=', 0)) { $color = $colors[1]; $format = $sections[1]; @@ -88,7 +88,7 @@ private static function splitFormatForSectionSelection(array $sections, mixed $v break; case 3: case 4: - $absval = abs($value); + $absval = abs($value + 0); if (!self::splitFormatComparison($value, $conditionOperations[0], $conditionComparisonValues[0], '>', 0)) { if (self::splitFormatComparison($value, $conditionOperations[1], $conditionComparisonValues[1], '<', 0)) { $color = $colors[1]; diff --git a/src/PhpSpreadsheet/Writer/Xls/Worksheet.php b/src/PhpSpreadsheet/Writer/Xls/Worksheet.php index 94134c6913..5258bbad38 100644 --- a/src/PhpSpreadsheet/Writer/Xls/Worksheet.php +++ b/src/PhpSpreadsheet/Writer/Xls/Worksheet.php @@ -2362,7 +2362,7 @@ private function writeObjPicture(int $colL, int $dxL, int $rwT, int|float $dyT, * * @param GdImage $image The image to process * - * @return array Array with data and properties of the bitmap + * @return array{0: float, 1: float, 2: int, 3: string} Data and properties of the bitmap */ public function processBitmapGd(GdImage $image): array { @@ -2372,9 +2372,9 @@ public function processBitmapGd(GdImage $image): array $data = pack('Vvvvv', 0x000C, $width, $height, 0x01, 0x18); for ($j = $height; --$j;) { for ($i = 0; $i < $width; ++$i) { - /** @phpstan-ignore-next-line */ - $color = imagecolorsforindex($image, imagecolorat($image, $i, $j)); - if ($color !== false) { + $colorAt = imagecolorat($image, $i, $j); + if ($colorAt !== false) { + $color = imagecolorsforindex($image, $colorAt); foreach (['red', 'green', 'blue'] as $key) { $color[$key] = $color[$key] + (int) round((255 - $color[$key]) * $color['alpha'] / 127); } @@ -2385,8 +2385,11 @@ public function processBitmapGd(GdImage $image): array $data .= str_repeat("\x00", 4 - 3 * $width % 4); } } + // Phpstan says this always throws an exception before getting here. + // I don't see why, but I think this is code is never exercised + // in unit tests, so I can't say for sure it's wrong. - return [$width, $height, strlen($data), $data]; + return [$width, $height, strlen($data), $data]; //* @phpstan-ignore-line } /** From 889bdeb465711f87bc9a4a4e167d13cdfed46dd2 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Wed, 13 Nov 2024 15:37:55 -0800 Subject: [PATCH 15/16] Update CHANGELOG.md --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd987eae6c..6e77664216 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,11 +21,12 @@ and this project adheres to [Semantic Versioning](https://semver.org). ### Deprecated -- Nothing yet. +- Worksheet::getHashCode is no longer needed. ### Fixed - Add support for `` tag when converting HTML to RichText. [Issue #4223](https://github.com/PHPOffice/PhpSpreadsheet/issues/4223) [PR #4224](https://github.com/PHPOffice/PhpSpreadsheet/pull/4224) +- Change hash code for worksheet. [Issue #4192](https://github.com/PHPOffice/PhpSpreadsheet/issues/4192) [PR #4207](https://github.com/PHPOffice/PhpSpreadsheet/pull/4207) ## 2024-11-10 - 3.4.0 From 9398f741878a2a24da0f8124a7217595a676295a Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Thu, 14 Nov 2024 14:42:10 -0800 Subject: [PATCH 16/16] Upgrade PhpUnit To a version that knows Php8.4 deprecates E_STRICT. --- composer.lock | 86 +++++++++++++++++++++++++-------------------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/composer.lock b/composer.lock index aa0082c227..fb6852f09c 100644 --- a/composer.lock +++ b/composer.lock @@ -1441,16 +1441,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.12.0", + "version": "1.12.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c" + "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", - "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845", + "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845", "shasum": "" }, "require": { @@ -1489,7 +1489,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.12.0" + "source": "https://github.com/myclabs/DeepCopy/tree/1.12.1" }, "funding": [ { @@ -1497,20 +1497,20 @@ "type": "tidelift" } ], - "time": "2024-06-12T14:39:25+00:00" + "time": "2024-11-08T17:47:46+00:00" }, { "name": "nikic/php-parser", - "version": "v5.1.0", + "version": "v5.3.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1" + "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/683130c2ff8c2739f4822ff7ac5c873ec529abd1", - "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/8eea230464783aa9671db8eea6f8c6ac5285794b", + "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b", "shasum": "" }, "require": { @@ -1553,9 +1553,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.1.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.1" }, - "time": "2024-07-01T20:03:41+00:00" + "time": "2024-10-08T18:51:32+00:00" }, { "name": "paragonie/random_compat", @@ -1899,32 +1899,32 @@ }, { "name": "phpunit/php-code-coverage", - "version": "10.1.15", + "version": "10.1.16", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "5da8b1728acd1e6ffdf2ff32ffbdfd04307f26ae" + "reference": "7e308268858ed6baedc8704a304727d20bc07c77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/5da8b1728acd1e6ffdf2ff32ffbdfd04307f26ae", - "reference": "5da8b1728acd1e6ffdf2ff32ffbdfd04307f26ae", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e308268858ed6baedc8704a304727d20bc07c77", + "reference": "7e308268858ed6baedc8704a304727d20bc07c77", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.18 || ^5.0", + "nikic/php-parser": "^4.19.1 || ^5.1.0", "php": ">=8.1", - "phpunit/php-file-iterator": "^4.0", - "phpunit/php-text-template": "^3.0", - "sebastian/code-unit-reverse-lookup": "^3.0", - "sebastian/complexity": "^3.0", - "sebastian/environment": "^6.0", - "sebastian/lines-of-code": "^2.0", - "sebastian/version": "^4.0", - "theseer/tokenizer": "^1.2.0" + "phpunit/php-file-iterator": "^4.1.0", + "phpunit/php-text-template": "^3.0.1", + "sebastian/code-unit-reverse-lookup": "^3.0.0", + "sebastian/complexity": "^3.2.0", + "sebastian/environment": "^6.1.0", + "sebastian/lines-of-code": "^2.0.2", + "sebastian/version": "^4.0.1", + "theseer/tokenizer": "^1.2.3" }, "require-dev": { "phpunit/phpunit": "^10.1" @@ -1936,7 +1936,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "10.1-dev" + "dev-main": "10.1.x-dev" } }, "autoload": { @@ -1965,7 +1965,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.15" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.16" }, "funding": [ { @@ -1973,7 +1973,7 @@ "type": "github" } ], - "time": "2024-06-29T08:25:15+00:00" + "time": "2024-08-22T04:31:57+00:00" }, { "name": "phpunit/php-file-iterator", @@ -2220,16 +2220,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.5.29", + "version": "10.5.38", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "8e9e80872b4e8064401788ee8a32d40b4455318f" + "reference": "a86773b9e887a67bc53efa9da9ad6e3f2498c132" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/8e9e80872b4e8064401788ee8a32d40b4455318f", - "reference": "8e9e80872b4e8064401788ee8a32d40b4455318f", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a86773b9e887a67bc53efa9da9ad6e3f2498c132", + "reference": "a86773b9e887a67bc53efa9da9ad6e3f2498c132", "shasum": "" }, "require": { @@ -2243,14 +2243,14 @@ "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.1", - "phpunit/php-code-coverage": "^10.1.15", + "phpunit/php-code-coverage": "^10.1.16", "phpunit/php-file-iterator": "^4.1.0", "phpunit/php-invoker": "^4.0.0", "phpunit/php-text-template": "^3.0.1", "phpunit/php-timer": "^6.0.0", "sebastian/cli-parser": "^2.0.1", "sebastian/code-unit": "^2.0.0", - "sebastian/comparator": "^5.0.1", + "sebastian/comparator": "^5.0.3", "sebastian/diff": "^5.1.1", "sebastian/environment": "^6.1.0", "sebastian/exporter": "^5.1.2", @@ -2301,7 +2301,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.29" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.38" }, "funding": [ { @@ -2317,7 +2317,7 @@ "type": "tidelift" } ], - "time": "2024-07-30T11:08:00+00:00" + "time": "2024-10-28T13:06:21+00:00" }, { "name": "psr/container", @@ -3237,16 +3237,16 @@ }, { "name": "sebastian/comparator", - "version": "5.0.1", + "version": "5.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "2db5010a484d53ebf536087a70b4a5423c102372" + "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2db5010a484d53ebf536087a70b4a5423c102372", - "reference": "2db5010a484d53ebf536087a70b4a5423c102372", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e", + "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e", "shasum": "" }, "require": { @@ -3257,7 +3257,7 @@ "sebastian/exporter": "^5.0" }, "require-dev": { - "phpunit/phpunit": "^10.3" + "phpunit/phpunit": "^10.5" }, "type": "library", "extra": { @@ -3302,7 +3302,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.1" + "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.3" }, "funding": [ { @@ -3310,7 +3310,7 @@ "type": "github" } ], - "time": "2023-08-14T13:18:12+00:00" + "time": "2024-10-18T14:56:07+00:00" }, { "name": "sebastian/complexity",