From 1c333d1f3d74db1634da3a5855039f9deb4c9a8a Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Mon, 15 Jul 2024 22:17:09 -0700 Subject: [PATCH 01/15] Reference to Defined Name Specifying Worksheet Name Fix #296, another entry in our magical history tour (closed as stale in 2018). Excel allows you to use a name defined on another worksheet by prefixing the sheet name, even when the scope of the defined name is its worksheet rather than the entire workbook. --- .../Calculation/Calculation.php | 9 ++-- tests/PhpSpreadsheetTests/NamedRange3Test.php | 52 +++++++++++++++++++ 2 files changed, 57 insertions(+), 4 deletions(-) create mode 100644 tests/PhpSpreadsheetTests/NamedRange3Test.php diff --git a/src/PhpSpreadsheet/Calculation/Calculation.php b/src/PhpSpreadsheet/Calculation/Calculation.php index 2f13a1634..883973cbd 100644 --- a/src/PhpSpreadsheet/Calculation/Calculation.php +++ b/src/PhpSpreadsheet/Calculation/Calculation.php @@ -5074,9 +5074,10 @@ class Calculation if ($cell === null || $pCellWorksheet === null) { return $this->raiseFormulaError("undefined name '$token'"); } + $specifiedWorksheet = trim($matches[2], "'"); $this->debugLog->writeDebugLog('Evaluating Defined Name %s', $definedName); - $namedRange = DefinedName::resolveName($definedName, $pCellWorksheet); + $namedRange = DefinedName::resolveName($definedName, $pCellWorksheet, $specifiedWorksheet); // If not Defined Name, try as Table. if ($namedRange === null && $this->spreadsheet !== null) { $table = $this->spreadsheet->getTableByName($definedName); @@ -5101,7 +5102,7 @@ class Calculation return $this->raiseFormulaError("undefined name '$definedName'"); } - $result = $this->evaluateDefinedName($cell, $namedRange, $pCellWorksheet, $stack); + $result = $this->evaluateDefinedName($cell, $namedRange, $pCellWorksheet, $stack, $specifiedWorksheet !== ''); if (isset($storeKey)) { $branchStore[$storeKey] = $result; } @@ -5580,10 +5581,10 @@ class Calculation return $args; } - private function evaluateDefinedName(Cell $cell, DefinedName $namedRange, Worksheet $cellWorksheet, Stack $stack): mixed + private function evaluateDefinedName(Cell $cell, DefinedName $namedRange, Worksheet $cellWorksheet, Stack $stack, bool $ignoreScope = false): mixed { $definedNameScope = $namedRange->getScope(); - if ($definedNameScope !== null && $definedNameScope !== $cellWorksheet) { + if ($definedNameScope !== null && $definedNameScope !== $cellWorksheet && !$ignoreScope) { // The defined name isn't in our current scope, so #REF $result = ExcelError::REF(); $stack->push('Error', $result, $namedRange->getName()); diff --git a/tests/PhpSpreadsheetTests/NamedRange3Test.php b/tests/PhpSpreadsheetTests/NamedRange3Test.php new file mode 100644 index 000000000..c25dec561 --- /dev/null +++ b/tests/PhpSpreadsheetTests/NamedRange3Test.php @@ -0,0 +1,52 @@ +getActiveSheet(); + $sheet1->setTitle('sheet1'); + $sheet1->setCellValue('B1', 100); + $sheet1->setCellValue('B2', 200); + $sheet1->setCellValue('B3', 300); + $sheet1->setCellValue('B4', 400); + $sheet1->setCellValue('B5', 500); + + $sheet2 = $spreadsheet->createsheet(); + $sheet2->setTitle('sheet2'); + $sheet2->setCellValue('A1', 10); + $sheet2->setCellValue('A2', 20); + $sheet2->setCellValue('A3', 30); + $sheet2->setCellValue('A4', 40); + $sheet2->setCellValue('A5', 50); + + $spreadsheet->addNamedRange( + new NamedRange('somecells', $sheet2, '$A$1:$A$5', true) + ); + $spreadsheet->addNamedRange( + new NamedRange('cellsonsheet1', $sheet1, '$B$1:$B$5') + ); + + $sheet1->getCell('G1')->setValue('=SUM(cellsonsheet1)'); + self::assertSame(1500, $sheet1->getCell('G1')->getCalculatedValue()); + $sheet1->getCell('G2')->setValue('=SUM(sheet2!somecells)'); + self::assertSame(150, $sheet1->getCell('G2')->getCalculatedValue()); + $sheet1->getCell('G3')->setValue('=SUM(somecells)'); + self::assertSame('#NAME?', $sheet1->getCell('G3')->getCalculatedValue()); + $sheet1->getCell('G4')->setValue('=SUM(sheet2!cellsonsheet1)'); + self::assertSame(1500, $sheet1->getCell('G4')->getCalculatedValue()); + $sheet1->getCell('G5')->setValue('=SUM(sheet2xxx!cellsonsheet1)'); + self::assertSame('#NAME?', $sheet1->getCell('G5')->getCalculatedValue()); + + $spreadsheet->disconnectWorksheets(); + } +} From 2a0090b9156d8e0eb4cba38fd1c88c3fb68531bb Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Wed, 17 Jul 2024 09:28:35 -0700 Subject: [PATCH 02/15] Xlsx Reader and Print/Show Gridlines Fix #912, opened in Feb. 2019, and closed as stale in Apr. 2019, and which I have re-opened to be closed properly by this PR. Another "better late than never". Original issue says that print options should not affect ShowGridlines, which seems true enough. Aside from that, the existing code isn't quite correct anyhow. Excel looks for 2 attributes, one of which must be explicitly set to true and the other of which must not be explicitly set to false, in order to determine whether PrintGridlines should be set. PhpSpreadsheet is changed to do the same. This could be treated as a BC break for the unusual situation described in the issue, but it seems more like a bug fix to me. --- .../Reader/Xlsx/SheetViewOptions.php | 9 ++-- .../Reader/Xlsx/GridlinesTest.php | 49 +++++++++++++++++++ 2 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 tests/PhpSpreadsheetTests/Reader/Xlsx/GridlinesTest.php diff --git a/src/PhpSpreadsheet/Reader/Xlsx/SheetViewOptions.php b/src/PhpSpreadsheet/Reader/Xlsx/SheetViewOptions.php index 136b92fed..9d71443f4 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/SheetViewOptions.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/SheetViewOptions.php @@ -122,11 +122,12 @@ class SheetViewOptions extends BaseParserClass private function printOptions(SimpleXMLElement $printOptionsx): void { $printOptions = $printOptionsx->attributes() ?? []; - if (isset($printOptions['gridLinesSet']) && self::boolean((string) $printOptions['gridLinesSet'])) { - $this->worksheet->setShowGridlines(true); - } + // Spec is weird. gridLines (default false) + // and gridLinesSet (default true) must both be true. if (isset($printOptions['gridLines']) && self::boolean((string) $printOptions['gridLines'])) { - $this->worksheet->setPrintGridlines(true); + if (!isset($printOptions['gridLinesSet']) || self::boolean((string) $printOptions['gridLinesSet'])) { + $this->worksheet->setPrintGridlines(true); + } } if (isset($printOptions['horizontalCentered']) && self::boolean((string) $printOptions['horizontalCentered'])) { $this->worksheet->getPageSetup()->setHorizontalCentered(true); diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/GridlinesTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/GridlinesTest.php new file mode 100644 index 000000000..15192711a --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/GridlinesTest.php @@ -0,0 +1,49 @@ +getActiveSheet(); + $sheet2 = $spreadsheet->createSheet(); + $sheet1->setShowGridlines($display); + $sheet1->setPrintGridlines($print); + $sheet1->fromArray( + [ + [1, 2, 3], + [4, 5, 6], + [7, 8, 9], + ] + ); + $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx'); + $spreadsheet->disconnectWorksheets(); + $rsheet1 = $reloadedSpreadsheet->getSheet(0); + $rsheet2 = $reloadedSpreadsheet->getSheet(1); + self::assertSame($display, $rsheet1->getShowGridlines()); + self::assertSame($print, $rsheet1->getPrintGridlines()); + self::assertTrue($rsheet2->getShowGridlines()); + self::assertFalse($rsheet2->getPrintGridlines()); + $reloadedSpreadsheet->disconnectWorksheets(); + } + + public static function loadDataProvider(): array + { + return [ + [true, true], + [true, false], + [false, true], + [false, false], + ]; + } +} From f632732564897411be42b650d63f20b7baa19946 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Wed, 17 Jul 2024 10:45:56 -0700 Subject: [PATCH 03/15] Scrutinizer Busy Work --- tests/PhpSpreadsheetTests/Reader/Xlsx/GridlinesTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/GridlinesTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/GridlinesTest.php index 15192711a..5387ea738 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xlsx/GridlinesTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/GridlinesTest.php @@ -17,6 +17,7 @@ class GridlinesTest extends AbstractFunctional $spreadsheet = new Spreadsheet(); $sheet1 = $spreadsheet->getActiveSheet(); $sheet2 = $spreadsheet->createSheet(); + $sheet2->setTitle('deliberatelyblank'); $sheet1->setShowGridlines($display); $sheet1->setPrintGridlines($print); $sheet1->fromArray( From 2952cf5526959b47abf9ccd953fa9fc093a276d3 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Thu, 18 Jul 2024 13:30:58 -0700 Subject: [PATCH 04/15] Ods Reader Allow Omission of Some Page Settings Tags Fix #4099. Ods Reader was expecting there to always be `header-style` and `footer-style` tags when `page-layout` tag is present, but these need not exist. It seemed like there might be other exposures along this line in `readPageSettingStyles`; rather than waiting for a problem report to show up for each, the code is updated to use `->item(0)` in place of `[0]` when appropriate, and make use of the nullsafe `?->` operator introduced with Php8. --- .../Reader/Ods/PageSettings.php | 30 +++++++++--------- .../Reader/Ods/Issue4099Test.php | 28 ++++++++++++++++ tests/data/Reader/Ods/issue.4099.ods | Bin 0 -> 8887 bytes 3 files changed, 43 insertions(+), 15 deletions(-) create mode 100644 tests/PhpSpreadsheetTests/Reader/Ods/Issue4099Test.php create mode 100644 tests/data/Reader/Ods/issue.4099.ods diff --git a/src/PhpSpreadsheet/Reader/Ods/PageSettings.php b/src/PhpSpreadsheet/Reader/Ods/PageSettings.php index f3e956c91..fd35de5b9 100644 --- a/src/PhpSpreadsheet/Reader/Ods/PageSettings.php +++ b/src/PhpSpreadsheet/Reader/Ods/PageSettings.php @@ -55,22 +55,22 @@ class PageSettings foreach ($styles as $styleSet) { $styleName = $styleSet->getAttributeNS($this->stylesNs, 'name'); - $pageLayoutProperties = $styleSet->getElementsByTagNameNS($this->stylesNs, 'page-layout-properties')[0]; - $styleOrientation = $pageLayoutProperties->getAttributeNS($this->stylesNs, 'print-orientation'); - $styleScale = $pageLayoutProperties->getAttributeNS($this->stylesNs, 'scale-to'); - $stylePrintOrder = $pageLayoutProperties->getAttributeNS($this->stylesNs, 'print-page-order'); - $centered = $pageLayoutProperties->getAttributeNS($this->stylesNs, 'table-centering'); + $pageLayoutProperties = $styleSet->getElementsByTagNameNS($this->stylesNs, 'page-layout-properties')->item(0); + $styleOrientation = $pageLayoutProperties?->getAttributeNS($this->stylesNs, 'print-orientation'); + $styleScale = $pageLayoutProperties?->getAttributeNS($this->stylesNs, 'scale-to'); + $stylePrintOrder = $pageLayoutProperties?->getAttributeNS($this->stylesNs, 'print-page-order'); + $centered = $pageLayoutProperties?->getAttributeNS($this->stylesNs, 'table-centering'); - $marginLeft = $pageLayoutProperties->getAttributeNS($this->stylesFo, 'margin-left'); - $marginRight = $pageLayoutProperties->getAttributeNS($this->stylesFo, 'margin-right'); - $marginTop = $pageLayoutProperties->getAttributeNS($this->stylesFo, 'margin-top'); - $marginBottom = $pageLayoutProperties->getAttributeNS($this->stylesFo, 'margin-bottom'); - $header = $styleSet->getElementsByTagNameNS($this->stylesNs, 'header-style')[0]; - $headerProperties = $header->getElementsByTagNameNS($this->stylesNs, 'header-footer-properties')[0]; - $marginHeader = isset($headerProperties) ? $headerProperties->getAttributeNS($this->stylesFo, 'min-height') : null; - $footer = $styleSet->getElementsByTagNameNS($this->stylesNs, 'footer-style')[0]; - $footerProperties = $footer->getElementsByTagNameNS($this->stylesNs, 'header-footer-properties')[0]; - $marginFooter = isset($footerProperties) ? $footerProperties->getAttributeNS($this->stylesFo, 'min-height') : null; + $marginLeft = $pageLayoutProperties?->getAttributeNS($this->stylesFo, 'margin-left'); + $marginRight = $pageLayoutProperties?->getAttributeNS($this->stylesFo, 'margin-right'); + $marginTop = $pageLayoutProperties?->getAttributeNS($this->stylesFo, 'margin-top'); + $marginBottom = $pageLayoutProperties?->getAttributeNS($this->stylesFo, 'margin-bottom'); + $header = $styleSet->getElementsByTagNameNS($this->stylesNs, 'header-style')->item(0); + $headerProperties = $header?->getElementsByTagNameNS($this->stylesNs, 'header-footer-properties')?->item(0); + $marginHeader = $headerProperties?->getAttributeNS($this->stylesFo, 'min-height'); + $footer = $styleSet->getElementsByTagNameNS($this->stylesNs, 'footer-style')->item(0); + $footerProperties = $footer?->getElementsByTagNameNS($this->stylesNs, 'header-footer-properties')?->item(0); + $marginFooter = $footerProperties?->getAttributeNS($this->stylesFo, 'min-height'); $this->pageLayoutStyles[$styleName] = (object) [ 'orientation' => $styleOrientation ?: PageSetup::ORIENTATION_DEFAULT, diff --git a/tests/PhpSpreadsheetTests/Reader/Ods/Issue4099Test.php b/tests/PhpSpreadsheetTests/Reader/Ods/Issue4099Test.php new file mode 100644 index 000000000..2b38181a6 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Ods/Issue4099Test.php @@ -0,0 +1,28 @@ +file . '#styles.xml'; + $contents = (string) file_get_contents($zipFile); + self::assertStringContainsString('page-layout ', $contents); + self::assertStringNotContainsString('header-style', $contents); + self::assertStringNotContainsString('footer-style', $contents); + $reader = new OdsReader(); + $spreadsheet = $reader->load($this->file); + $sheet = $spreadsheet->getActiveSheet(); + self::assertSame('FirstCell', $sheet->getCell('A1')->getValue()); + $spreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/data/Reader/Ods/issue.4099.ods b/tests/data/Reader/Ods/issue.4099.ods new file mode 100644 index 0000000000000000000000000000000000000000..26b17ecbd3f1b1ef4331515747f54d667161c5b5 GIT binary patch literal 8887 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 literal 0 HcmV?d00001 From 09584d29501cb909de758ed1a3d881154506d5ec Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sun, 21 Jul 2024 19:48:40 -0700 Subject: [PATCH 05/15] Html Reader Preserve Unicode Whitespace Characters Fix #1284, which was closed as stale in 2019, but which I will now reopen. Html Reader converts *Unicode* whitespace characters in a DOM text node to space. However, Html treats only space, tab, CR, LF, vertical tab, and form-feed as whitespace. Using a regular expression with the `u` (Unicode) modifier causes a number of other characters to be converted to space inappropriately. The issue mentions "ideographic space" in particular, stating that it is used for formatting and should be preserved. "Non-breaking space" is also used in the same way and should also be preserved. An exception is made for a text node consisting of a single non-breaking space, since that is used as a placeholder by Html Writer; my own guess is that this is the reason why the Unicode modifier was used in the first place. --- src/PhpSpreadsheet/Reader/Html.php | 5 ++- .../Reader/Html/Issue1284Test.php | 36 +++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 tests/PhpSpreadsheetTests/Reader/Html/Issue1284Test.php diff --git a/src/PhpSpreadsheet/Reader/Html.php b/src/PhpSpreadsheet/Reader/Html.php index cc4a4859a..2a2ffc410 100644 --- a/src/PhpSpreadsheet/Reader/Html.php +++ b/src/PhpSpreadsheet/Reader/Html.php @@ -627,7 +627,10 @@ class Html extends BaseReader { foreach ($element->childNodes as $child) { if ($child instanceof DOMText) { - $domText = (string) preg_replace('/\s+/u', ' ', trim($child->nodeValue ?? '')); + $domText = (string) preg_replace('/\s+/', ' ', trim($child->nodeValue ?? '')); + if ($domText === "\u{a0}") { + $domText = ''; + } if (is_string($cellContent)) { // simply append the text if the cell content is a plain text string $cellContent .= $domText; diff --git a/tests/PhpSpreadsheetTests/Reader/Html/Issue1284Test.php b/tests/PhpSpreadsheetTests/Reader/Html/Issue1284Test.php new file mode 100644 index 000000000..6faee356d --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Html/Issue1284Test.php @@ -0,0 +1,36 @@ +getActiveSheet(); + $osheet->getCell('A1')->setValue('# item 1'); + $osheet->getCell('A2')->setValue("$ideographicSpace# item 2"); + $osheet->getCell('A3')->setValue("$ideographicSpace$ideographicSpace# item 3"); + $osheet->getCell('A4')->setValue("$nbsp# item\t4"); + $osheet->getCell('A5')->setValue("$nbsp$nbsp# item 5"); + + $spreadsheet = $this->writeAndReload($spreadsheetOld, 'Html'); + $spreadsheetOld->disconnectWorksheets(); + + $sheet = $spreadsheet->getActiveSheet(); + self::assertSame('# item 1', $sheet->getCell('A1')->getValue(), 'nothing changed'); + self::assertSame("$ideographicSpace# item 2", $sheet->getCell('A2')->getValue(), 'nothing changed including 1 ideographic space'); + self::assertSame("$ideographicSpace$ideographicSpace# item 3", $sheet->getCell('A3')->getValue(), 'nothing changed including 2 ideographic spaces'); + self::assertSame("$nbsp# item 4", $sheet->getCell('A4')->getValue(), 'nbsp unchanged, 2 spaces reduced to 1, tab changed to space'); + self::assertSame("$nbsp$nbsp# item 5", $sheet->getCell('A5')->getValue(), 'many spaces reduced to 1'); + + $spreadsheet->disconnectWorksheets(); + } +} From b6ff857ba794311069a34694fbd49ed2b3b30d84 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Mon, 22 Jul 2024 08:32:54 -0700 Subject: [PATCH 06/15] RATE Function Permits Floating Point NPER Suggested by PR #848 from @markkimsal. The RATE calculation had already been corrected, so that part of the PR was unnecessary, however one of the tests included a floating point value for Number of Periods, which Excel permits. PhpSpreadsheet till now expected that parameter to be an integer. This is trivially changed with some tests added. --- .../CashFlow/Constant/Periodic/Interest.php | 4 ++-- tests/data/Calculation/Financial/RATE.php | 12 ++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Interest.php b/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Interest.php index 833c5734e..ad68ec133 100644 --- a/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Interest.php +++ b/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Interest.php @@ -165,7 +165,7 @@ class Interest $guess = ($guess === null) ? 0.1 : Functions::flattenSingleValue($guess); try { - $numberOfPeriods = CashFlowValidations::validateInt($numberOfPeriods); + $numberOfPeriods = CashFlowValidations::validateFloat($numberOfPeriods); $payment = CashFlowValidations::validateFloat($payment); $presentValue = CashFlowValidations::validatePresentValue($presentValue); $futureValue = CashFlowValidations::validateFutureValue($futureValue); @@ -193,7 +193,7 @@ class Interest return $close ? $rate : ExcelError::NAN(); } - private static function rateNextGuess(float $rate, int $numberOfPeriods, float $payment, float $presentValue, float $futureValue, int $type): string|float + private static function rateNextGuess(float $rate, float $numberOfPeriods, float $payment, float $presentValue, float $futureValue, int $type): string|float { if ($rate == 0.0) { return ExcelError::NAN(); diff --git a/tests/data/Calculation/Financial/RATE.php b/tests/data/Calculation/Financial/RATE.php index f5a5ab492..bea4ce9a1 100644 --- a/tests/data/Calculation/Financial/RATE.php +++ b/tests/data/Calculation/Financial/RATE.php @@ -29,6 +29,18 @@ return [ -250, 5000, ], + [ + 0.017929869399484, + 24.99, + -250, + 5000, + ], + [ + 0.016581855, + 24.5, + -250, + 5000, + ], [ 0.016550119066711999, 24, From 10823ee00ab958c1149280a5f3284ab38a3669c7 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Wed, 24 Jul 2024 01:15:11 -0700 Subject: [PATCH 07/15] Changelog Prep for 2.2.0 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1241d00ed..c731b8663 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ 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 - 2.2.0 +## 2024-07-24 - 2.2.0 ### Added From b86629ff58eb8c9294da0e4647bfaaf5dca58784 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Wed, 24 Jul 2024 07:08:44 -0700 Subject: [PATCH 08/15] Prepare Changelog For Next Release --- CHANGELOG.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c731b8663..cd609aed9 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.0.0 + +### Added + +- Nothing + +### Changed + +- Nothing + +### Deprecated + +- Nothing + +### Moved + +- Nothing + +### Fixed + +- Nothing + ## 2024-07-24 - 2.2.0 ### Added From 762d73daf5b4e3072aa5a56f5a42bb0b0bba78b9 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Fri, 26 Jul 2024 08:30:52 -0700 Subject: [PATCH 09/15] Addsheet May Leave Active Sheet Uninitialized Fix #4112. Direct cause is that `applyStylesFromArray` tries to save and restore `activeSheetIndex`. However, if activeSheetIndex is -1, indicating no active sheet, the restore should not be attempted. Code is changed to test before attempting to restore. The actual problem, however, is that user specified a sheet number for `addSheet`. That method will set activeSheetIndex most of the time, but this was a gap - when the supplied sheet number (0 in this case) is greater than activeSheetIndex (-1 in this case), it was leaving activeSheetIndex as -1. It is changed to set activeSheetIndex to 0 when activeSheetIndex is negative. --- CHANGELOG.md | 2 +- src/PhpSpreadsheet/Spreadsheet.php | 3 ++ src/PhpSpreadsheet/Worksheet/Worksheet.php | 4 +- .../Worksheet/Issue4112Test.php | 43 +++++++++++++++++++ 4 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 tests/PhpSpreadsheetTests/Worksheet/Issue4112Test.php diff --git a/CHANGELOG.md b/CHANGELOG.md index cd609aed9..e645df82e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,7 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org). ### Fixed -- Nothing +- Add Sheet may leave Active Sheet uninitialized. [Issue #4112](https://github.com/PHPOffice/PhpSpreadsheet/issues/4112) [PR #4113](https://github.com/PHPOffice/PhpSpreadsheet/pull/4113) ## 2024-07-24 - 2.2.0 diff --git a/src/PhpSpreadsheet/Spreadsheet.php b/src/PhpSpreadsheet/Spreadsheet.php index e571cc4f6..bcea8e6a7 100644 --- a/src/PhpSpreadsheet/Spreadsheet.php +++ b/src/PhpSpreadsheet/Spreadsheet.php @@ -558,6 +558,9 @@ class Spreadsheet implements JsonSerializable if ($this->activeSheetIndex >= $sheetIndex) { ++$this->activeSheetIndex; } + if ($this->activeSheetIndex < 0) { + $this->activeSheetIndex = 0; + } } if ($worksheet->getParent() === null) { diff --git a/src/PhpSpreadsheet/Worksheet/Worksheet.php b/src/PhpSpreadsheet/Worksheet/Worksheet.php index 0bb64ba59..7afa82b5d 100644 --- a/src/PhpSpreadsheet/Worksheet/Worksheet.php +++ b/src/PhpSpreadsheet/Worksheet/Worksheet.php @@ -3684,7 +3684,9 @@ class Worksheet implements IComparable $originalSelected = $this->selectedCells; $this->getStyle($coordinate)->applyFromArray($styleArray); $this->selectedCells = $originalSelected; - $spreadsheet->setActiveSheetIndex($activeSheetIndex); + if ($activeSheetIndex >= 0) { + $spreadsheet->setActiveSheetIndex($activeSheetIndex); + } return true; } diff --git a/tests/PhpSpreadsheetTests/Worksheet/Issue4112Test.php b/tests/PhpSpreadsheetTests/Worksheet/Issue4112Test.php new file mode 100644 index 000000000..ec232b08e --- /dev/null +++ b/tests/PhpSpreadsheetTests/Worksheet/Issue4112Test.php @@ -0,0 +1,43 @@ +removeSheetByIndex(0); + $worksheet = new Worksheet($mySpreadsheet, 'addedsheet'); + self::assertSame(-1, $mySpreadsheet->getActiveSheetIndex()); + $mySpreadsheet->addSheet($worksheet, 0); + self::assertSame('addedsheet', $mySpreadsheet->getActiveSheet()->getTitle()); + $row = 1; + $col = 1; + $worksheet->getCell([$col, $row])->setValue('id_uti'); + self::assertSame('id_uti', $worksheet->getCell([$col, $row])->getValue()); + $mySpreadsheet->disconnectWorksheets(); + } + + public static function providerSheetNumber(): array + { + return [ + 'problem case' => [0], + 'normal case' => [null], + 'negative 1 (as if there were no sheets)' => [-1], + 'diffeent negative number' => [-4], + 'positive number' => [4], + ]; + } +} From 1df4b17d55e36d071993ff156a2ff4ed00b97b46 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Fri, 26 Jul 2024 08:53:00 -0700 Subject: [PATCH 10/15] Scrutinizer Found a Real Problem My test was imperfect,and Scrutinizer detected it. --- CHANGELOG.md | 2 +- tests/PhpSpreadsheetTests/Worksheet/Issue4112Test.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e645df82e..6044ff12f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,7 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org). ### Fixed -- Add Sheet may leave Active Sheet uninitialized. [Issue #4112](https://github.com/PHPOffice/PhpSpreadsheet/issues/4112) [PR #4113](https://github.com/PHPOffice/PhpSpreadsheet/pull/4113) +- Add Sheet may leave Active Sheet uninitialized. [Issue #4112](https://github.com/PHPOffice/PhpSpreadsheet/issues/4112) [PR #4114](https://github.com/PHPOffice/PhpSpreadsheet/pull/4114) ## 2024-07-24 - 2.2.0 diff --git a/tests/PhpSpreadsheetTests/Worksheet/Issue4112Test.php b/tests/PhpSpreadsheetTests/Worksheet/Issue4112Test.php index ec232b08e..9b230a9c9 100644 --- a/tests/PhpSpreadsheetTests/Worksheet/Issue4112Test.php +++ b/tests/PhpSpreadsheetTests/Worksheet/Issue4112Test.php @@ -21,7 +21,7 @@ class Issue4112Test extends AbstractFunctional $mySpreadsheet->removeSheetByIndex(0); $worksheet = new Worksheet($mySpreadsheet, 'addedsheet'); self::assertSame(-1, $mySpreadsheet->getActiveSheetIndex()); - $mySpreadsheet->addSheet($worksheet, 0); + $mySpreadsheet->addSheet($worksheet, $sheetNumber); self::assertSame('addedsheet', $mySpreadsheet->getActiveSheet()->getTitle()); $row = 1; $col = 1; From 459f442b9ec62d6b8679788d27d47d69846012d0 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Fri, 26 Jul 2024 11:52:35 -0700 Subject: [PATCH 11/15] Additional Test New test testGifIssue4112 uses the same technique as reported in the original issue, and it would fail on all PhpSpreadsheet releases, not just 2.2.0. --- .../Writer/Xls/XlsGifBmpTest.php | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/PhpSpreadsheetTests/Writer/Xls/XlsGifBmpTest.php b/tests/PhpSpreadsheetTests/Writer/Xls/XlsGifBmpTest.php index 74901dd40..21e64f2a9 100644 --- a/tests/PhpSpreadsheetTests/Writer/Xls/XlsGifBmpTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Xls/XlsGifBmpTest.php @@ -9,6 +9,7 @@ use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Worksheet\Drawing; use PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing; +use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; use PhpOffice\PhpSpreadsheetTests\Functional\AbstractFunctional; class XlsGifBmpTest extends AbstractFunctional @@ -83,6 +84,34 @@ class XlsGifBmpTest extends AbstractFunctional $reloadedSpreadsheet->disconnectWorksheets(); } + public function testGifIssue4112(): void + { + $spreadsheet = new Spreadsheet(); + $spreadsheet->removeSheetByIndex(0); + $sheet = new Worksheet($spreadsheet, 'Insured List'); + $spreadsheet->addSheet($sheet, 0); + + // Add a drawing to the worksheet + $drawing = new Drawing(); + $drawing->setName('Letters G, I, and G'); + $drawing->setDescription('Handwritten G, I, and F'); + $drawing->setPath(__DIR__ . '/../../../../samples/images/gif.gif'); + $drawing->setHeight(36); + $drawing->setWorksheet($sheet); + $drawing->setCoordinates('A1'); + + $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xls'); + $spreadsheet->disconnectWorksheets(); + $worksheet = $reloadedSpreadsheet->getActiveSheet(); + $drawings = $worksheet->getDrawingCollection(); + self::assertCount(1, $drawings); + foreach ($worksheet->getDrawingCollection() as $drawing) { + $mimeType = ($drawing instanceof MemoryDrawing) ? $drawing->getMimeType() : 'notmemorydrawing'; + self::assertEquals('image/png', $mimeType); + } + $reloadedSpreadsheet->disconnectWorksheets(); + } + public function testInvalidTimestamp(): void { $this->expectException(ReaderException::class); From debb1776ef2731792934a5b0ddda393942eddeb4 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sat, 27 Jul 2024 07:48:40 -0700 Subject: [PATCH 12/15] Update CHANGELOG.md --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6044ff12f..11aa4f80e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,9 @@ and this project adheres to [Semantic Versioning](https://semver.org). ### Fixed - Add Sheet may leave Active Sheet uninitialized. [Issue #4112](https://github.com/PHPOffice/PhpSpreadsheet/issues/4112) [PR #4114](https://github.com/PHPOffice/PhpSpreadsheet/pull/4114) +- Reference to Defined Name Specifying Worksheet. [Issue #206](https://github.com/PHPOffice/PhpSpreadsheet/issues/296) [PR #4096](https://github.com/PHPOffice/PhpSpreadsheet/pull/4096) +- Xls Reader Print/Show Gridlines. [Issue #912](https://github.com/PHPOffice/PhpSpreadsheet/issues/912) [PR #4098](https://github.com/PHPOffice/PhpSpreadsheet/pull/4098) +- ODS Reader Allow Omission of Page Settings Tags. [Issue #4099](https://github.com/PHPOffice/PhpSpreadsheet/issues/4099) [PR #4101](https://github.com/PHPOffice/PhpSpreadsheet/pull/4101) ## 2024-07-24 - 2.2.0 From bea2d4b30f24bcc8a7712e208d1359e603b45dda Mon Sep 17 00:00:00 2001 From: Adrien Crivelli Date: Mon, 29 Jul 2024 16:19:27 +0900 Subject: [PATCH 13/15] Security: prevent XXE (XML External Entity) when loading files Prevent XEE by hiding custom entities by using single quote to declare a non-UTF-8 encoding. XML standard, https://www.w3.org/TR/xml/#NT-EncodingDecl, allows single quote to declare encoding, but we did not support it. Instead, we incorrectly fell back on the default of UTF-8. That incorrectly kept the XML as non-UTF-8, and thus prevented our regexp-based custom entity detection mechanism to work. --- CHANGELOG.md | 4 ++++ .../Reader/Security/XmlScanner.php | 24 ++++++++++++++----- .../Xml/XEETestInvalidUTF-7-single-quote.xml | 2 ++ .../Xml/XEETestValidUTF-8-single-quote.xml | 4 ++++ 4 files changed, 28 insertions(+), 6 deletions(-) create mode 100644 tests/data/Reader/Xml/XEETestInvalidUTF-7-single-quote.xml create mode 100644 tests/data/Reader/Xml/XEETestValidUTF-8-single-quote.xml diff --git a/CHANGELOG.md b/CHANGELOG.md index 11aa4f80e..f5930d0f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org). ## TBD - 3.0.0 +### Security Fix + +- Prevent XXE when loading files + ### Added - Nothing diff --git a/src/PhpSpreadsheet/Reader/Security/XmlScanner.php b/src/PhpSpreadsheet/Reader/Security/XmlScanner.php index 979325a44..3946ed00e 100644 --- a/src/PhpSpreadsheet/Reader/Security/XmlScanner.php +++ b/src/PhpSpreadsheet/Reader/Security/XmlScanner.php @@ -35,15 +35,11 @@ class XmlScanner private function toUtf8(string $xml): string { - $pattern = '/encoding="(.*?)"/'; - $result = preg_match($pattern, $xml, $matches); - $charset = strtoupper($result ? $matches[1] : 'UTF-8'); - + $charset = $this->findCharSet($xml); if ($charset !== 'UTF-8') { $xml = self::forceString(mb_convert_encoding($xml, 'UTF-8', $charset)); - $result = preg_match($pattern, $xml, $matches); - $charset = strtoupper($result ? $matches[1] : 'UTF-8'); + $charset = $this->findCharSet($xml); if ($charset !== 'UTF-8') { throw new Reader\Exception('Suspicious Double-encoded XML, spreadsheet file load() aborted to prevent XXE/XEE attacks'); } @@ -52,6 +48,22 @@ class XmlScanner return $xml; } + private function findCharSet(string $xml): string + { + $patterns = [ + '/encoding="([^"]*]?)"/', + "/encoding='([^']*?)'/", + ]; + + foreach ($patterns as $pattern) { + if (preg_match($pattern, $xml, $matches)) { + return strtoupper($matches[1]); + } + } + + return 'UTF-8'; + } + /** * Scan the XML for use of + +ADw-+ACE-DOCTYPE+ACA-foo+ACA-+AFs-+ADw-+ACE-ENTITY+ACA-toreplace+ACA-+ACI-xxe+AF8-test+ACI-+AD4-+ACA-+AF0-+AD4-+AAo-+ADw-sst+ACA-xmlns+AD0-+ACI-http://schemas.openxmlformats.org/spreadsheetml/2006/main+ACI-+ACA-count+AD0-+ACI-2+ACI-+ACA-uniqueCount+AD0-+ACI-1+ACI-+AD4-+ADw-si+AD4-+ADw-t+AD4-+ACY-toreplace+ADs-+ADw-/t+AD4-+ADw-/si+AD4-+ADw-/sst+AD4- diff --git a/tests/data/Reader/Xml/XEETestValidUTF-8-single-quote.xml b/tests/data/Reader/Xml/XEETestValidUTF-8-single-quote.xml new file mode 100644 index 000000000..e478c7d40 --- /dev/null +++ b/tests/data/Reader/Xml/XEETestValidUTF-8-single-quote.xml @@ -0,0 +1,4 @@ + + + test: Valid + From a3c5c9e7ce3a2a8577bfa9e69960fbccbd2d4a9e Mon Sep 17 00:00:00 2001 From: Adrien Crivelli Date: Mon, 29 Jul 2024 16:54:41 +0900 Subject: [PATCH 14/15] 2.2.1 --- CHANGELOG.md | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f5930d0f0..2fdf34f2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,27 +5,11 @@ 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.0.0 +## 2024-07-29 - 2.2.1 ### Security Fix -- Prevent XXE when loading files - -### Added - -- Nothing - -### Changed - -- Nothing - -### Deprecated - -- Nothing - -### Moved - -- Nothing +- Prevent XXE when loading files [PR #4119](https://github.com/PHPOffice/PhpSpreadsheet/pull/4119) ### Fixed From 7556b12b1ba1efe0a26263c8e14664b0c54a1ba0 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Tue, 30 Jul 2024 22:22:22 -0700 Subject: [PATCH 15/15] Prepare Changelog for 3.0 Also upgrade vendor modules a day ahead of Dependabot. --- CHANGELOG.md | 23 ++ composer.lock | 204 +++++++++--------- .../Calculation/DateTimeExcel/Helpers.php | 2 + .../Calculation/DateTimeExcel/TimeValue.php | 2 +- src/PhpSpreadsheet/Reader/Slk.php | 2 +- 5 files changed, 133 insertions(+), 100 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fdf34f2b..dc3906ca8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,29 @@ 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.0.0 + +### Added + +- Nothing yet. + +### Changed + +- Nothing yet. + +### Deprecated + +- Nothing yet. + +### Moved + +- Nothing yet. + +### Fixed + +- Html Reader Preserve Unicode Whitespace. [Issue #1284](https://github.com/PHPOffice/PhpSpreadsheet/issues/1284) [PR #4106](https://github.com/PHPOffice/PhpSpreadsheet/pull/4106) +- RATE Function Floating Point Number of Periods. [PR #4107](https://github.com/PHPOffice/PhpSpreadsheet/pull/4107) + ## 2024-07-29 - 2.2.1 ### Security Fix diff --git a/composer.lock b/composer.lock index a50b4734a..6e6165afd 100644 --- a/composer.lock +++ b/composer.lock @@ -473,30 +473,38 @@ }, { "name": "composer/pcre", - "version": "3.1.4", + "version": "3.2.0", "source": { "type": "git", "url": "https://github.com/composer/pcre.git", - "reference": "04229f163664973f68f38f6f73d917799168ef24" + "reference": "ea4ab6f9580a4fd221e0418f2c357cdd39102a90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/04229f163664973f68f38f6f73d917799168ef24", - "reference": "04229f163664973f68f38f6f73d917799168ef24", + "url": "https://api.github.com/repos/composer/pcre/zipball/ea4ab6f9580a4fd221e0418f2c357cdd39102a90", + "reference": "ea4ab6f9580a4fd221e0418f2c357cdd39102a90", "shasum": "" }, "require": { "php": "^7.4 || ^8.0" }, + "conflict": { + "phpstan/phpstan": "<1.11.8" + }, "require-dev": { - "phpstan/phpstan": "^1.3", + "phpstan/phpstan": "^1.11.8", "phpstan/phpstan-strict-rules": "^1.1", - "symfony/phpunit-bridge": "^5" + "phpunit/phpunit": "^8 || ^9" }, "type": "library", "extra": { "branch-alias": { "dev-main": "3.x-dev" + }, + "phpstan": { + "includes": [ + "extension.neon" + ] } }, "autoload": { @@ -524,7 +532,7 @@ ], "support": { "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/3.1.4" + "source": "https://github.com/composer/pcre/tree/3.2.0" }, "funding": [ { @@ -540,20 +548,20 @@ "type": "tidelift" } ], - "time": "2024-05-27T13:40:54+00:00" + "time": "2024-07-25T09:36:02+00:00" }, { "name": "composer/semver", - "version": "3.4.0", + "version": "3.4.2", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32" + "reference": "c51258e759afdb17f1fd1fe83bc12baaef6309d6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/35e8d0af4486141bc745f23a29cc2091eb624a32", - "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32", + "url": "https://api.github.com/repos/composer/semver/zipball/c51258e759afdb17f1fd1fe83bc12baaef6309d6", + "reference": "c51258e759afdb17f1fd1fe83bc12baaef6309d6", "shasum": "" }, "require": { @@ -605,7 +613,7 @@ "support": { "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.4.0" + "source": "https://github.com/composer/semver/tree/3.4.2" }, "funding": [ { @@ -621,7 +629,7 @@ "type": "tidelift" } ], - "time": "2023-08-31T09:50:34+00:00" + "time": "2024-07-12T11:35:52+00:00" }, { "name": "composer/xdebug-handler", @@ -1047,16 +1055,16 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.59.3", + "version": "v3.60.0", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "30ba9ecc2b0e5205e578fe29973c15653d9bfd29" + "reference": "e595e4e070d17c5d42ed8c4206f630fcc5f401a4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/30ba9ecc2b0e5205e578fe29973c15653d9bfd29", - "reference": "30ba9ecc2b0e5205e578fe29973c15653d9bfd29", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/e595e4e070d17c5d42ed8c4206f630fcc5f401a4", + "reference": "e595e4e070d17c5d42ed8c4206f630fcc5f401a4", "shasum": "" }, "require": { @@ -1138,7 +1146,7 @@ ], "support": { "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.59.3" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.60.0" }, "funding": [ { @@ -1146,7 +1154,7 @@ "type": "github" } ], - "time": "2024-06-16T14:17:03+00:00" + "time": "2024-07-25T09:26:51+00:00" }, { "name": "masterminds/html5", @@ -1497,16 +1505,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.0.2", + "version": "v5.1.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13" + "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/139676794dc1e9231bf7bcd123cfc0c99182cb13", - "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/683130c2ff8c2739f4822ff7ac5c873ec529abd1", + "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1", "shasum": "" }, "require": { @@ -1517,7 +1525,7 @@ }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + "phpunit/phpunit": "^9.0" }, "bin": [ "bin/php-parse" @@ -1549,9 +1557,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.0.2" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.1.0" }, - "time": "2024-03-05T20:51:40+00:00" + "time": "2024-07-01T20:03:41+00:00" }, { "name": "paragonie/random_compat", @@ -1785,16 +1793,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.11.5", + "version": "1.11.8", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "490f0ae1c92b082f154681d7849aee776a7c1443" + "reference": "6adbd118e6c0515dd2f36b06cde1d6da40f1b8ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/490f0ae1c92b082f154681d7849aee776a7c1443", - "reference": "490f0ae1c92b082f154681d7849aee776a7c1443", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/6adbd118e6c0515dd2f36b06cde1d6da40f1b8ec", + "reference": "6adbd118e6c0515dd2f36b06cde1d6da40f1b8ec", "shasum": "" }, "require": { @@ -1839,7 +1847,7 @@ "type": "github" } ], - "time": "2024-06-17T15:10:54+00:00" + "time": "2024-07-24T07:01:22+00:00" }, { "name": "phpstan/phpstan-phpunit", @@ -2216,16 +2224,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.5.24", + "version": "10.5.29", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "5f124e3e3e561006047b532fd0431bf5bb6b9015" + "reference": "8e9e80872b4e8064401788ee8a32d40b4455318f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/5f124e3e3e561006047b532fd0431bf5bb6b9015", - "reference": "5f124e3e3e561006047b532fd0431bf5bb6b9015", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/8e9e80872b4e8064401788ee8a32d40b4455318f", + "reference": "8e9e80872b4e8064401788ee8a32d40b4455318f", "shasum": "" }, "require": { @@ -2235,26 +2243,26 @@ "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.10.1", - "phar-io/manifest": "^2.0.3", - "phar-io/version": "^3.0.2", + "myclabs/deep-copy": "^1.12.0", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", "php": ">=8.1", - "phpunit/php-code-coverage": "^10.1.5", - "phpunit/php-file-iterator": "^4.0", - "phpunit/php-invoker": "^4.0", - "phpunit/php-text-template": "^3.0", - "phpunit/php-timer": "^6.0", - "sebastian/cli-parser": "^2.0", - "sebastian/code-unit": "^2.0", - "sebastian/comparator": "^5.0", - "sebastian/diff": "^5.0", - "sebastian/environment": "^6.0", - "sebastian/exporter": "^5.1", - "sebastian/global-state": "^6.0.1", - "sebastian/object-enumerator": "^5.0", - "sebastian/recursion-context": "^5.0", - "sebastian/type": "^4.0", - "sebastian/version": "^4.0" + "phpunit/php-code-coverage": "^10.1.15", + "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/diff": "^5.1.1", + "sebastian/environment": "^6.1.0", + "sebastian/exporter": "^5.1.2", + "sebastian/global-state": "^6.0.2", + "sebastian/object-enumerator": "^5.0.0", + "sebastian/recursion-context": "^5.0.0", + "sebastian/type": "^4.0.0", + "sebastian/version": "^4.0.1" }, "suggest": { "ext-soap": "To be able to generate mocks based on WSDL files" @@ -2297,7 +2305,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.24" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.29" }, "funding": [ { @@ -2313,7 +2321,7 @@ "type": "tidelift" } ], - "time": "2024-06-20T13:09:54+00:00" + "time": "2024-07-30T11:08:00+00:00" }, { "name": "psr/container", @@ -2842,31 +2850,31 @@ }, { "name": "react/socket", - "version": "v1.15.0", + "version": "v1.16.0", "source": { "type": "git", "url": "https://github.com/reactphp/socket.git", - "reference": "216d3aec0b87f04a40ca04f481e6af01bdd1d038" + "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/socket/zipball/216d3aec0b87f04a40ca04f481e6af01bdd1d038", - "reference": "216d3aec0b87f04a40ca04f481e6af01bdd1d038", + "url": "https://api.github.com/repos/reactphp/socket/zipball/23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", + "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", "shasum": "" }, "require": { "evenement/evenement": "^3.0 || ^2.0 || ^1.0", "php": ">=5.3.0", - "react/dns": "^1.11", + "react/dns": "^1.13", "react/event-loop": "^1.2", - "react/promise": "^3 || ^2.6 || ^1.2.1", - "react/stream": "^1.2" + "react/promise": "^3.2 || ^2.6 || ^1.2.1", + "react/stream": "^1.4" }, "require-dev": { "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", - "react/async": "^4 || ^3 || ^2", + "react/async": "^4.3 || ^3.3 || ^2", "react/promise-stream": "^1.4", - "react/promise-timer": "^1.10" + "react/promise-timer": "^1.11" }, "type": "library", "autoload": { @@ -2910,7 +2918,7 @@ ], "support": { "issues": "https://github.com/reactphp/socket/issues", - "source": "https://github.com/reactphp/socket/tree/v1.15.0" + "source": "https://github.com/reactphp/socket/tree/v1.16.0" }, "funding": [ { @@ -2918,7 +2926,7 @@ "type": "open_collective" } ], - "time": "2023-12-15T11:02:10+00:00" + "time": "2024-07-26T10:38:09+00:00" }, { "name": "react/stream", @@ -3000,16 +3008,16 @@ }, { "name": "sabberworm/php-css-parser", - "version": "v8.5.1", + "version": "v8.6.0", "source": { "type": "git", "url": "https://github.com/MyIntervals/PHP-CSS-Parser.git", - "reference": "4a3d572b0f8b28bb6fd016ae8bbfc445facef152" + "reference": "d2fb94a9641be84d79c7548c6d39bbebba6e9a70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/4a3d572b0f8b28bb6fd016ae8bbfc445facef152", - "reference": "4a3d572b0f8b28bb6fd016ae8bbfc445facef152", + "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/d2fb94a9641be84d79c7548c6d39bbebba6e9a70", + "reference": "d2fb94a9641be84d79c7548c6d39bbebba6e9a70", "shasum": "" }, "require": { @@ -3059,9 +3067,9 @@ ], "support": { "issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues", - "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v8.5.1" + "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v8.6.0" }, - "time": "2024-02-15T16:41:13+00:00" + "time": "2024-07-01T07:33:21+00:00" }, { "name": "sebastian/cli-parser", @@ -4053,16 +4061,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.10.1", + "version": "3.10.2", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "8f90f7a53ce271935282967f53d0894f8f1ff877" + "reference": "86e5f5dd9a840c46810ebe5ff1885581c42a3017" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/8f90f7a53ce271935282967f53d0894f8f1ff877", - "reference": "8f90f7a53ce271935282967f53d0894f8f1ff877", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/86e5f5dd9a840c46810ebe5ff1885581c42a3017", + "reference": "86e5f5dd9a840c46810ebe5ff1885581c42a3017", "shasum": "" }, "require": { @@ -4129,20 +4137,20 @@ "type": "open_collective" } ], - "time": "2024-05-22T21:24:41+00:00" + "time": "2024-07-21T23:26:44+00:00" }, { "name": "symfony/console", - "version": "v6.4.9", + "version": "v6.4.10", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "6edb5363ec0c78ad4d48c5128ebf4d083d89d3a9" + "reference": "504974cbe43d05f83b201d6498c206f16fc0cdbc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/6edb5363ec0c78ad4d48c5128ebf4d083d89d3a9", - "reference": "6edb5363ec0c78ad4d48c5128ebf4d083d89d3a9", + "url": "https://api.github.com/repos/symfony/console/zipball/504974cbe43d05f83b201d6498c206f16fc0cdbc", + "reference": "504974cbe43d05f83b201d6498c206f16fc0cdbc", "shasum": "" }, "require": { @@ -4207,7 +4215,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.4.9" + "source": "https://github.com/symfony/console/tree/v6.4.10" }, "funding": [ { @@ -4223,7 +4231,7 @@ "type": "tidelift" } ], - "time": "2024-06-28T09:49:33+00:00" + "time": "2024-07-26T12:30:32+00:00" }, { "name": "symfony/deprecation-contracts", @@ -4516,16 +4524,16 @@ }, { "name": "symfony/finder", - "version": "v6.4.8", + "version": "v6.4.10", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "3ef977a43883215d560a2cecb82ec8e62131471c" + "reference": "af29198d87112bebdd397bd7735fbd115997824c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/3ef977a43883215d560a2cecb82ec8e62131471c", - "reference": "3ef977a43883215d560a2cecb82ec8e62131471c", + "url": "https://api.github.com/repos/symfony/finder/zipball/af29198d87112bebdd397bd7735fbd115997824c", + "reference": "af29198d87112bebdd397bd7735fbd115997824c", "shasum": "" }, "require": { @@ -4560,7 +4568,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v6.4.8" + "source": "https://github.com/symfony/finder/tree/v6.4.10" }, "funding": [ { @@ -4576,7 +4584,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-07-24T07:06:38+00:00" }, { "name": "symfony/options-resolver", @@ -5327,16 +5335,16 @@ }, { "name": "symfony/string", - "version": "v6.4.9", + "version": "v6.4.10", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "76792dbd99690a5ebef8050d9206c60c59e681d7" + "reference": "ccf9b30251719567bfd46494138327522b9a9446" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/76792dbd99690a5ebef8050d9206c60c59e681d7", - "reference": "76792dbd99690a5ebef8050d9206c60c59e681d7", + "url": "https://api.github.com/repos/symfony/string/zipball/ccf9b30251719567bfd46494138327522b9a9446", + "reference": "ccf9b30251719567bfd46494138327522b9a9446", "shasum": "" }, "require": { @@ -5393,7 +5401,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.4.9" + "source": "https://github.com/symfony/string/tree/v6.4.10" }, "funding": [ { @@ -5409,7 +5417,7 @@ "type": "tidelift" } ], - "time": "2024-06-28T09:25:38+00:00" + "time": "2024-07-22T10:21:14+00:00" }, { "name": "tecnickcom/tcpdf", @@ -5561,5 +5569,5 @@ "platform-overrides": { "php": "8.1.99" }, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.3.0" } diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/Helpers.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Helpers.php index 94ffa4c4d..1e9af6cb5 100644 --- a/src/PhpSpreadsheet/Calculation/DateTimeExcel/Helpers.php +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/Helpers.php @@ -19,6 +19,8 @@ class Helpers */ public static function isLeapYear(int|string $year): bool { + $year = (int) $year; + return (($year % 4) === 0) && (($year % 100) !== 0) || (($year % 400) === 0); } diff --git a/src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeValue.php b/src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeValue.php index 0af421143..d8c53b476 100644 --- a/src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeValue.php +++ b/src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeValue.php @@ -52,7 +52,7 @@ class TimeValue $arraySplit = preg_split('/[\/:\-\s]/', $timeValue) ?: []; if ((count($arraySplit) == 2 || count($arraySplit) == 3) && $arraySplit[0] > 24) { - $arraySplit[0] = ($arraySplit[0] % 24); + $arraySplit[0] = ((int) $arraySplit[0] % 24); $timeValue = implode(':', $arraySplit); } diff --git a/src/PhpSpreadsheet/Reader/Slk.php b/src/PhpSpreadsheet/Reader/Slk.php index 355ce7f79..2a0b2fcd9 100644 --- a/src/PhpSpreadsheet/Reader/Slk.php +++ b/src/PhpSpreadsheet/Reader/Slk.php @@ -449,7 +449,7 @@ class Slk extends BaseReader private function processPColors(string $rowDatum, array &$formatArray): void { if (preg_match('/L([1-9]\\d*)/', $rowDatum, $matches)) { - $fontColor = $matches[1] % 8; + $fontColor = ((int) $matches[1]) % 8; $formatArray['font']['color']['argb'] = self::COLOR_ARRAY[$fontColor]; } }