Skip to content

Commit 8a159a5

Browse files
committed
PhpSpreadsheet 3.4.0
1 parent 4a4d7ff commit 8a159a5

File tree

488 files changed

+73868
-28681
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

488 files changed

+73868
-28681
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
### Added
55
- disable single data sources globally #415
66

7+
### Changed
8+
- PhpSpreadsheet 3.4.0
9+
710
### Fixed
811
- wrong info link in data load screen
912
- own data options not working #413

vendor/phpoffice/phpspreadsheet/CHANGELOG.md

Lines changed: 1057 additions & 5 deletions
Large diffs are not rendered by default.

vendor/phpoffice/phpspreadsheet/CONTRIBUTING.md

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,44 @@
22

33
If you would like to contribute, here are some notes and guidelines:
44

5-
- All new development happens on feature/fix branches, and are then merged to the `master` branch once stable; so the `master` branch is always the most up-to-date, working code
6-
- Tagged releases are made from the `master` branch
7-
- If you are going to be submitting a pull request, please fork from `master`, and submit your pull request back as a fix/feature branch referencing the GitHub issue number
8-
- Code style might be automatically fixed by `composer fix`
9-
- All code changes must be validated by `composer check`
5+
- 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
6+
- 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
7+
- The code must work with all PHP versions that we support.
8+
- You can call `composer versions` to test version compatibility.
9+
- Code style should be maintained.
10+
- `composer style` will identify any issues with Coding Style.
11+
- `composer fix` will fix most issues with Coding Style.
12+
- All code changes must be validated by `composer check`.
13+
- Please include Unit Tests to verify that a bug exists, and that this PR fixes it.
14+
- Please include Unit Tests to show that a new Feature works as expected.
15+
- Please don't "bundle" several changes into a single PR; submit a PR for each discrete change/fix.
16+
- Remember to update documentation if necessary.
17+
1018
- [Helpful article about forking](https://help.github.com/articles/fork-a-repo/ "Forking a GitHub repository")
1119
- [Helpful article about pull requests](https://help.github.com/articles/using-pull-requests/ "Pull Requests")
20+
21+
## Unit Tests
22+
23+
When writing Unit Tests, please
24+
- Always try to write Unit Tests for both the happy and unhappy paths.
25+
- Put all assertions in the Test itself, not in an abstract class that the Test extends (even if this means code duplication between tests).
26+
- Include any necessary `setup()` and `tearDown()` in the Test itself.
27+
- If you change any global settings (such as system locale, or Compatibility Mode for Excel Function tests), make sure that you reset to the default in the `tearDown()`.
28+
- Use the `ExcelError` functions in assertions for Excel Error values in Excel Function implementations.
29+
<br />Not only does it reduce the risk of typos; but at some point in the future, ExcelError values will be an object rather than a string, and we won't then need to update all the tests.
30+
- Don't over-complicate test code by testing happy and unhappy paths in the same test.
31+
32+
This makes it easier to see exactly what is being tested when reviewing the PR. I want to be able to see it in the PR, not have to hunt in other unchanged classes to see what the test is doing.
33+
34+
## How to release
35+
36+
1. Complete CHANGELOG.md and commit
37+
2. Create an annotated tag
38+
1. `git tag -a 1.2.3`
39+
2. Tag subject must be the version number, eg: `1.2.3`
40+
3. Tag body must be a copy-paste of the changelog entries.
41+
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.
42+
4. Github seems to remove markdown headings in the Release Notes, so you should edit to restore these.
43+
44+
> **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.)
45+

vendor/phpoffice/phpspreadsheet/README.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,52 @@
1111
PhpSpreadsheet is a library written in pure PHP and offers a set of classes that
1212
allow you to read and write various spreadsheet file formats such as Excel and LibreOffice Calc.
1313

14+
## Installation
15+
16+
See the [install instructions](https://phpspreadsheet.readthedocs.io/en/latest/#installation).
17+
1418
## Documentation
1519

1620
Read more about it, including install instructions, in the [official documentation](https://phpspreadsheet.readthedocs.io). Or check out the [API documentation](https://phpoffice.github.io/PhpSpreadsheet).
1721

1822
Please ask your support questions on [StackOverflow](https://stackoverflow.com/questions/tagged/phpspreadsheet), or have a quick chat on [Gitter](https://gitter.im/PHPOffice/PhpSpreadsheet).
1923

24+
## Patreon
25+
26+
I am now running a [Patreon](https://www.patreon.com/MarkBaker) to support the work that I do on PhpSpreadsheet.
27+
28+
Supporters will receive access to articles about working with PhpSpreadsheet, and how to use some of its more advanced features.
29+
30+
Posts already available to Patreon supporters:
31+
- The Dating Game
32+
- A look at how MS Excel (and PhpSpreadsheet) handle date and time values.
33+
- Looping the Loop
34+
- Advice on Iterating through the rows and cells in a worksheet.
35+
36+
And for Patrons at levels actively using PhpSpreadsheet:
37+
- Behind the Mask
38+
- A look at Number Format Masks.
39+
40+
The Next Article (currently Work in Progress):
41+
- Formula for Success
42+
- How to debug formulae that don't produce the expected result.
43+
44+
45+
My aim is to post at least one article each month, taking a detailed look at some feature of MS Excel and how to use that feature in PhpSpreadsheet, or on how to perform different activities in PhpSpreadsheet.
46+
47+
Planned posts for the future include topics like:
48+
- Tables
49+
- Structured References
50+
- AutoFiltering
51+
- Array Formulae
52+
- Conditional Formatting
53+
- Data Validation
54+
- Value Binders
55+
- Images
56+
- Charts
57+
58+
After a period of six months exclusive to Patreon supporters, articles will be incorporated into the public documentation for the library.
59+
2060
## PHPExcel vs PhpSpreadsheet ?
2161

2262
PhpSpreadsheet is the next version of PHPExcel. It breaks compatibility to dramatically improve the code base quality (namespaces, PSR compliance, use of latest PHP language features, etc.).
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
<?php
2+
3+
namespace PhpOffice\PhpSpreadsheet\Calculation;
4+
5+
use PhpOffice\PhpSpreadsheet\Calculation\Engine\ArrayArgumentHelper;
6+
use PhpOffice\PhpSpreadsheet\Calculation\Engine\ArrayArgumentProcessor;
7+
8+
trait ArrayEnabled
9+
{
10+
private static bool $initializationNeeded = true;
11+
12+
private static ArrayArgumentHelper $arrayArgumentHelper;
13+
14+
/**
15+
* @param array|false $arguments Can be changed to array for Php8.1+
16+
*/
17+
private static function initialiseHelper($arguments): void
18+
{
19+
if (self::$initializationNeeded === true) {
20+
self::$arrayArgumentHelper = new ArrayArgumentHelper();
21+
self::$initializationNeeded = false;
22+
}
23+
self::$arrayArgumentHelper->initialise(($arguments === false) ? [] : $arguments);
24+
}
25+
26+
/**
27+
* Handles array argument processing when the function accepts a single argument that can be an array argument.
28+
* Example use for:
29+
* DAYOFMONTH() or FACT().
30+
*/
31+
protected static function evaluateSingleArgumentArray(callable $method, array $values): array
32+
{
33+
$result = [];
34+
foreach ($values as $value) {
35+
$result[] = $method($value);
36+
}
37+
38+
return $result;
39+
}
40+
41+
/**
42+
* Handles array argument processing when the function accepts multiple arguments,
43+
* and any of them can be an array argument.
44+
* Example use for:
45+
* ROUND() or DATE().
46+
*/
47+
protected static function evaluateArrayArguments(callable $method, mixed ...$arguments): array
48+
{
49+
self::initialiseHelper($arguments);
50+
$arguments = self::$arrayArgumentHelper->arguments();
51+
52+
return ArrayArgumentProcessor::processArguments(self::$arrayArgumentHelper, $method, ...$arguments);
53+
}
54+
55+
/**
56+
* Handles array argument processing when the function accepts multiple arguments,
57+
* but only the first few (up to limit) can be an array arguments.
58+
* Example use for:
59+
* NETWORKDAYS() or CONCATENATE(), where the last argument is a matrix (or a series of values) that need
60+
* to be treated as a such rather than as an array arguments.
61+
*/
62+
protected static function evaluateArrayArgumentsSubset(callable $method, int $limit, mixed ...$arguments): array
63+
{
64+
self::initialiseHelper(array_slice($arguments, 0, $limit));
65+
$trailingArguments = array_slice($arguments, $limit);
66+
$arguments = self::$arrayArgumentHelper->arguments();
67+
$arguments = array_merge($arguments, $trailingArguments);
68+
69+
return ArrayArgumentProcessor::processArguments(self::$arrayArgumentHelper, $method, ...$arguments);
70+
}
71+
72+
private static function testFalse(mixed $value): bool
73+
{
74+
return $value === false;
75+
}
76+
77+
/**
78+
* Handles array argument processing when the function accepts multiple arguments,
79+
* but only the last few (from start) can be an array arguments.
80+
* Example use for:
81+
* Z.TEST() or INDEX(), where the first argument 1 is a matrix that needs to be treated as a dataset
82+
* rather than as an array argument.
83+
*/
84+
protected static function evaluateArrayArgumentsSubsetFrom(callable $method, int $start, mixed ...$arguments): array
85+
{
86+
$arrayArgumentsSubset = array_combine(
87+
range($start, count($arguments) - $start),
88+
array_slice($arguments, $start)
89+
);
90+
if (self::testFalse($arrayArgumentsSubset)) {
91+
return ['#VALUE!'];
92+
}
93+
94+
self::initialiseHelper($arrayArgumentsSubset);
95+
$leadingArguments = array_slice($arguments, 0, $start);
96+
$arguments = self::$arrayArgumentHelper->arguments();
97+
$arguments = array_merge($leadingArguments, $arguments);
98+
99+
return ArrayArgumentProcessor::processArguments(self::$arrayArgumentHelper, $method, ...$arguments);
100+
}
101+
102+
/**
103+
* Handles array argument processing when the function accepts multiple arguments,
104+
* and any of them can be an array argument except for the one specified by ignore.
105+
* Example use for:
106+
* HLOOKUP() and VLOOKUP(), where argument 1 is a matrix that needs to be treated as a database
107+
* rather than as an array argument.
108+
*/
109+
protected static function evaluateArrayArgumentsIgnore(callable $method, int $ignore, mixed ...$arguments): array
110+
{
111+
$leadingArguments = array_slice($arguments, 0, $ignore);
112+
$ignoreArgument = array_slice($arguments, $ignore, 1);
113+
$trailingArguments = array_slice($arguments, $ignore + 1);
114+
115+
self::initialiseHelper(array_merge($leadingArguments, [[null]], $trailingArguments));
116+
$arguments = self::$arrayArgumentHelper->arguments();
117+
118+
array_splice($arguments, $ignore, 1, $ignoreArgument);
119+
120+
return ArrayArgumentProcessor::processArguments(self::$arrayArgumentHelper, $method, ...$arguments);
121+
}
122+
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
<?php
2+
3+
namespace PhpOffice\PhpSpreadsheet\Calculation;
4+
5+
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
6+
7+
class BinaryComparison
8+
{
9+
/**
10+
* Epsilon Precision used for comparisons in calculations.
11+
*/
12+
private const DELTA = 0.1e-12;
13+
14+
/**
15+
* Compare two strings in the same way as strcmp() except that lowercase come before uppercase letters.
16+
*
17+
* @param null|string $str1 First string value for the comparison
18+
* @param null|string $str2 Second string value for the comparison
19+
*/
20+
private static function strcmpLowercaseFirst(?string $str1, ?string $str2): int
21+
{
22+
$inversedStr1 = StringHelper::strCaseReverse($str1 ?? '');
23+
$inversedStr2 = StringHelper::strCaseReverse($str2 ?? '');
24+
25+
return strcmp($inversedStr1, $inversedStr2);
26+
}
27+
28+
/**
29+
* PHP8.1 deprecates passing null to strcmp.
30+
*
31+
* @param null|string $str1 First string value for the comparison
32+
* @param null|string $str2 Second string value for the comparison
33+
*/
34+
private static function strcmpAllowNull(?string $str1, ?string $str2): int
35+
{
36+
return strcmp($str1 ?? '', $str2 ?? '');
37+
}
38+
39+
public static function compare(mixed $operand1, mixed $operand2, string $operator): bool
40+
{
41+
// Simple validate the two operands if they are string values
42+
if (is_string($operand1) && $operand1 > '' && $operand1[0] == Calculation::FORMULA_STRING_QUOTE) {
43+
$operand1 = Calculation::unwrapResult($operand1);
44+
}
45+
if (is_string($operand2) && $operand2 > '' && $operand2[0] == Calculation::FORMULA_STRING_QUOTE) {
46+
$operand2 = Calculation::unwrapResult($operand2);
47+
}
48+
49+
// Use case insensitive comparaison if not OpenOffice mode
50+
if (Functions::getCompatibilityMode() != Functions::COMPATIBILITY_OPENOFFICE) {
51+
if (is_string($operand1)) {
52+
$operand1 = StringHelper::strToUpper($operand1);
53+
}
54+
if (is_string($operand2)) {
55+
$operand2 = StringHelper::strToUpper($operand2);
56+
}
57+
}
58+
59+
$useLowercaseFirstComparison = is_string($operand1)
60+
&& is_string($operand2)
61+
&& Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE;
62+
63+
return self::evaluateComparison($operand1, $operand2, $operator, $useLowercaseFirstComparison);
64+
}
65+
66+
private static function evaluateComparison(mixed $operand1, mixed $operand2, string $operator, bool $useLowercaseFirstComparison): bool
67+
{
68+
return match ($operator) {
69+
'=' => self::equal($operand1, $operand2),
70+
'>' => self::greaterThan($operand1, $operand2, $useLowercaseFirstComparison),
71+
'<' => self::lessThan($operand1, $operand2, $useLowercaseFirstComparison),
72+
'>=' => self::greaterThanOrEqual($operand1, $operand2, $useLowercaseFirstComparison),
73+
'<=' => self::lessThanOrEqual($operand1, $operand2, $useLowercaseFirstComparison),
74+
'<>' => self::notEqual($operand1, $operand2),
75+
default => throw new Exception('Unsupported binary comparison operator'),
76+
};
77+
}
78+
79+
private static function equal(mixed $operand1, mixed $operand2): bool
80+
{
81+
if (is_numeric($operand1) && is_numeric($operand2)) {
82+
$result = (abs($operand1 - $operand2) < self::DELTA);
83+
} elseif (($operand1 === null && is_numeric($operand2)) || ($operand2 === null && is_numeric($operand1))) {
84+
$result = $operand1 == $operand2;
85+
} else {
86+
$result = self::strcmpAllowNull($operand1, $operand2) == 0;
87+
}
88+
89+
return $result;
90+
}
91+
92+
private static function greaterThanOrEqual(mixed $operand1, mixed $operand2, bool $useLowercaseFirstComparison): bool
93+
{
94+
if (is_numeric($operand1) && is_numeric($operand2)) {
95+
$result = ((abs($operand1 - $operand2) < self::DELTA) || ($operand1 > $operand2));
96+
} elseif (($operand1 === null && is_numeric($operand2)) || ($operand2 === null && is_numeric($operand1))) {
97+
$result = $operand1 >= $operand2;
98+
} elseif ($useLowercaseFirstComparison) {
99+
$result = self::strcmpLowercaseFirst($operand1, $operand2) >= 0;
100+
} else {
101+
$result = self::strcmpAllowNull($operand1, $operand2) >= 0;
102+
}
103+
104+
return $result;
105+
}
106+
107+
private static function lessThanOrEqual(mixed $operand1, mixed $operand2, bool $useLowercaseFirstComparison): bool
108+
{
109+
if (is_numeric($operand1) && is_numeric($operand2)) {
110+
$result = ((abs($operand1 - $operand2) < self::DELTA) || ($operand1 < $operand2));
111+
} elseif (($operand1 === null && is_numeric($operand2)) || ($operand2 === null && is_numeric($operand1))) {
112+
$result = $operand1 <= $operand2;
113+
} elseif ($useLowercaseFirstComparison) {
114+
$result = self::strcmpLowercaseFirst($operand1, $operand2) <= 0;
115+
} else {
116+
$result = self::strcmpAllowNull($operand1, $operand2) <= 0;
117+
}
118+
119+
return $result;
120+
}
121+
122+
private static function greaterThan(mixed $operand1, mixed $operand2, bool $useLowercaseFirstComparison): bool
123+
{
124+
return self::lessThanOrEqual($operand1, $operand2, $useLowercaseFirstComparison) !== true;
125+
}
126+
127+
private static function lessThan(mixed $operand1, mixed $operand2, bool $useLowercaseFirstComparison): bool
128+
{
129+
return self::greaterThanOrEqual($operand1, $operand2, $useLowercaseFirstComparison) !== true;
130+
}
131+
132+
private static function notEqual(mixed $operand1, mixed $operand2): bool
133+
{
134+
return self::equal($operand1, $operand2) !== true;
135+
}
136+
}

0 commit comments

Comments
 (0)