Skip to content

Commit 31502b0

Browse files
authored
Merge branch 'master' into maxwidth
2 parents 06fef87 + 3c28bf4 commit 31502b0

40 files changed

+970
-89
lines changed

.github/workflows/main.yml

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -250,9 +250,15 @@ jobs:
250250

251251
- name: Coverage
252252
run: |
253-
./vendor/bin/phpunit --coverage-clover coverage-clover.xml
254-
composer global require scrutinizer/ocular
255-
~/.composer/vendor/bin/ocular code-coverage:upload --format=php-clover coverage-clover.xml
253+
./vendor/bin/phpunit --coverage-clover build/clover.xml
254+
255+
- name: Upload coverage results to Coveralls
256+
env:
257+
COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
258+
run: |
259+
wget https://github.com/php-coveralls/php-coveralls/releases/download/v2.4.3/php-coveralls.phar
260+
chmod +x php-coveralls.phar
261+
php php-coveralls.phar --coverage_clover=build/clover.xml --json_path=build/coveralls-upload.json -vvv
256262
257263
release:
258264
permissions:

.scrutinizer.yml

Lines changed: 0 additions & 30 deletions
This file was deleted.

CHANGELOG.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org). Thia is a
99

1010
### Added
1111

12-
- Nothing yet.
12+
- Add Conditional Formatting with IconSet (Xlsx only). [Issue #4560](https://github.com/PHPOffice/PhpSpreadsheet/issues/4560) [PR #4574](https://github.com/PHPOffice/PhpSpreadsheet/pull/4574)
13+
- Copy cell adjusting formula. [Issue #1203](https://github.com/PHPOffice/PhpSpreadsheet/issues/1203) [PR #4577](https://github.com/PHPOffice/PhpSpreadsheet/pull/4577)
14+
- splitRange and ProtectedRange. [Issue #1457](https://github.com/PHPOffice/PhpSpreadsheet/issues/1457) [PR #4580](https://github.com/PHPOffice/PhpSpreadsheet/pull/4580)
1315

1416
### Removed
1517

@@ -29,7 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org). Thia is a
2931

3032
### Fixed
3133

32-
- Nothing yet.
34+
- Google-only formulas exported from Google Sheets. [Issue #1637](https://github.com/PHPOffice/PhpSpreadsheet/issues/1637) [PR #4579](https://github.com/PHPOffice/PhpSpreadsheet/pull/4579)
3335

3436
## 2025-08-10 - 5.0.0
3537

composer.lock

Lines changed: 14 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/topics/accessing-cells.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,19 @@ $spreadsheet->getActiveSheet()
4242
If you make a call to `getCell()`, and the cell doesn't already exist, then
4343
PhpSpreadsheet will create that cell for you.
4444

45+
### Copying a Cell's Value And Style Adjusting Formulas
46+
47+
If cell A1 contains `5`, cell A2 contains `10`, and cell `B1` contains `=A1`, the formula in B1 will be evaluated as `5`. In Excel, if you copy B1 to B2, B2 will wind up with the adjusted formula `=A2` and will be evaluated as 10. Until release 5.1.0, PhpSpreadsheet requires the program to perform its own formula adjustment. In 5.1.0, a new method is introduced to handle formula adjustments:
48+
```php
49+
$worksheet->copyformula($fromCell, $toCell);
50+
```
51+
This will behave as Excel does. If $fromCell does not contain a formula, its contents will be copied as-is.
52+
53+
If you also want to copy $fromCell's style, as Excel does, you can use the following (available in all supported releases):
54+
```php
55+
$worksheet->duplicateStyle($fromCell->getStyle(), $toCell);
56+
```
57+
4558
### BEWARE: Cells and Styles assigned to variables as a Detached Reference
4659

4760
As an "in-memory" model, PHPSpreadsheet can be very demanding of memory,
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
<?php
2+
3+
use PhpOffice\PhpSpreadsheet\Spreadsheet;
4+
use PhpOffice\PhpSpreadsheet\Style\Conditional;
5+
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalFormatValueObject;
6+
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalIconSet;
7+
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\IconSetValues;
8+
9+
require __DIR__ . '/../Header.php';
10+
/** @var PhpOffice\PhpSpreadsheet\Helper\Sample $helper */
11+
12+
// Create new Spreadsheet object
13+
$helper->log('Create new Spreadsheet object');
14+
$spreadsheet = new Spreadsheet();
15+
$sheet = $spreadsheet->getActiveSheet();
16+
17+
// Set document properties
18+
$helper->log('Set document properties');
19+
$spreadsheet->getProperties()->setCreator('issakujitsuk')
20+
->setLastModifiedBy('issakujitsuk')
21+
->setTitle('PhpSpreadsheet Test Document')
22+
->setSubject('PhpSpreadsheet Test Document')
23+
->setDescription('Test document for PhpSpreadsheet, generated using PHP classes.')
24+
->setKeywords('office PhpSpreadsheet php')
25+
->setCategory('Test result file');
26+
27+
// Create the worksheet
28+
$helper->log('Add data');
29+
foreach (['A', 'B', 'C'] as $columnIndex) {
30+
$sheet
31+
->setCellValue("{$columnIndex}1", 1)
32+
->setCellValue("{$columnIndex}2", 2)
33+
->setCellValue("{$columnIndex}3", 8)
34+
->setCellValue("{$columnIndex}4", 4)
35+
->setCellValue("{$columnIndex}5", 5)
36+
->setCellValue("{$columnIndex}6", 6)
37+
->setCellValue("{$columnIndex}7", 7)
38+
->setCellValue("{$columnIndex}8", 3)
39+
->setCellValue("{$columnIndex}9", 9)
40+
->setCellValue("{$columnIndex}10", 10);
41+
}
42+
43+
// Set conditional formatting rules and styles
44+
$helper->log('Define conditional formatting using Icon Set');
45+
46+
// 3 icons
47+
$sheet->getStyle('A1:A10')
48+
->setConditionalStyles([
49+
makeConditionalIconSet(
50+
IconSetValues::ThreeSymbols,
51+
[
52+
new ConditionalFormatValueObject('percent', 0),
53+
new ConditionalFormatValueObject('percent', 33),
54+
new ConditionalFormatValueObject('percent', 67),
55+
]
56+
),
57+
]);
58+
59+
// 4 icons
60+
$sheet->getStyle('B1:B10')
61+
->setConditionalStyles([
62+
makeConditionalIconSet(
63+
IconSetValues::FourArrows,
64+
[
65+
new ConditionalFormatValueObject('percent', 0),
66+
new ConditionalFormatValueObject('percent', 25),
67+
new ConditionalFormatValueObject('percent', 50),
68+
new ConditionalFormatValueObject('percent', 75),
69+
]
70+
),
71+
]);
72+
73+
// 5 icons
74+
$sheet->getStyle('C1:C10')
75+
->setConditionalStyles([
76+
makeConditionalIconSet(
77+
IconSetValues::FiveQuarters,
78+
[
79+
new ConditionalFormatValueObject('percent', 0),
80+
new ConditionalFormatValueObject('percent', 20),
81+
new ConditionalFormatValueObject('percent', 40),
82+
new ConditionalFormatValueObject('percent', 60),
83+
new ConditionalFormatValueObject('percent', 80),
84+
]
85+
),
86+
]);
87+
88+
// Save
89+
$sheet->setSelectedCells('A1');
90+
$helper->write($spreadsheet, __FILE__, ['Xlsx']);
91+
92+
/**
93+
* Helper function to create a Conditional object with an IconSet.
94+
*
95+
* @param IconSetValues $type The type of icon set
96+
* @param ConditionalFormatValueObject[] $cfvos The conditional format value objects
97+
*/
98+
function makeConditionalIconSet(
99+
IconSetValues $type,
100+
array $cfvos,
101+
): Conditional {
102+
$condition = new Conditional();
103+
$condition->setConditionType(Conditional::CONDITION_ICONSET);
104+
$iconSet = new ConditionalIconSet();
105+
$condition->setIconSet($iconSet);
106+
$iconSet->setIconSetType($type)
107+
->setCfvos($cfvos);
108+
109+
return $condition;
110+
}

src/PhpSpreadsheet/Calculation/Calculation.php

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class Calculation extends CalculationLocale
3535
// Opening bracket
3636
const CALCULATION_REGEXP_OPENBRACE = '\(';
3737
// Function (allow for the old @ symbol that could be used to prefix a function, but we'll ignore it)
38-
const CALCULATION_REGEXP_FUNCTION = '@?(?:_xlfn\.)?(?:_xlws\.)?([\p{L}][\p{L}\p{N}\.]*)[\s]*\(';
38+
const CALCULATION_REGEXP_FUNCTION = '@?(?:_xlfn\.)?(?:_xlws\.)?((?:__xludf\.)?[\p{L}][\p{L}\p{N}\.]*)[\s]*\(';
3939
// Cell reference (cell or range of cells, with or without a sheet reference)
4040
const CALCULATION_REGEXP_CELLREF = '((([^\s,!&%^\/\*\+<>=:`-]*)|(\'(?:[^\']|\'[^!])+?\')|(\"(?:[^\"]|\"[^!])+?\"))!)?\$?\b([a-z]{1,3})\$?(\d{1,7})(?![\w.])';
4141
// Used only to detect spill operator #
@@ -1738,6 +1738,16 @@ private function processTokenStack(false|array $tokens, ?string $cellID = null,
17381738
break;
17391739
// Binary Operators
17401740
case ':': // Range
1741+
if ($operand1Data['type'] === 'Error') {
1742+
$stack->push($operand1Data['type'], $operand1Data['value'], null);
1743+
1744+
break;
1745+
}
1746+
if ($operand2Data['type'] === 'Error') {
1747+
$stack->push($operand2Data['type'], $operand2Data['value'], null);
1748+
1749+
break;
1750+
}
17411751
if ($operand1Data['type'] === 'Defined Name') {
17421752
/** @var array{reference: string} $operand1Data */
17431753
if (preg_match('/$' . self::CALCULATION_REGEXP_DEFINEDNAME . '^/mui', $operand1Data['reference']) !== false && $this->spreadsheet !== null) {
@@ -2101,6 +2111,13 @@ private function processTokenStack(false|array $tokens, ?string $cellID = null,
21012111
$nextArg = '';
21022112
}
21032113
}
2114+
} elseif (($arg['type'] ?? '') === 'Error') {
2115+
$argValue = $arg['value'];
2116+
if (is_scalar($argValue)) {
2117+
$nextArg = $argValue;
2118+
} elseif (empty($argValue)) {
2119+
$nextArg = '';
2120+
}
21042121
}
21052122
$args[] = $nextArg;
21062123
if ($functionName !== 'MKMATRIX') {
@@ -2231,11 +2248,13 @@ private function processTokenStack(false|array $tokens, ?string $cellID = null,
22312248
}
22322249
}
22332250
if ($namedRange === null) {
2234-
return $this->raiseFormulaError("undefined name '$definedName'");
2251+
$result = ExcelError::NAME();
2252+
$stack->push('Error', $result, null);
2253+
$this->debugLog->writeDebugLog("Error $result");
2254+
} else {
2255+
$result = $this->evaluateDefinedName($cell, $namedRange, $pCellWorksheet, $stack, $specifiedWorksheet !== '');
22352256
}
22362257

2237-
$result = $this->evaluateDefinedName($cell, $namedRange, $pCellWorksheet, $stack, $specifiedWorksheet !== '');
2238-
22392258
if (isset($storeKey)) {
22402259
$branchStore[$storeKey] = $result;
22412260
}
@@ -2501,6 +2520,8 @@ protected function raiseFormulaError(string $errorMessage, int $code = 0, ?Throw
25012520
$this->formulaError = $errorMessage;
25022521
$this->cyclicReferenceStack->clear();
25032522
$suppress = $this->suppressFormulaErrors;
2523+
$suppressed = $suppress ? ' $suppressed' : '';
2524+
$this->debugLog->writeDebugLog("Raise Error$suppressed $errorMessage");
25042525
if (!$suppress) {
25052526
throw new Exception($errorMessage, $code, $exception);
25062527
}
@@ -2808,7 +2829,13 @@ private function evaluateDefinedName(Cell $cell, DefinedName $namedRange, Worksh
28082829
$this->debugLog->writeDebugLog('Evaluation Result for Named %s %s is %s', $definedNameType, $namedRange->getName(), $this->showTypeDetails($result));
28092830
}
28102831

2811-
$stack->push('Defined Name', $result, $namedRange->getName());
2832+
$y = $namedRange->getWorksheet()?->getTitle();
2833+
$x = $namedRange->getLocalOnly();
2834+
if ($x && $y !== null) {
2835+
$stack->push('Defined Name', $result, "'$y'!" . $namedRange->getName());
2836+
} else {
2837+
$stack->push('Defined Name', $result, $namedRange->getName());
2838+
}
28122839

28132840
return $result;
28142841
}

0 commit comments

Comments
 (0)