diff --git a/CHANGELOG.md b/CHANGELOG.md index 1241d00ed..dc3906ca8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,43 @@ 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 +## 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 + +- Prevent XXE when loading files [PR #4119](https://github.com/PHPOffice/PhpSpreadsheet/pull/4119) + +### 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 ### Added 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/Calculation.php b/src/PhpSpreadsheet/Calculation/Calculation.php index be7a5dd1e..ea8132540 100644 --- a/src/PhpSpreadsheet/Calculation/Calculation.php +++ b/src/PhpSpreadsheet/Calculation/Calculation.php @@ -5151,9 +5151,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); @@ -5178,7 +5179,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; } @@ -5669,10 +5670,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/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/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/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/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/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 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/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 1503c04b0..6c4046774 100644 --- a/src/PhpSpreadsheet/Worksheet/Worksheet.php +++ b/src/PhpSpreadsheet/Worksheet/Worksheet.php @@ -3726,7 +3726,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/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(); + } +} 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(); + } +} 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/PhpSpreadsheetTests/Reader/Xlsx/GridlinesTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/GridlinesTest.php new file mode 100644 index 000000000..5387ea738 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/GridlinesTest.php @@ -0,0 +1,50 @@ +getActiveSheet(); + $sheet2 = $spreadsheet->createSheet(); + $sheet2->setTitle('deliberatelyblank'); + $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], + ]; + } +} diff --git a/tests/PhpSpreadsheetTests/Worksheet/Issue4112Test.php b/tests/PhpSpreadsheetTests/Worksheet/Issue4112Test.php new file mode 100644 index 000000000..9b230a9c9 --- /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, $sheetNumber); + 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], + ]; + } +} 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); 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, diff --git a/tests/data/Reader/Ods/issue.4099.ods b/tests/data/Reader/Ods/issue.4099.ods new file mode 100644 index 000000000..26b17ecbd Binary files /dev/null and b/tests/data/Reader/Ods/issue.4099.ods differ diff --git a/tests/data/Reader/Xml/XEETestInvalidUTF-7-single-quote.xml b/tests/data/Reader/Xml/XEETestInvalidUTF-7-single-quote.xml new file mode 100644 index 000000000..9fc7eba72 --- /dev/null +++ b/tests/data/Reader/Xml/XEETestInvalidUTF-7-single-quote.xml @@ -0,0 +1,2 @@ + + +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 +