From 5c94de34a9c2ed860b579d409de06c30e9a4ee1f Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sat, 3 May 2025 21:18:55 -0700 Subject: [PATCH] More Phpstan Level 10 Prep - Readers Also upgrade Phpstan. --- composer.lock | 24 +-- phpstan-baseline.neon | 2 +- .../Cell/AdvancedValueBinder.php | 4 +- src/PhpSpreadsheet/Cell/Cell.php | 5 +- src/PhpSpreadsheet/Chart/ChartColor.php | 3 +- src/PhpSpreadsheet/Chart/Properties.php | 6 +- src/PhpSpreadsheet/Helper/Html.php | 6 +- src/PhpSpreadsheet/Helper/Sample.php | 1 + src/PhpSpreadsheet/Helper/TextGrid.php | 15 ++ src/PhpSpreadsheet/IOFactory.php | 2 + src/PhpSpreadsheet/NamedRange.php | 1 + src/PhpSpreadsheet/Reader/Csv.php | 19 +- src/PhpSpreadsheet/Reader/Html.php | 72 +++++-- src/PhpSpreadsheet/Reader/Xls.php | 184 ++++++++++++++---- src/PhpSpreadsheet/Reader/Xls/Biff5.php | 2 + src/PhpSpreadsheet/Reader/Xls/Biff8.php | 6 + src/PhpSpreadsheet/Reader/Xls/Color.php | 4 +- src/PhpSpreadsheet/Reader/Xls/Color/BIFF5.php | 2 + src/PhpSpreadsheet/Reader/Xls/Color/BIFF8.php | 2 + .../Reader/Xls/Color/BuiltIn.php | 2 + .../Reader/Xls/ConditionalFormatting.php | 18 +- .../Reader/Xls/DataValidationHelper.php | 1 + .../Reader/Xls/ListFunctions.php | 2 +- .../Reader/Xls/LoadSpreadsheet.php | 23 ++- src/PhpSpreadsheet/Reader/Xls/MD5.php | 11 +- src/PhpSpreadsheet/Reader/XlsBase.php | 8 + src/PhpSpreadsheet/Reader/Xlsx.php | 60 +++++- src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php | 2 + src/PhpSpreadsheet/Reader/Xlsx/Chart.php | 11 ++ .../Reader/Xlsx/ColumnAndRowAttributes.php | 12 +- .../Reader/Xlsx/ConditionalStyles.php | 19 ++ src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php | 3 +- src/PhpSpreadsheet/Reader/Xlsx/PageSetup.php | 11 ++ src/PhpSpreadsheet/Reader/Xlsx/Properties.php | 5 +- src/PhpSpreadsheet/Reader/Xlsx/Styles.php | 19 +- .../Reader/Xlsx/TableReader.php | 22 ++- .../Reader/Xlsx/WorkbookView.php | 1 + .../Escher/DgContainer/SpgrContainer.php | 1 + src/PhpSpreadsheet/Style/Font.php | 2 + src/PhpSpreadsheet/Worksheet/Worksheet.php | 10 +- src/PhpSpreadsheet/Writer/Html.php | 2 +- .../Functions/LookupRef/IndirectTest.php | 6 +- .../Custom/ComplexAssert.php | 3 +- .../PhpSpreadsheetTests/Helper/SampleTest.php | 1 + .../Helper/TextGridTest.php | 9 +- .../Reader/Html/HtmlBorderTest.php | 1 + .../Xls/ConditionalFormattingBasicTest.php | 4 +- .../ConditionalFormattingExpressionTest.php | 4 +- .../Reader/Xls/DataValidationTest.php | 4 +- .../Reader/Xls/HiddenWorksheetTest.php | 1 + .../Reader/Xls/LoadSheetsOnlyTest.php | 12 +- .../Reader/Xls/PageSetupTest.php | 2 + .../Reader/Xls/PasswordTest.php | 55 ++++++ .../Reader/Xlsx/AutoFilter2Test.php | 1 + .../Reader/Xlsx/AutoFilterEvaluateTest.php | 1 + .../Reader/Xlsx/CommentTest.php | 7 +- .../Reader/Xlsx/HiddenWorksheetTest.php | 1 + .../Reader/Xlsx/Issue3126Test.php | 21 +- .../Reader/Xlsx/Issue3679ImgTest.php | 2 + .../Reader/Xlsx/LoadSheetsOnlyTest.php | 12 +- .../Reader/Xlsx/PageSetupTest.php | 2 + .../Reader/Xlsx/SheetProtectionTest.php | 1 + .../Reader/Xlsx/XlsxTest.php | 4 +- .../PhpSpreadsheetTests/Shared/Date2Test.php | 82 ++++---- .../Shared/StringHelperLocaleTest.php | 3 +- .../SpreadsheetCopyCloneTest.php | 24 +-- .../SpreadsheetCoverageTest.php | 57 +++--- .../SpreadsheetDuplicateSheetTest.php | 4 +- tests/PhpSpreadsheetTests/Style/FontTest.php | 1 + .../Writer/Xlsx/CalculationErrorTest.php | 16 +- tests/data/Reader/XLS/pwtest.xls | Bin 0 -> 13824 bytes tests/data/Reader/XLS/pwtest2.xls | Bin 0 -> 13824 bytes tests/data/Reader/XLS/pwtest3.xls | Bin 0 -> 26112 bytes 73 files changed, 714 insertions(+), 232 deletions(-) create mode 100644 tests/PhpSpreadsheetTests/Reader/Xls/PasswordTest.php create mode 100644 tests/data/Reader/XLS/pwtest.xls create mode 100644 tests/data/Reader/XLS/pwtest2.xls create mode 100644 tests/data/Reader/XLS/pwtest3.xls diff --git a/composer.lock b/composer.lock index c50914164..cc01138c6 100644 --- a/composer.lock +++ b/composer.lock @@ -1798,16 +1798,16 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.11", + "version": "2.1.13", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "8ca5f79a8f63c49b2359065832a654e1ec70ac30" + "reference": "e55e03e6d4ac49cd1240907e5b08e5cd378572a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/8ca5f79a8f63c49b2359065832a654e1ec70ac30", - "reference": "8ca5f79a8f63c49b2359065832a654e1ec70ac30", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e55e03e6d4ac49cd1240907e5b08e5cd378572a9", + "reference": "e55e03e6d4ac49cd1240907e5b08e5cd378572a9", "shasum": "" }, "require": { @@ -1852,25 +1852,25 @@ "type": "github" } ], - "time": "2025-03-24T13:45:00+00:00" + "time": "2025-04-27T12:28:25+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", - "version": "2.0.1", + "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-deprecation-rules.git", - "reference": "1cc1259cb91ee4cfbb5c39bca9f635f067c910b4" + "reference": "9d8e7d4e32711715ad78a1fb6ec368df9af01fdf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/1cc1259cb91ee4cfbb5c39bca9f635f067c910b4", - "reference": "1cc1259cb91ee4cfbb5c39bca9f635f067c910b4", + "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/9d8e7d4e32711715ad78a1fb6ec368df9af01fdf", + "reference": "9d8e7d4e32711715ad78a1fb6ec368df9af01fdf", "shasum": "" }, "require": { "php": "^7.4 || ^8.0", - "phpstan/phpstan": "^2.0" + "phpstan/phpstan": "^2.1.13" }, "require-dev": { "php-parallel-lint/php-parallel-lint": "^1.2", @@ -1897,9 +1897,9 @@ "description": "PHPStan rules for detecting usage of deprecated classes, methods, properties, constants and traits.", "support": { "issues": "https://github.com/phpstan/phpstan-deprecation-rules/issues", - "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/2.0.1" + "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/2.0.2" }, - "time": "2024-11-28T21:56:36+00:00" + "time": "2025-04-26T19:59:57+00:00" }, { "name": "phpstan/phpstan-phpunit", diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 364905f71..aab499115 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,2 +1,2 @@ parameters: - ignoreErrors: + ignoreErrors: [] diff --git a/src/PhpSpreadsheet/Cell/AdvancedValueBinder.php b/src/PhpSpreadsheet/Cell/AdvancedValueBinder.php index 7c2f989b8..1db0c0f5a 100644 --- a/src/PhpSpreadsheet/Cell/AdvancedValueBinder.php +++ b/src/PhpSpreadsheet/Cell/AdvancedValueBinder.php @@ -110,7 +110,7 @@ class AdvancedValueBinder extends DefaultValueBinder implements IValueBinder return parent::bindValue($cell, $value); } - /** @param array{0: non-falsy-string, 1: ?string, 2: numeric-string, 3: numeric-string, 4: numeric-string} $matches */ + /** @param array{0: string, 1: ?string, 2: numeric-string, 3: numeric-string, 4: numeric-string} $matches */ protected function setImproperFraction(array $matches, Cell $cell): bool { // Convert value to number @@ -131,7 +131,7 @@ class AdvancedValueBinder extends DefaultValueBinder implements IValueBinder return true; } - /** @param array{0: non-falsy-string, 1: ?string, 2: numeric-string, 3: numeric-string} $matches */ + /** @param array{0: string, 1: ?string, 2: numeric-string, 3: numeric-string} $matches */ protected function setProperFraction(array $matches, Cell $cell): bool { // Convert value to number diff --git a/src/PhpSpreadsheet/Cell/Cell.php b/src/PhpSpreadsheet/Cell/Cell.php index 704352456..08175fc1e 100644 --- a/src/PhpSpreadsheet/Cell/Cell.php +++ b/src/PhpSpreadsheet/Cell/Cell.php @@ -497,10 +497,9 @@ class Cell implements Stringable if (isset($matches[3])) { $minCol = $matches[1]; $minRow = (int) $matches[2]; - // https://github.com/phpstan/phpstan/issues/11602 - $maxCol = $matches[4]; // @phpstan-ignore-line + $maxCol = $matches[4]; ++$maxCol; - $maxRow = (int) $matches[5]; // @phpstan-ignore-line + $maxRow = (int) $matches[5]; for ($row = $minRow; $row <= $maxRow; ++$row) { for ($col = $minCol; $col !== $maxCol; ++$col) { if ("$col$row" !== $coordinate) { diff --git a/src/PhpSpreadsheet/Chart/ChartColor.php b/src/PhpSpreadsheet/Chart/ChartColor.php index d6306de6b..6ac4b5acf 100644 --- a/src/PhpSpreadsheet/Chart/ChartColor.php +++ b/src/PhpSpreadsheet/Chart/ChartColor.php @@ -22,7 +22,7 @@ class ChartColor private ?int $brightness = null; /** - * @param string|string[] $value + * @param array{value: ?string, alpha: null|int|string, brightness?: null|int|string, type: ?string}|string $value */ public function __construct($value = '', ?int $alpha = null, ?string $type = null, ?int $brightness = null) { @@ -114,6 +114,7 @@ class ChartColor return $this; } + /** @param array{value: ?string, alpha: null|int|string, brightness?: null|int|string, type: ?string} $color */ public function setColorPropertiesArray(array $color): self { return $this->setColorProperties( diff --git a/src/PhpSpreadsheet/Chart/Properties.php b/src/PhpSpreadsheet/Chart/Properties.php index 3e02a964f..64f97d53c 100644 --- a/src/PhpSpreadsheet/Chart/Properties.php +++ b/src/PhpSpreadsheet/Chart/Properties.php @@ -177,6 +177,7 @@ abstract class Properties return ((float) $value) / self::PERCENTAGE_MULTIPLIER; } + /** @return array{type: ?string, value: ?string, alpha: ?int} */ protected function setColorProperties(?string $color, null|float|int|string $alpha, ?string $colorType): array { return [ @@ -506,11 +507,14 @@ abstract class Properties return $this->softEdges['size']; } + /** @param null|array{value?: ?string, alpha?: null|int|string, brightness?: null|int|string, type?: ?string}|float|string $value */ public function setShadowProperty(string $propertyName, mixed $value): self { $this->activateObject(); if ($propertyName === 'color' && is_array($value)) { - $this->shadowColor->setColorPropertiesArray($value); + /** @var array{value: ?string, alpha: null|int|string, brightness?: null|int|string, type: ?string} */ + $valuex = $value; + $this->shadowColor->setColorPropertiesArray($valuex); } else { $this->shadowProperties[$propertyName] = $value; } diff --git a/src/PhpSpreadsheet/Helper/Html.php b/src/PhpSpreadsheet/Helper/Html.php index 46fdd7547..b668206e3 100644 --- a/src/PhpSpreadsheet/Helper/Html.php +++ b/src/PhpSpreadsheet/Helper/Html.php @@ -557,6 +557,7 @@ class Html /** @var callable[] */ protected array $endTagCallbacks; + /** @var mixed[] */ private array $stack = []; public string $stringData = ''; @@ -711,7 +712,10 @@ class Html public static function colourNameLookup(string $colorName): string { - return static::COLOUR_MAP[$colorName] ?? ''; + /** @var string[] */ + $temp = static::COLOUR_MAP; + + return $temp[$colorName] ?? ''; } protected function startFontTag(DOMElement $tag): void diff --git a/src/PhpSpreadsheet/Helper/Sample.php b/src/PhpSpreadsheet/Helper/Sample.php index 7176696dd..729d5d736 100644 --- a/src/PhpSpreadsheet/Helper/Sample.php +++ b/src/PhpSpreadsheet/Helper/Sample.php @@ -241,6 +241,7 @@ class Sample : $this->log(sprintf('Function: %s() - %s.', rtrim($functionName, '()'), rtrim($description, '.'))); } + /** @param mixed[][] $matrix */ public function displayGrid(array $matrix): void { $renderer = new TextGrid($matrix, $this->isCli()); diff --git a/src/PhpSpreadsheet/Helper/TextGrid.php b/src/PhpSpreadsheet/Helper/TextGrid.php index c7868c050..9c64c617c 100644 --- a/src/PhpSpreadsheet/Helper/TextGrid.php +++ b/src/PhpSpreadsheet/Helper/TextGrid.php @@ -8,10 +8,13 @@ class TextGrid { private bool $isCli; + /** @var mixed[][] */ protected array $matrix; + /** @var int[] */ protected array $rows; + /** @var string[] */ protected array $columns; private string $gridDisplay; @@ -22,6 +25,7 @@ class TextGrid private bool $columnHeaders = true; + /** @param mixed[][] $matrix */ public function __construct(array $matrix, bool $isCli = true, bool $rowDividers = false, bool $rowHeaders = true, bool $columnHeaders = true) { $this->rows = array_keys($matrix); @@ -63,6 +67,7 @@ class TextGrid return $this->gridDisplay; } + /** @param int[] $columnWidths */ private function renderRows(int $maxRowLength, array $columnWidths): void { foreach ($this->matrix as $row => $rowData) { @@ -77,6 +82,10 @@ class TextGrid } } + /** + * @param mixed[] $rowData + * @param int[] $columnWidths + */ private function renderCells(array $rowData, array $columnWidths): void { foreach ($rowData as $column => $cell) { @@ -87,6 +96,7 @@ class TextGrid } } + /** @param int[] $columnWidths */ private function renderColumnHeader(int $maxRowLength, array &$columnWidths): void { if (!$this->columnHeaders) { @@ -95,6 +105,7 @@ class TextGrid return; } foreach ($this->columns as $column => $reference) { + /** @var string $reference */ $columnWidths[$column] = max($columnWidths[$column], $this->strlen($reference)); } if ($this->rowHeaders) { @@ -109,6 +120,7 @@ class TextGrid $this->gridDisplay .= str_repeat(' ', $maxRowLength + 2); } foreach ($this->columns as $column => $reference) { + /** @var scalar $reference */ $this->gridDisplay .= '| ' . str_pad((string) $reference, $columnWidths[$column] + 1, ' '); } $this->gridDisplay .= '|' . PHP_EOL; @@ -116,6 +128,7 @@ class TextGrid $this->renderFooter($maxRowLength, $columnWidths); } + /** @param int[] $columnWidths */ private function renderFooter(int $maxRowLength, array $columnWidths): void { if ($this->rowHeaders) { @@ -128,6 +141,7 @@ class TextGrid $this->gridDisplay .= '+' . PHP_EOL; } + /** @return int[] */ private function getColumnWidths(): array { $columnCount = count($this->matrix, COUNT_RECURSIVE) / count($this->matrix); @@ -139,6 +153,7 @@ class TextGrid return $columnWidths; } + /** @param mixed[] $columnData */ private function getColumnWidth(array $columnData): int { $columnWidth = 0; diff --git a/src/PhpSpreadsheet/IOFactory.php b/src/PhpSpreadsheet/IOFactory.php index 73734fc70..e16cf62b2 100644 --- a/src/PhpSpreadsheet/IOFactory.php +++ b/src/PhpSpreadsheet/IOFactory.php @@ -116,6 +116,8 @@ abstract class IOFactory /** * Identify file type using automatic IReader resolution. + * + * @param string[] $readers */ public static function identify(string $filename, ?array $readers = null, bool $fullClassName = false): string { diff --git a/src/PhpSpreadsheet/NamedRange.php b/src/PhpSpreadsheet/NamedRange.php index 819ddeac6..c7fb853df 100644 --- a/src/PhpSpreadsheet/NamedRange.php +++ b/src/PhpSpreadsheet/NamedRange.php @@ -43,6 +43,7 @@ class NamedRange extends DefinedName return $this; } + /** @return string[] */ public function getCellsInRange(): array { $range = $this->value; diff --git a/src/PhpSpreadsheet/Reader/Csv.php b/src/PhpSpreadsheet/Reader/Csv.php index f4cdd02d3..783c4f00c 100644 --- a/src/PhpSpreadsheet/Reader/Csv.php +++ b/src/PhpSpreadsheet/Reader/Csv.php @@ -188,7 +188,8 @@ class Csv extends BaseReader */ protected function inferSeparator(): void { - if ($this->delimiter !== null) { + $temp = $this->delimiter; + if ($temp !== null) { return; } @@ -228,13 +229,15 @@ class Csv extends BaseReader $this->checkSeparator(); $this->inferSeparator(); - /** @var array */ - $worksheetInfo = []; - $worksheetInfo[0]['worksheetName'] = 'Worksheet'; - $worksheetInfo[0]['lastColumnLetter'] = 'A'; - $worksheetInfo[0]['lastColumnIndex'] = 0; - $worksheetInfo[0]['totalRows'] = 0; - $worksheetInfo[0]['totalColumns'] = 0; + $worksheetInfo = [ + [ + 'worksheetName' => 'Worksheet', + 'lastColumnLetter' => 'A', + 'lastColumnIndex' => 0, + 'totalRows' => 0, + 'totalColumns' => 0, + ], + ]; $delimiter = $this->delimiter ?? ''; // Loop through each line of the file in turn diff --git a/src/PhpSpreadsheet/Reader/Html.php b/src/PhpSpreadsheet/Reader/Html.php index 55f97d327..eb39752bf 100644 --- a/src/PhpSpreadsheet/Reader/Html.php +++ b/src/PhpSpreadsheet/Reader/Html.php @@ -51,7 +51,7 @@ class Html extends BaseReader /** * Formats. */ - protected array $formats = [ + protected const FORMATS = [ 'h1' => [ 'font' => [ 'bold' => true, @@ -128,6 +128,7 @@ class Html extends BaseReader ], // Italic ]; + /** @var array */ protected array $rowspan = []; /** @@ -218,12 +219,17 @@ class Html extends BaseReader return $this->loadIntoExisting($filename, $spreadsheet); } - // Data Array used for testing only, should write to Spreadsheet object on completion of tests - + /** + * Data Array used for testing only, should write to + * Spreadsheet object on completion of tests. + * + * @var mixed[][] + */ protected array $dataArray = []; protected int $tableLevel = 0; + /** @var string[] */ protected array $nestedColumn = ['A']; protected function setTableStartColumn(string $column): string @@ -246,12 +252,14 @@ class Html extends BaseReader { --$this->tableLevel; - return array_pop($this->nestedColumn); + return array_pop($this->nestedColumn) ?? ''; } /** * Flush cell. * + * @param string[] $attributeArray + * * @param-out string $cellContent In one case, it can be bool */ protected function flushCell(Worksheet $sheet, string $column, int|string $row, mixed &$cellContent, array $attributeArray): void @@ -345,6 +353,7 @@ class Html extends BaseReader } } + /** @param string[] $attributeArray */ private function processDomElementTitle(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void { if ($child->nodeName === 'title') { @@ -364,6 +373,7 @@ class Html extends BaseReader private const SPAN_ETC = ['span', 'div', 'font', 'i', 'em', 'strong', 'b']; + /** @param string[] $attributeArray */ private function processDomElementSpanEtc(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void { if (in_array((string) $child->nodeName, self::SPAN_ETC, true)) { @@ -376,7 +386,7 @@ class Html extends BaseReader } if (isset($attributeArray['style'])) { $alignStyle = $attributeArray['style']; - if (preg_match('/\btext-align:\s*(left|right|center|justify)\b/', $alignStyle, $matches) === 1) { + if (preg_match('/\btext-align:\s*(left|right|center|justify)\b/', (string) $alignStyle, $matches) === 1) { $sheet->getComment($column . $row)->setAlignment($matches[1]); } } @@ -384,28 +394,28 @@ class Html extends BaseReader $this->processDomElement($child, $sheet, $row, $column, $cellContent); } - if (isset($this->formats[$child->nodeName])) { - $sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]); + if (isset(self::FORMATS[$child->nodeName])) { + $sheet->getStyle($column . $row)->applyFromArray(self::FORMATS[$child->nodeName]); } } else { $this->processDomElementHr($sheet, $row, $column, $cellContent, $child, $attributeArray); } } + /** @param string[] $attributeArray */ private function processDomElementHr(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void { if ($child->nodeName === 'hr') { $this->flushCell($sheet, $column, $row, $cellContent, $attributeArray); ++$row; - if (isset($this->formats[$child->nodeName])) { - $sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]); - } + $sheet->getStyle($column . $row)->applyFromArray(self::FORMATS[$child->nodeName]); ++$row; } // fall through to br $this->processDomElementBr($sheet, $row, $column, $cellContent, $child, $attributeArray); } + /** @param string[] $attributeArray */ private function processDomElementBr(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void { if ($child->nodeName === 'br' || $child->nodeName === 'hr') { @@ -423,6 +433,7 @@ class Html extends BaseReader } } + /** @param string[] $attributeArray */ private function processDomElementA(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void { if ($child->nodeName === 'a') { @@ -430,9 +441,7 @@ class Html extends BaseReader switch ($attributeName) { case 'href': $sheet->getCell($column . $row)->getHyperlink()->setUrl($attributeValue); - if (isset($this->formats[$child->nodeName])) { - $sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]); - } + $sheet->getStyle($column . $row)->applyFromArray(self::FORMATS[$child->nodeName]); break; case 'class': @@ -451,6 +460,7 @@ class Html extends BaseReader private const H1_ETC = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ol', 'ul', 'p']; + /** @param string[] $attributeArray */ private function processDomElementH1Etc(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void { if (in_array((string) $child->nodeName, self::H1_ETC, true)) { @@ -467,8 +477,8 @@ class Html extends BaseReader $this->processDomElement($child, $sheet, $row, $column, $cellContent); $this->flushCell($sheet, $column, $row, $cellContent, $attributeArray); - if (isset($this->formats[$child->nodeName])) { - $sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]); + if (isset(self::FORMATS[$child->nodeName])) { + $sheet->getStyle($column . $row)->applyFromArray(self::FORMATS[$child->nodeName]); } ++$row; @@ -479,6 +489,7 @@ class Html extends BaseReader } } + /** @param string[] $attributeArray */ private function processDomElementLi(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void { if ($child->nodeName === 'li') { @@ -500,6 +511,7 @@ class Html extends BaseReader } } + /** @param string[] $attributeArray */ private function processDomElementImg(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void { if ($child->nodeName === 'img') { @@ -511,6 +523,7 @@ class Html extends BaseReader private string $currentColumn = 'A'; + /** @param string[] $attributeArray */ private function processDomElementTable(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void { if ($child->nodeName === 'table') { @@ -537,6 +550,7 @@ class Html extends BaseReader } } + /** @param string[] $attributeArray */ private function processDomElementTr(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void { if ($child->nodeName === 'col') { @@ -548,7 +562,7 @@ class Html extends BaseReader $this->processDomElement($child, $sheet, $row, $column, $cellContent); if (isset($attributeArray['height'])) { - $sheet->getRowDimension($row)->setRowHeight($attributeArray['height']); + $sheet->getRowDimension($row)->setRowHeight((float) $attributeArray['height']); } ++$row; @@ -557,6 +571,7 @@ class Html extends BaseReader } } + /** @param string[] $attributeArray */ private function processDomElementThTdOther(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void { if ($child->nodeName !== 'td' && $child->nodeName !== 'th') { @@ -566,6 +581,7 @@ class Html extends BaseReader } } + /** @param string[] $attributeArray */ private function processDomElementBgcolor(Worksheet $sheet, int $row, string $column, array $attributeArray): void { if (isset($attributeArray['bgcolor'])) { @@ -580,6 +596,7 @@ class Html extends BaseReader } } + /** @param string[] $attributeArray */ private function processDomElementWidth(Worksheet $sheet, string $column, array $attributeArray): void { if (isset($attributeArray['width'])) { @@ -587,6 +604,7 @@ class Html extends BaseReader } } + /** @param string[] $attributeArray */ private function processDomElementHeight(Worksheet $sheet, int $row, array $attributeArray): void { if (isset($attributeArray['height'])) { @@ -594,6 +612,7 @@ class Html extends BaseReader } } + /** @param string[] $attributeArray */ private function processDomElementAlign(Worksheet $sheet, int $row, string $column, array $attributeArray): void { if (isset($attributeArray['align'])) { @@ -601,6 +620,7 @@ class Html extends BaseReader } } + /** @param string[] $attributeArray */ private function processDomElementVAlign(Worksheet $sheet, int $row, string $column, array $attributeArray): void { if (isset($attributeArray['valign'])) { @@ -608,6 +628,7 @@ class Html extends BaseReader } } + /** @param string[] $attributeArray */ private function processDomElementDataFormat(Worksheet $sheet, int $row, string $column, array $attributeArray): void { if (isset($attributeArray['data-format'])) { @@ -615,6 +636,7 @@ class Html extends BaseReader } } + /** @param string[] $attributeArray */ private function processDomElementThTd(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void { while (isset($this->rowspan[$column . $row])) { @@ -640,6 +662,7 @@ class Html extends BaseReader //create merging rowspan and colspan $columnTo = $column; for ($i = 0; $i < (int) $attributeArray['colspan'] - 1; ++$i) { + /** @var string $columnTo */ ++$columnTo; } $range = $column . $row . ':' . $columnTo . ($row + (int) $attributeArray['rowspan'] - 1); @@ -660,6 +683,7 @@ class Html extends BaseReader //create merging colspan $columnTo = $column; for ($i = 0; $i < (int) $attributeArray['colspan'] - 1; ++$i) { + /** @var string $columnTo */ ++$columnTo; } $sheet->mergeCells($column . $row . ':' . $columnTo . $row); @@ -667,7 +691,7 @@ class Html extends BaseReader $column = $columnTo; // ++$columnTo above confuses phpstan } - ++$column; + ++$column; //* @phpstan-ignore-line } protected function processDomElement(DOMNode $element, Worksheet $sheet, int &$row, string &$column, string &$cellContent): void @@ -792,6 +816,7 @@ class Html extends BaseReader } } + /** @param string[] $matches */ private static function replaceNonAscii(array $matches): string { return '&#' . mb_ord($matches[0], 'UTF-8') . ';'; @@ -891,6 +916,8 @@ class Html extends BaseReader * * TODO : * - Implement to other propertie, such as border + * + * @param string[] $attributeArray */ private function applyInlineStyle(Worksheet &$sheet, int $row, string $column, array $attributeArray): void { @@ -903,6 +930,7 @@ class Html extends BaseReader } elseif (isset($attributeArray['rowspan'], $attributeArray['colspan'])) { $columnTo = $column; for ($i = 0; $i < (int) $attributeArray['colspan'] - 1; ++$i) { + /** @var string $columnTo */ ++$columnTo; } $range = $column . $row . ':' . $columnTo . ($row + (int) $attributeArray['rowspan'] - 1); @@ -913,6 +941,7 @@ class Html extends BaseReader } elseif (isset($attributeArray['colspan'])) { $columnTo = $column; for ($i = 0; $i < (int) $attributeArray['colspan'] - 1; ++$i) { + /** @var string $columnTo */ ++$columnTo; } $range = $column . $row . ':' . $columnTo . $row; @@ -1079,6 +1108,7 @@ class Html extends BaseReader return HelperHtml::colourNameLookup($value); } + /** @param string[] $attributes */ private function insertImage(Worksheet $sheet, string $column, int $row, array $attributes): void { if (!isset($attributes['src'])) { @@ -1109,6 +1139,8 @@ class Html extends BaseReader $drawing->setName($name); } + /** @var null|scalar $width */ + /** @var null|scalar $height */ if ($width) { if ($height) { $drawing->setWidthAndHeight((int) $width, (int) $height); @@ -1135,6 +1167,11 @@ class Html extends BaseReader } } + /** + * @param string[] $attributes + * + * @return mixed[] + */ private static function getStyleArray(array $attributes): array { $styleArray = []; @@ -1183,6 +1220,7 @@ class Html extends BaseReader 'thick' => Border::BORDER_THICK, ]; + /** @return array */ public static function getBorderMappings(): array { return self::BORDER_MAPPINGS; diff --git a/src/PhpSpreadsheet/Reader/Xls.php b/src/PhpSpreadsheet/Reader/Xls.php index d1e3cdb99..ae0460361 100644 --- a/src/PhpSpreadsheet/Reader/Xls.php +++ b/src/PhpSpreadsheet/Reader/Xls.php @@ -104,6 +104,8 @@ class Xls extends XlsBase /** * Shared formats. + * + * @var mixed[] */ protected array $formats; @@ -116,36 +118,50 @@ class Xls extends XlsBase /** * Color palette. + * + * @var string[][] */ protected array $palette; /** * Worksheets. + * + * @var array */ protected array $sheets; /** * External books. + * + * @var mixed[][] */ protected array $externalBooks; /** * REF structures. Only applies to BIFF8. + * + * @var mixed[][] */ protected array $ref; /** * External names. + * + * @var array|string> */ protected array $externalNames; /** * Defined names. + * + * @var array{isBuiltInName: int, name: string, formula: string, scope: int} */ protected array $definedname; /** * Shared strings. Only applies to BIFF8. + * + * @var array */ protected array $sst; @@ -161,16 +177,22 @@ class Xls extends XlsBase /** * Objects. One OBJ record contributes with one entry. + * + * @var mixed[] */ protected array $objs; /** * Text Objects. One TXO record corresponds with one entry. + * + * @var array */ protected array $textObjects; /** * Cell Annotations (BIFF8). + * + * @var mixed[] */ protected array $cellNotes; @@ -191,22 +213,30 @@ class Xls extends XlsBase /** * Mapping of XF index (that is a cell XF) to final index in cellXf collection. + * + * @var int[] */ protected array $mapCellXfIndex; /** * Mapping of XF index (that is a style XF) to final index in cellStyleXf collection. + * + * @var int[] */ protected array $mapCellStyleXfIndex; /** * The shared formulas in a sheet. One SHAREDFMLA record contributes with one value. + * + * @var mixed[] */ protected array $sharedFormulas; /** * The shared formula parts in a sheet. One FORMULA record contributes with one value if it * refers to a shared formula. + * + * @var mixed[] */ protected array $sharedFormulaParts; @@ -220,6 +250,8 @@ class Xls extends XlsBase */ protected int $encryptionStartPos = 0; + protected string $encryptionPassword = 'VelvetSweatshop'; + /** * The current RC4 decryption object. */ @@ -232,7 +264,7 @@ class Xls extends XlsBase /** * The current MD5 context state. - * It is never set in the program, so code which uses it is suspect. + * It is set via call-by-reference to verifyPassword. */ private string $md5Ctxt = ''; @@ -721,10 +753,18 @@ class Xls extends XlsBase $cbRuns = self::getUInt2d($recordData, 12); $text = $this->getSplicedRecordData(); - $textByte = $text['spliceOffsets'][1] - $text['spliceOffsets'][0] - 1; - $textStr = substr($text['recordData'], $text['spliceOffsets'][0] + 1, $textByte); + /** @var int[] */ + $tempSplice = $text['spliceOffsets']; + /** @var int */ + $temp = $tempSplice[0]; + /** @var int */ + $temp1 = $tempSplice[1]; + $textByte = $temp1 - $temp - 1; + /** @var string */ + $textRecordData = $text['recordData']; + $textStr = substr($textRecordData, $temp + 1, $textByte); // get 1 byte - $is16Bit = ord($text['recordData'][0]); + $is16Bit = ord($textRecordData[0]); // it is possible to use a compressed format, // which omits the high bytes of all characters, if they are all zero if (($is16Bit & 0x01) === 0) { @@ -735,7 +775,7 @@ class Xls extends XlsBase $this->textObjects[$this->textObjRef] = [ 'text' => $textStr, - 'format' => substr($text['recordData'], $text['spliceOffsets'][1], $cbRuns), + 'format' => substr($textRecordData, $tempSplice[1], $cbRuns), 'alignment' => $grbitOpts, 'rotation' => $rot, ]; @@ -780,6 +820,13 @@ class Xls extends XlsBase } } + public function setEncryptionPassword(string $encryptionPassword): self + { + $this->encryptionPassword = $encryptionPassword; + + return $this; + } + /** * FILEPASS. * @@ -799,7 +846,7 @@ class Xls extends XlsBase { $length = self::getUInt2d($this->data, $this->pos + 2); - if ($length != 54) { + if ($length < 54) { throw new Exception('Unexpected file pass record length'); } @@ -808,7 +855,10 @@ class Xls extends XlsBase // move stream pointer to next record $this->pos += 4 + $length; - if (!$this->verifyPassword('VelvetSweatshop', substr($recordData, 6, 16), substr($recordData, 22, 16), substr($recordData, 38, 16), $this->md5Ctxt)) { + if (substr($recordData, 0, 2) !== "\x01\x00" || substr($recordData, 4, 2) !== "\x01\x00") { + throw new Exception('Unsupported encryption algorithm'); + } + if (!$this->verifyPassword($this->encryptionPassword, substr($recordData, 6, 16), substr($recordData, 22, 16), substr($recordData, 38, 16), $this->md5Ctxt)) { throw new Exception('Decryption password incorrect'); } @@ -1046,6 +1096,7 @@ class Xls extends XlsBase } else { $string = $this->readByteStringShort(substr($recordData, 14)); } + /** @var string[] $string */ $objFont->setName($string['value']); $this->objFonts[] = $objFont; @@ -1142,7 +1193,9 @@ class Xls extends XlsBase // we set the general format code $numberFormat = ['formatCode' => NumberFormat::FORMAT_GENERAL]; } - $objStyle->getNumberFormat()->setFormatCode($numberFormat['formatCode']); + /** @var string[] $numberFormat */ + $objStyle->getNumberFormat() + ->setFormatCode($numberFormat['formatCode']); // offset: 4; size: 2; XF type, cell protection, and parent style XF // bit 2-0; mask 0x0007; XF_TYPE_PROT @@ -1632,7 +1685,7 @@ class Xls extends XlsBase $string = $this->readByteStringShort(substr($recordData, 6)); $rec_name = $string['value']; } - + /** @var string $rec_name */ $this->sheets[] = [ 'name' => $rec_name, 'offset' => $rec_offset, @@ -1837,6 +1890,7 @@ class Xls extends XlsBase // get spliced record data $splicedRecordData = $this->getSplicedRecordData(); + /** @var string */ $recordData = $splicedRecordData['recordData']; $this->drawingGroupData .= $recordData; @@ -1865,12 +1919,14 @@ class Xls extends XlsBase $splicedRecordData = $this->getSplicedRecordData(); $recordData = $splicedRecordData['recordData']; + /** @var mixed[] */ $spliceOffsets = $splicedRecordData['spliceOffsets']; // offset: 0; size: 4; total number of strings in the workbook $pos += 4; // offset: 4; size: 4; number of following strings ($nm) + /** @var string $recordData */ $nm = self::getInt4d($recordData, 4); $pos += 4; @@ -1886,10 +1942,13 @@ class Xls extends XlsBase // loop through the Unicode strings (16-bit length) for ($i = 0; $i < $nm && $pos < $limitposSST; ++$i) { // number of characters in the Unicode string + /** @var int $pos */ $numChars = self::getUInt2d($recordData, $pos); + /** @var int $pos */ $pos += 2; // option flags + /** @var string $recordData */ $optionFlags = ord($recordData[$pos]); ++$pos; @@ -1931,6 +1990,7 @@ class Xls extends XlsBase } } + /** @var int $limitpos */ if ($pos + $len <= $limitpos) { // character array is not split between records @@ -1963,11 +2023,14 @@ class Xls extends XlsBase // repeated option flags // OpenOffice.org documentation 5.21 $option = ord($recordData[$pos]); + /** @var int $pos */ ++$pos; + /** @var int $limitpos */ if ($isCompressed && ($option == 0)) { // 1st fragment compressed // this fragment compressed + /** @var int */ $len = min($charsLeft, $limitpos - $pos); $retstr .= substr($recordData, $pos, $len); $charsLeft -= $len; @@ -1975,6 +2038,7 @@ class Xls extends XlsBase } elseif (!$isCompressed && ($option != 0)) { // 1st fragment uncompressed // this fragment uncompressed + /** @var int */ $len = min($charsLeft * 2, $limitpos - $pos); $retstr .= substr($recordData, $pos, $len); $charsLeft -= $len / 2; @@ -1998,6 +2062,7 @@ class Xls extends XlsBase $newstr .= $retstr[$j] . chr(0); } $retstr = $newstr; + /** @var int */ $len = min($charsLeft * 2, $limitpos - $pos); $retstr .= substr($recordData, $pos, $len); $charsLeft -= $len / 2; @@ -2017,6 +2082,7 @@ class Xls extends XlsBase // list of formatting runs for ($j = 0; $j < $formattingRuns; ++$j) { // first formatted character; zero-based + /** @var int $pos */ $charPos = self::getUInt2d($recordData, $pos + $j * 4); // index to font record @@ -2182,8 +2248,13 @@ class Xls extends XlsBase $string = $this->readByteStringShort($recordData); } - $this->phpSheet->getHeaderFooter()->setOddHeader($string['value']); - $this->phpSheet->getHeaderFooter()->setEvenHeader($string['value']); + /** @var string[] $string */ + $this->phpSheet + ->getHeaderFooter() + ->setOddHeader($string['value']); + $this->phpSheet + ->getHeaderFooter() + ->setEvenHeader($string['value']); } } } @@ -2208,8 +2279,14 @@ class Xls extends XlsBase } else { $string = $this->readByteStringShort($recordData); } - $this->phpSheet->getHeaderFooter()->setOddFooter($string['value']); - $this->phpSheet->getHeaderFooter()->setEvenFooter($string['value']); + /** @var string */ + $temp = $string['value']; + $this->phpSheet + ->getHeaderFooter() + ->setOddFooter($temp); + $this->phpSheet + ->getHeaderFooter() + ->setEvenFooter($temp); } } } @@ -2689,9 +2766,14 @@ class Xls extends XlsBase $charPos = 0; $sstCount = count($this->sst[$index]['fmtRuns']); for ($i = 0; $i <= $sstCount; ++$i) { + /** @var mixed[][] $fmtRuns */ if (isset($fmtRuns[$i])) { - $text = StringHelper::substring($this->sst[$index]['value'], $charPos, $fmtRuns[$i]['charPos'] - $charPos); - $charPos = $fmtRuns[$i]['charPos']; + /** @var int[] */ + $temp = $fmtRuns[$i]; + $temp = $temp['charPos']; + /** @var int $charPos */ + $text = StringHelper::substring($this->sst[$index]['value'], $charPos, $temp - $charPos); + $charPos = $temp; } else { $text = StringHelper::substring($this->sst[$index]['value'], $charPos, StringHelper::countCharacters($this->sst[$index]['value'])); } @@ -2701,13 +2783,16 @@ class Xls extends XlsBase $richText->createText($text); } else { $textRun = $richText->createTextRun($text); + /** @var int[][] $fmtRuns */ if (isset($fmtRuns[$i - 1])) { if ($fmtRuns[$i - 1]['fontIndex'] < 4) { $fontIndex = $fmtRuns[$i - 1]['fontIndex']; } else { // this has to do with that index 4 is omitted in all BIFF versions for some stra nge reason // check the OpenOffice documentation of the FONT record - $fontIndex = $fmtRuns[$i - 1]['fontIndex'] - 1; + /** @var int */ + $temp = $fmtRuns[$i - 1]['fontIndex']; + $fontIndex = $temp - 1; } if (array_key_exists($fontIndex, $this->objFonts) === false) { $fontIndex = count($this->objFonts) - 1; @@ -3015,6 +3100,7 @@ class Xls extends XlsBase $string = $this->readByteStringLong($recordData); $value = $string['value']; } + /** @var string $value */ return $value; } @@ -3158,6 +3244,7 @@ class Xls extends XlsBase $string = $this->readByteStringLong(substr($recordData, 6)); $value = $string['value']; } + /** @var string $value */ if ($this->readEmptyCells || trim($value) !== '') { $cell = $this->phpSheet->getCell($columnString . ($row + 1)); $cell->setValueExplicit($value, DataType::TYPE_STRING); @@ -3211,7 +3298,7 @@ class Xls extends XlsBase $splicedRecordData = $this->getSplicedRecordData(); $recordData = $splicedRecordData['recordData']; - $this->drawingData .= $recordData; + $this->drawingData .= StringHelper::convertToString($recordData); } /** @@ -3491,6 +3578,7 @@ class Xls extends XlsBase ++$rangeBoundaries[1][0]; for ($row = $rangeBoundaries[0][1]; $row <= $rangeBoundaries[1][1]; ++$row) { for ($column = $rangeBoundaries[0][0]; $column != $rangeBoundaries[1][0]; ++$column) { + /** @var string $column */ if ($this->getReadFilter()->readCell($column, $row, $this->phpSheet->getTitle())) { $includeCellRange = true; @@ -3522,6 +3610,7 @@ class Xls extends XlsBase if ($this->version == self::XLS_BIFF8 && !$this->readDataOnly) { $cellRangeAddressList = Xls\Biff8::readBIFF8CellRangeAddressList($recordData); foreach ($cellRangeAddressList['cellRangeAddresses'] as $cellRangeAddress) { + /** @var string $cellRangeAddress */ if ( (str_contains($cellRangeAddress, ':')) && ($this->includeCellRangeFiltered($cellRangeAddress)) @@ -3750,6 +3839,7 @@ class Xls extends XlsBase case 0x14: // offset: 16; size: 2; color index for sheet tab $colorIndex = self::getUInt2d($recordData, 16); + /** @var string[] */ $color = Xls\Color::map($colorIndex, $this->palette, $this->version); $this->phpSheet->getTabColor()->setRGB($color['rgb']); @@ -3964,7 +4054,7 @@ class Xls extends XlsBase if (in_array($splitPoint, $validSplitPoints)) { // get spliced record data (and move pointer to next record) $splicedRecordData = $this->getSplicedRecordData(); - $this->drawingData .= $splicedRecordData['recordData']; + $this->drawingData .= StringHelper::convertToString($splicedRecordData['recordData']); return; } @@ -3978,6 +4068,8 @@ class Xls extends XlsBase * records are found. Splices the record data pieces and returns the combined string as if record data * is in one piece. * Moves to next current position in data stream to start of next record different from a CONtINUE record. + * + * @return mixed[] */ private function getSplicedRecordData(): array { @@ -4050,6 +4142,7 @@ class Xls extends XlsBase while ($formulaData !== '' && $token = $this->getNextToken($formulaData, $baseCell)) { $tokens[] = $token; + /** @var int[] $token */ $formulaData = substr($formulaData, $token['size']); } @@ -4061,6 +4154,7 @@ class Xls extends XlsBase /** * Take array of tokens together with additional data for formula and return human readable formula. * + * @param mixed[][] $tokens * @param string $additionalData Additional binary data going with the formula * * @return string Human readable formula @@ -4081,7 +4175,8 @@ class Xls extends XlsBase $space3 = $space3 ?? ''; // carriage returns before opening parenthesis $space4 = $space4 ?? ''; // spaces before closing parenthesis $space5 = $space5 ?? ''; // carriage returns before closing parenthesis - + /** @var string */ + $tokenData = $token['data'] ?? ''; switch ($token['name']) { case 'tAdd': // addition case 'tConcat': // addition @@ -4100,20 +4195,20 @@ class Xls extends XlsBase case 'tSub': // subtraction $op2 = array_pop($formulaStrings); $op1 = array_pop($formulaStrings); - $formulaStrings[] = "$op1$space1$space0{$token['data']}$op2"; + $formulaStrings[] = "$op1$space1$space0{$tokenData}$op2"; unset($space0, $space1); break; case 'tUplus': // unary plus case 'tUminus': // unary minus $op = array_pop($formulaStrings); - $formulaStrings[] = "$space1$space0{$token['data']}$op"; + $formulaStrings[] = "$space1$space0{$tokenData}$op"; unset($space0, $space1); break; case 'tPercent': // percent sign $op = array_pop($formulaStrings); - $formulaStrings[] = "$op$space1$space0{$token['data']}"; + $formulaStrings[] = "$op$space1$space0{$tokenData}"; unset($space0, $space1); break; @@ -4126,29 +4221,30 @@ class Xls extends XlsBase break; case 'tAttrSpace': // space / carriage return // space will be used when next token arrives, do not alter formulaString stack + /** @var string[][] $token */ switch ($token['data']['spacetype']) { case 'type0': - $space0 = str_repeat(' ', $token['data']['spacecount']); + $space0 = str_repeat(' ', (int) $token['data']['spacecount']); break; case 'type1': - $space1 = str_repeat("\n", $token['data']['spacecount']); + $space1 = str_repeat("\n", (int) $token['data']['spacecount']); break; case 'type2': - $space2 = str_repeat(' ', $token['data']['spacecount']); + $space2 = str_repeat(' ', (int) $token['data']['spacecount']); break; case 'type3': - $space3 = str_repeat("\n", $token['data']['spacecount']); + $space3 = str_repeat("\n", (int) $token['data']['spacecount']); break; case 'type4': - $space4 = str_repeat(' ', $token['data']['spacecount']); + $space4 = str_repeat(' ', (int) $token['data']['spacecount']); break; case 'type5': - $space5 = str_repeat("\n", $token['data']['spacecount']); + $space5 = str_repeat("\n", (int) $token['data']['spacecount']); break; } @@ -4162,19 +4258,25 @@ class Xls extends XlsBase break; case 'tFunc': // function with fixed number of arguments case 'tFuncV': // function with variable number of arguments - if ($token['data']['function'] != '') { + /** @var string[] */ + $temp1 = $token['data']; + $temp2 = $temp1['function']; + if ($temp2 != '') { // normal function $ops = []; // array of operators - for ($i = 0; $i < $token['data']['args']; ++$i) { + $temp3 = (int) $temp1['args']; + for ($i = 0; $i < $temp3; ++$i) { $ops[] = array_pop($formulaStrings); } $ops = array_reverse($ops); - $formulaStrings[] = "$space1$space0{$token['data']['function']}(" . implode(',', $ops) . ')'; + $formulaStrings[] = "$space1$space0{$temp2}(" . implode(',', $ops) . ')'; unset($space0, $space1); } else { // add-in function $ops = []; // array of operators - for ($i = 0; $i < $token['data']['args'] - 1; ++$i) { + /** @var int[] */ + $temp = $token['data']; + for ($i = 0; $i < $temp['args'] - 1; ++$i) { $ops[] = array_pop($formulaStrings); } $ops = array_reverse($ops); @@ -4201,7 +4303,7 @@ class Xls extends XlsBase // bite off chunk of additional data $cellRangeAddressList = Xls\Biff8::readBIFF8CellRangeAddressList($additionalData); $additionalData = substr($additionalData, $cellRangeAddressList['size']); - $formulaStrings[] = "$space1$space0{$token['data']}"; + $formulaStrings[] = "$space1$space0{$tokenData}"; unset($space0, $space1); break; @@ -4221,7 +4323,7 @@ class Xls extends XlsBase case 'tRefN': case 'tAreaN': case 'tStr': // string - $formulaStrings[] = "$space1$space0{$token['data']}"; + $formulaStrings[] = "$space1$space0{$tokenData}"; unset($space0, $space1); break; @@ -4237,6 +4339,8 @@ class Xls extends XlsBase * * @param string $formulaData Formula data * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas + * + * @return mixed[] */ private function getNextToken(string $formulaData, string $baseCell = 'A1'): array { @@ -4510,7 +4614,8 @@ class Xls extends XlsBase // offset: 1; size: 2; one-based index to definedname record $definedNameIndex = self::getUInt2d($formulaData, 1) - 1; // offset: 2; size: 2; not used - $data = $this->definedname[$definedNameIndex]['name'] ?? ''; + /** @var string[] */ + $data = $this->definedname[$definedNameIndex]['name'] ?? ''; //* @phpstan-ignore-line break; case 0x24: // single cell reference e.g. A5 @@ -4694,6 +4799,8 @@ class Xls extends XlsBase /** * Read byte string (8-bit string length) * OpenOffice documentation: 2.5.2. + * + * @return array{value: mixed, size: int} */ protected function readByteStringShort(string $subData): array { @@ -4712,6 +4819,8 @@ class Xls extends XlsBase /** * Read byte string (16-bit string length) * OpenOffice documentation: 2.5.2. + * + * @return array{value: mixed, size: int} */ protected function readByteStringLong(string $subData): array { @@ -4742,6 +4851,8 @@ class Xls extends XlsBase * For now, however, this function makes it readable, * which satisfies Phpstan. * + * @return mixed[] + * * @codeCoverageIgnore */ public function getMapCellStyleXfIndex(): array @@ -4753,12 +4864,15 @@ class Xls extends XlsBase * Parse conditional formatting blocks. * * @see https://www.openoffice.org/sc/excelfileformat.pdf Search for CFHEADER followed by CFRULE + * + * @return mixed[] */ protected function readCFHeader(): array { return (new Xls\ConditionalFormatting())->readCFHeader2($this); } + /** @param string[] $cellRangeAddresses */ protected function readCFRule(array $cellRangeAddresses): void { (new Xls\ConditionalFormatting())->readCFRule2($cellRangeAddresses, $this); diff --git a/src/PhpSpreadsheet/Reader/Xls/Biff5.php b/src/PhpSpreadsheet/Reader/Xls/Biff5.php index ef9619eec..61ff9ee0b 100644 --- a/src/PhpSpreadsheet/Reader/Xls/Biff5.php +++ b/src/PhpSpreadsheet/Reader/Xls/Biff5.php @@ -46,6 +46,8 @@ class Biff5 extends Xls /** * Read BIFF5 cell range address list * section 2.5.15. + * + * @return array{size: int, cellRangeAddresses: string[]} */ public static function readBIFF5CellRangeAddressList(string $subData): array { diff --git a/src/PhpSpreadsheet/Reader/Xls/Biff8.php b/src/PhpSpreadsheet/Reader/Xls/Biff8.php index f95c0b5ea..2de9fb8c3 100644 --- a/src/PhpSpreadsheet/Reader/Xls/Biff8.php +++ b/src/PhpSpreadsheet/Reader/Xls/Biff8.php @@ -12,6 +12,8 @@ class Biff8 extends Xls * read BIFF8 constant value array from array data * returns e.g. ['value' => '{1,2;3,4}', 'size' => 40] * section 2.5.8. + * + * @return array{value: string, size: int} */ protected static function readBIFF8ConstantArray(string $arrayData): array { @@ -47,6 +49,8 @@ class Biff8 extends Xls * read BIFF8 constant value which may be 'Empty Value', 'Number', 'String Value', 'Boolean Value', 'Error Value' * section 2.5.7 * returns e.g. ['value' => '5', 'size' => 9]. + * + * @return array{value: bool|float|int|string, size: int} */ private static function readBIFF8Constant(string $valueData): array { @@ -101,6 +105,8 @@ class Biff8 extends Xls /** * Read BIFF8 cell range address list * section 2.5.15. + * + * @return array{size: int, cellRangeAddresses: mixed[]} */ public static function readBIFF8CellRangeAddressList(string $subData): array { diff --git a/src/PhpSpreadsheet/Reader/Xls/Color.php b/src/PhpSpreadsheet/Reader/Xls/Color.php index 17b6e165c..adbe1a251 100644 --- a/src/PhpSpreadsheet/Reader/Xls/Color.php +++ b/src/PhpSpreadsheet/Reader/Xls/Color.php @@ -10,9 +10,9 @@ class Color * Read color. * * @param int $color Indexed color - * @param array $palette Color palette + * @param string[][] $palette Color palette * - * @return array RGB color value, example: ['rgb' => 'FF0000'] + * @return string[] RGB color value, example: ['rgb' => 'FF0000'] */ public static function map(int $color, array $palette, int $version): array { diff --git a/src/PhpSpreadsheet/Reader/Xls/Color/BIFF5.php b/src/PhpSpreadsheet/Reader/Xls/Color/BIFF5.php index 2c0790c9c..ecc026c38 100644 --- a/src/PhpSpreadsheet/Reader/Xls/Color/BIFF5.php +++ b/src/PhpSpreadsheet/Reader/Xls/Color/BIFF5.php @@ -65,6 +65,8 @@ class BIFF5 /** * Map color array from BIFF5 built-in color index. + * + * @return array{rgb: string} */ public static function lookup(int $color): array { diff --git a/src/PhpSpreadsheet/Reader/Xls/Color/BIFF8.php b/src/PhpSpreadsheet/Reader/Xls/Color/BIFF8.php index 914034df2..11745f797 100644 --- a/src/PhpSpreadsheet/Reader/Xls/Color/BIFF8.php +++ b/src/PhpSpreadsheet/Reader/Xls/Color/BIFF8.php @@ -65,6 +65,8 @@ class BIFF8 /** * Map color array from BIFF8 built-in color index. + * + * @return array{rgb: string} */ public static function lookup(int $color): array { diff --git a/src/PhpSpreadsheet/Reader/Xls/Color/BuiltIn.php b/src/PhpSpreadsheet/Reader/Xls/Color/BuiltIn.php index a715b110b..575244645 100644 --- a/src/PhpSpreadsheet/Reader/Xls/Color/BuiltIn.php +++ b/src/PhpSpreadsheet/Reader/Xls/Color/BuiltIn.php @@ -21,6 +21,8 @@ class BuiltIn * Map built-in color to RGB value. * * @param int $color Indexed color + * + * @return array{rgb: string} */ public static function lookup(int $color): array { diff --git a/src/PhpSpreadsheet/Reader/Xls/ConditionalFormatting.php b/src/PhpSpreadsheet/Reader/Xls/ConditionalFormatting.php index 1c05c63c7..9f89d1620 100644 --- a/src/PhpSpreadsheet/Reader/Xls/ConditionalFormatting.php +++ b/src/PhpSpreadsheet/Reader/Xls/ConditionalFormatting.php @@ -48,6 +48,8 @@ class ConditionalFormatting extends Xls * Parse conditional formatting blocks. * * @see https://www.openoffice.org/sc/excelfileformat.pdf Search for CFHEADER followed by CFRULE + * + * @return mixed[] */ protected function readCFHeader2(Xls $xls): array { @@ -73,6 +75,7 @@ class ConditionalFormatting extends Xls return $cellRangeAddresses; } + /** @param string[] $cellRangeAddresses */ protected function readCFRule2(array $cellRangeAddresses, Xls $xls): void { $length = self::getUInt2d($xls->data, $xls->pos + 2); @@ -212,7 +215,9 @@ class ConditionalFormatting extends Xls $color = self::getInt4d($options, 80); if ($color !== -1) { - $style->getFont()->getColor()->setRGB(Color::map($color, $xls->palette, $xls->version)['rgb']); + $style->getFont() + ->getColor() + ->setRGB(Color::map($color, $xls->palette, $xls->version)['rgb']); } } @@ -222,6 +227,7 @@ class ConditionalFormatting extends Xls private function getCFBorderStyle(string $options, Style $style, bool $hasBorderLeft, bool $hasBorderRight, bool $hasBorderTop, bool $hasBorderBottom, Xls $xls): void { + /** @var false|int[] */ $valueArray = unpack('V', $options); $value = is_array($valueArray) ? $valueArray[1] : 0; $left = $value & 15; @@ -230,6 +236,7 @@ class ConditionalFormatting extends Xls $bottom = ($value >> 12) & 15; $leftc = ($value >> 16) & 0x7F; $rightc = ($value >> 23) & 0x7F; + /** @var false|int[] */ $valueArray = unpack('V', substr($options, 4)); $value = is_array($valueArray) ? $valueArray[1] : 0; $topc = $value & 0x7F; @@ -308,6 +315,7 @@ class ConditionalFormatting extends Xls } } + /** @param string[] $cellRanges */ private function setCFRules(array $cellRanges, string $type, string $operator, null|float|int|string $formula1, null|float|int|string $formula2, Style $style, bool $noFormatSet, Xls $xls): void { foreach ($cellRanges as $cellRange) { @@ -324,10 +332,14 @@ class ConditionalFormatting extends Xls } $conditional->setStyle($style); - $conditionalStyles = $xls->phpSheet->getStyle($cellRange)->getConditionalStyles(); + $conditionalStyles = $xls->phpSheet + ->getStyle($cellRange) + ->getConditionalStyles(); $conditionalStyles[] = $conditional; - $xls->phpSheet->getStyle($cellRange)->setConditionalStyles($conditionalStyles); + $xls->phpSheet + ->getStyle($cellRange) + ->setConditionalStyles($conditionalStyles); } } } diff --git a/src/PhpSpreadsheet/Reader/Xls/DataValidationHelper.php b/src/PhpSpreadsheet/Reader/Xls/DataValidationHelper.php index e3a8d98c1..b5a945444 100644 --- a/src/PhpSpreadsheet/Reader/Xls/DataValidationHelper.php +++ b/src/PhpSpreadsheet/Reader/Xls/DataValidationHelper.php @@ -176,6 +176,7 @@ class DataValidationHelper extends Xls // offset: var; size: var; cell range address list with $cellRangeAddressList = Biff8::readBIFF8CellRangeAddressList(substr($recordData, $offset)); + /** @var string[] */ $cellRangeAddresses = $cellRangeAddressList['cellRangeAddresses']; $maxRow = (string) AddressRange::MAX_ROW; $maxCol = AddressRange::MAX_COLUMN; diff --git a/src/PhpSpreadsheet/Reader/Xls/ListFunctions.php b/src/PhpSpreadsheet/Reader/Xls/ListFunctions.php index 9d3eeaba7..cd1c5cae9 100644 --- a/src/PhpSpreadsheet/Reader/Xls/ListFunctions.php +++ b/src/PhpSpreadsheet/Reader/Xls/ListFunctions.php @@ -48,7 +48,7 @@ class ListFunctions extends Xls } foreach ($xls->sheets as $sheet) { - if ($sheet['sheetType'] === 0x00 && is_string($sheet['name'])) { + if ($sheet['sheetType'] === 0x00) { // 0x00: Worksheet, 0x02: Chart, 0x06: Visual Basic module $worksheetNames[] = $sheet['name']; } diff --git a/src/PhpSpreadsheet/Reader/Xls/LoadSpreadsheet.php b/src/PhpSpreadsheet/Reader/Xls/LoadSpreadsheet.php index cf99d472c..60fc5a125 100644 --- a/src/PhpSpreadsheet/Reader/Xls/LoadSpreadsheet.php +++ b/src/PhpSpreadsheet/Reader/Xls/LoadSpreadsheet.php @@ -52,7 +52,7 @@ class LoadSpreadsheet extends Xls $xls->sheets = []; $xls->externalBooks = []; $xls->ref = []; - $xls->definedname = []; + $xls->definedname = []; //* @phpstan-ignore-line $xls->sst = []; $xls->drawingGroupData = ''; $xls->xfIndex = 0; @@ -382,6 +382,7 @@ class LoadSpreadsheet extends Xls break; case self::XLS_TYPE_CFHEADER: + /** @var string[] */ $cellRangeAddresses = $xls->readCFHeader(); break; @@ -464,6 +465,7 @@ class LoadSpreadsheet extends Xls $offsetX = (int) ($startOffsetX * SharedXls::sizeCol($xls->phpSheet, $startColumn) / 1024); $offsetY = (int) ($startOffsetY * SharedXls::sizeRow($xls->phpSheet, $startRow) / 256); + /** @var int[] $obj */ switch ($obj['otObjType']) { case 0x19: // Note @@ -472,7 +474,7 @@ class LoadSpreadsheet extends Xls if (isset($xls->textObjects[$obj['idObjID']])) { $textObject = $xls->textObjects[$obj['idObjID']]; - $xls->cellNotes[$obj['idObjID']]['objTextData'] = $textObject; + $xls->cellNotes[$obj['idObjID']]['objTextData'] = $textObject; //* @phpstan-ignore-line } } @@ -492,6 +494,7 @@ class LoadSpreadsheet extends Xls } if ($escherWorkbook) { + /** @var BSE[] */ $BSECollection = method_exists($escherWorkbook, 'getDggContainer') ? $escherWorkbook->getDggContainer()->getBstoreContainer()->getBSECollection() : []; $BSE = $BSECollection[$BSEindex - 1]; $blipType = $BSE->getBlipType(); @@ -545,7 +548,9 @@ class LoadSpreadsheet extends Xls /** @var int $row */ [$column, $row] = Coordinate::coordinateFromString($cell); if ($xls->getReadFilter()->readCell($column, $row, $xls->phpSheet->getTitle())) { - $formula = $xls->getFormulaFromStructure($xls->sharedFormulas[$baseCell], $cell); + /** @var string */ + $temp = $xls->sharedFormulas[$baseCell]; + $formula = $xls->getFormulaFromStructure($temp, $cell); $xls->phpSheet->getCell($cell)->setValueExplicit('=' . $formula, DataType::TYPE_FORMULA); } } @@ -553,6 +558,7 @@ class LoadSpreadsheet extends Xls if (!empty($xls->cellNotes)) { foreach ($xls->cellNotes as $note => $noteDetails) { + /** @var array{author: string, cellRef: string, objTextData?: mixed[]} $noteDetails */ if (!isset($noteDetails['objTextData'])) { if (isset($xls->textObjects[$note])) { $textObject = $xls->textObjects[$note]; @@ -562,7 +568,14 @@ class LoadSpreadsheet extends Xls } } $cellAddress = str_replace('$', '', $noteDetails['cellRef']); - $xls->phpSheet->getComment($cellAddress)->setAuthor($noteDetails['author'])->setText($xls->parseRichText($noteDetails['objTextData']['text'])); + /** @var string */ + $tempDetails = $noteDetails['objTextData']['text']; + $xls->phpSheet + ->getComment($cellAddress) + ->setAuthor($noteDetails['author']) + ->setText( + $xls->parseRichText($tempDetails) + ); } } if ($selectedCells !== '') { @@ -575,6 +588,7 @@ class LoadSpreadsheet extends Xls // add the named ranges (defined names) foreach ($xls->definedname as $definedName) { + /** @var array{isBuiltInName: int, name: string, formula: string, scope: int} $definedName */ if ($definedName['isBuiltInName']) { switch ($definedName['name']) { case pack('C', 0x06): @@ -647,7 +661,6 @@ class LoadSpreadsheet extends Xls } } else { // Extract range - /** @var non-empty-string $formula */ $formula = $definedName['formula']; if (str_contains($formula, '!')) { $explodes = Worksheet::extractSheetTitle($formula, true, true); diff --git a/src/PhpSpreadsheet/Reader/Xls/MD5.php b/src/PhpSpreadsheet/Reader/Xls/MD5.php index 7da2eeee5..56dd4a322 100644 --- a/src/PhpSpreadsheet/Reader/Xls/MD5.php +++ b/src/PhpSpreadsheet/Reader/Xls/MD5.php @@ -2,6 +2,8 @@ namespace PhpOffice\PhpSpreadsheet\Reader\Xls; +use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException; + class MD5 { private int $a; @@ -58,8 +60,9 @@ class MD5 */ public function add(string $data): void { - // @phpstan-ignore-next-line - $words = array_values(unpack('V16', $data)); + $unpacked = unpack('V16', $data) ?: throw new ReaderException('unable to unpack data'); + /** @var int[] */ + $words = array_values($unpacked); $A = $this->a; $B = $this->b; @@ -173,7 +176,9 @@ class MD5 private static function step(callable $func, int &$A, int $B, int $C, int $D, int $M, int $s, $t): void { $t = self::signedInt($t); - $A = (int) ($A + call_user_func($func, $B, $C, $D) + $M + $t) & self::$allOneBits; + /** @var int */ + $temp = call_user_func($func, $B, $C, $D); + $A = (int) ($A + $temp + $M + $t) & self::$allOneBits; $A = self::rotate($A, $s); $A = (int) ($B + $A) & self::$allOneBits; } diff --git a/src/PhpSpreadsheet/Reader/XlsBase.php b/src/PhpSpreadsheet/Reader/XlsBase.php index e6969db04..6f9da57ce 100644 --- a/src/PhpSpreadsheet/Reader/XlsBase.php +++ b/src/PhpSpreadsheet/Reader/XlsBase.php @@ -177,6 +177,8 @@ class XlsBase extends BaseReader * OpenOffice.org's Documentation of the Microsoft Excel File Format, section 2.5.4. * * @param string $rgb Encoded RGB value (4 bytes) + * + * @return array{rgb: string} */ protected static function readRGB(string $rgb): array { @@ -199,6 +201,8 @@ class XlsBase extends BaseReader * Extracts an Excel Unicode short string (8-bit string length) * OpenOffice documentation: 2.5.3 * function will automatically find out where the Unicode string ends. + * + * @return array{value: string, size: int} */ protected static function readUnicodeStringShort(string $subData): array { @@ -217,6 +221,8 @@ class XlsBase extends BaseReader * Extracts an Excel Unicode long string (16-bit string length) * OpenOffice documentation: 2.5.3 * this function is under construction, needs to support rich text, and Asian phonetic settings. + * + * @return array{value: string, size: int} */ protected static function readUnicodeStringLong(string $subData): array { @@ -235,6 +241,8 @@ class XlsBase extends BaseReader * Read Unicode string with no string length field, but with known character count * this function is under construction, needs to support rich text, and Asian phonetic settings * OpenOffice.org's Documentation of the Microsoft Excel File Format, section 2.5.3. + * + * @return array{value: string, size: int} */ protected static function readUnicodeString(string $subData, int $characterCount): array { diff --git a/src/PhpSpreadsheet/Reader/Xlsx.php b/src/PhpSpreadsheet/Reader/Xlsx.php index e687e4a2f..178f73bcc 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx.php +++ b/src/PhpSpreadsheet/Reader/Xlsx.php @@ -38,6 +38,7 @@ use PhpOffice\PhpSpreadsheet\Style\Font as StyleFont; use PhpOffice\PhpSpreadsheet\Style\NumberFormat; use PhpOffice\PhpSpreadsheet\Style\Style; use PhpOffice\PhpSpreadsheet\Worksheet\HeaderFooterDrawing; +use PhpOffice\PhpSpreadsheet\Worksheet\Table\TableDxfsStyle; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; use SimpleXMLElement; use Throwable; @@ -57,6 +58,7 @@ class Xlsx extends BaseReader private Styles $styleReader; + /** @var SharedFormula[] */ private array $sharedFormulae = []; /** @@ -102,11 +104,13 @@ class Xlsx extends BaseReader } // Phpstan thinks, correctly, that xpath can return false. + /** @return mixed[] */ private static function xpathNoFalse(SimpleXMLElement $sxml, string $path): array { return self::falseToArray($sxml->xpath($path)); } + /** @return mixed[] */ public static function falseToArray(mixed $value): array { return is_array($value) ? $value : []; @@ -158,6 +162,8 @@ class Xlsx extends BaseReader /** * Reads names of the worksheets from a file, without parsing the whole file to a Spreadsheet object. + * + * @return string[] */ public function listWorksheetNames(string $filename): array { @@ -408,6 +414,7 @@ class Xlsx extends BaseReader $addingFirstCellStyleXf = true; $addingFirstCellXf = true; + /** @var mixed[][][][] */ $unparsedLoadedData = []; $this->zip = $zip = new ZipArchive(); @@ -602,11 +609,13 @@ class Xlsx extends BaseReader $numFmts = $xmlStyles->numFmts[0]; } if (isset($numFmts)) { + /** @var SimpleXMLElement $numFmts */ $numFmts->registerXPathNamespace('sml', $mainNS); } $this->styleReader->setNamespace($mainNS); if (!$this->readDataOnly/* && $xmlStyles*/) { foreach ($xfTags as $xfTag) { + /** @var SimpleXMLElement $xfTag */ $xf = self::getAttributes($xfTag); $numFmt = null; @@ -655,6 +664,7 @@ class Xlsx extends BaseReader } foreach ($cellXfTags as $xfTag) { + /** @var SimpleXMLElement $xfTag */ $xf = self::getAttributes($xfTag); $numFmt = NumberFormat::FORMAT_GENERAL; if ($numFmts && $xf['numFmtId']) { @@ -803,6 +813,7 @@ class Xlsx extends BaseReader $xmlSheetMain = $xmlSheetNS->children($mainNS); // Setting Conditional Styles adjusts selected cells, so we need to execute this // before reading the sheet view data to get the actual selected cells + /** @var Style[] $dxfs */ if (!$this->readDataOnly && ($xmlSheet->conditionalFormatting)) { (new ConditionalStyles($docSheet, $xmlSheet, $dxfs, $this->styleReader))->load(); } @@ -967,7 +978,7 @@ class Xlsx extends BaseReader $cAttrS = isset($styles[$cAttrS]) ? $cAttrS : 0; $cell->setXfIndex($cAttrS); // issue 3495 - if ($cellDataType === DataType::TYPE_FORMULA && $styles[$cAttrS]->quotePrefix === true) { + if ($cellDataType === DataType::TYPE_FORMULA && $styles[$cAttrS]->quotePrefix === true) { //* @phpstan-ignore-line $holdSelected = $docSheet->getSelectedCells(); $cell->getStyle()->setQuotePrefix(false); $docSheet->setSelectedCells($holdSelected); @@ -1059,6 +1070,7 @@ class Xlsx extends BaseReader if ($mc->AlternateContent) { foreach ($mc->AlternateContent as $alternateContent) { $alternateContent = self::testSimpleXml($alternateContent); + /** @var mixed[][][][] $unparsedLoadedData */ $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['AlternateContents'][] = $alternateContent->asXML(); } } @@ -1111,18 +1123,23 @@ class Xlsx extends BaseReader $commentsFile->registerXpathNamespace('com', $mainNS); $authorPath = self::xpathNoFalse($commentsFile, 'com:authors/com:author'); foreach ($authorPath as $author) { + /** @var SimpleXMLElement $author */ $authors[] = (string) $author; } // Loop through contents $contentPath = self::xpathNoFalse($commentsFile, 'com:commentList/com:comment'); foreach ($contentPath as $comment) { + /** @var SimpleXMLElement $comment */ $commentx = $comment->attributes(); + /** @var array{ref: scalar, authorId?: scalar} $commentx */ $commentModel = $docSheet->getComment((string) $commentx['ref']); if (isset($commentx['authorId'])) { $commentModel->setAuthor($authors[(int) $commentx['authorId']]); } - $commentModel->setText($this->parseRichText($comment->children($mainNS)->text)); + /** @var SimpleXMLElement */ + $temp = $comment->children($mainNS); + $commentModel->setText($this->parseRichText($temp->text)); } } @@ -1160,6 +1177,7 @@ class Xlsx extends BaseReader $shapes = self::xpathNoFalse($vmlCommentsFile, '//v:shape'); foreach ($shapes as $shape) { + /** @var SimpleXMLElement $shape */ $shape->registerXPathNamespace('v', Namespaces::URN_VML); if (isset($shape['style'])) { @@ -1276,7 +1294,9 @@ class Xlsx extends BaseReader // unparsed vmlDrawing if ($unparsedVmlDrawings) { foreach ($unparsedVmlDrawings as $rId => $relPath) { + /** @var mixed[][][] $unparsedLoadedData */ $rId = substr($rId, 3); // rIdXXX + /** @var mixed[][] */ $unparsedVmlDrawing = &$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['vmlDrawings']; $unparsedVmlDrawing[$rId] = []; $unparsedVmlDrawing[$rId]['filePath'] = self::dirAdd("$dir/$fileWorksheet", $relPath); @@ -1324,6 +1344,7 @@ class Xlsx extends BaseReader $shapes = self::xpathNoFalse($vmlDrawing, '//v:shape'); foreach ($shapes as $idx => $shape) { + /** @var SimpleXMLElement $shape */ $shape->registerXPathNamespace('v', Namespaces::URN_VML); $imageData = $shape->xpath('//v:imagedata'); @@ -1334,6 +1355,7 @@ class Xlsx extends BaseReader $imageData = $imageData[$idx]; $imageData = self::getAttributes($imageData, Namespaces::URN_MSOFFICE); + /** @var array{width: int, height: int, margin-left?: int, margin-top: int} */ $style = self::toCSSArray((string) $shape['style']); if (array_key_exists((string) $imageData['relid'], $drawings)) { @@ -1668,6 +1690,7 @@ class Xlsx extends BaseReader } // store original rId of drawing files + /** @var mixed[][][][] $unparsedLoadedData */ $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingOriginalIds'] = []; foreach ($relsWorksheet->Relationship as $elex) { $ele = self::getAttributes($elex); @@ -1696,12 +1719,14 @@ class Xlsx extends BaseReader if ($xmlAltDrawing->AlternateContent) { foreach ($xmlAltDrawing->AlternateContent as $alternateContent) { $alternateContent = self::testSimpleXml($alternateContent); + /** @var mixed[][][][][] $unparsedLoadedData */ $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingAlternateContents'][] = $alternateContent->asXML(); } } } } + /** @var mixed[][][][] $unparsedLoadedData */ $this->readFormControlProperties($excel, $dir, $fileWorksheet, $docSheet, $unparsedLoadedData); $this->readPrinterSettings($excel, $dir, $fileWorksheet, $docSheet, $unparsedLoadedData); @@ -2044,11 +2069,13 @@ class Xlsx extends BaseReader } } + /** @param null|bool|mixed[]|SimpleXMLElement $array */ private static function getArrayItem(null|array|bool|SimpleXMLElement $array, int|string $key = 0): mixed { return ($array === null || is_bool($array)) ? null : ($array[$key] ?? null); } + /** @param null|bool|mixed[]|SimpleXMLElement $array */ private static function getArrayItemString(null|array|bool|SimpleXMLElement $array, int|string $key = 0): string { $retVal = self::getArrayItem($array, $key); @@ -2056,6 +2083,7 @@ class Xlsx extends BaseReader return StringHelper::convertToString($retVal, false); } + /** @param null|bool|mixed[]|SimpleXMLElement $array */ private static function getArrayItemIntOrSxml(null|array|bool|SimpleXMLElement $array, int|string $key = 0): int|SimpleXMLElement { $retVal = self::getArrayItem($array, $key); @@ -2071,6 +2099,7 @@ class Xlsx extends BaseReader return (string) preg_replace('~[^/]+/\.\./~', '', dirname($base) . "/$add"); } + /** @return mixed[] */ private static function toCSSArray(string $style): array { $style = self::stripWhiteSpaceFromStyleString($style); @@ -2116,6 +2145,7 @@ class Xlsx extends BaseReader return $value === 'true' || $value === 'TRUE'; } + /** @param string[] $hyperlinks */ private function readHyperLinkDrawing(\PhpOffice\PhpSpreadsheet\Worksheet\Drawing $objDrawing, SimpleXMLElement $cellAnchor, array $hyperlinks): void { $hlinkClick = $cellAnchor->pic->nvPicPr->cNvPr->children(Namespaces::DRAWINGML)->hlinkClick; @@ -2169,6 +2199,7 @@ class Xlsx extends BaseReader return $returnValue; } + /** @param mixed[][][][] $unparsedLoadedData */ private function readFormControlProperties(Spreadsheet $excel, string $dir, string $fileWorksheet, Worksheet $docSheet, array &$unparsedLoadedData): void { $zip = $this->zip; @@ -2185,6 +2216,7 @@ class Xlsx extends BaseReader } } + /** @var mixed[][] */ $unparsedCtrlProps = &$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['ctrlProps']; foreach ($ctrlProps as $rId => $ctrlProp) { $rId = substr($rId, 3); // rIdXXX @@ -2196,6 +2228,7 @@ class Xlsx extends BaseReader unset($unparsedCtrlProps); } + /** @param mixed[][][][] $unparsedLoadedData */ private function readPrinterSettings(Spreadsheet $excel, string $dir, string $fileWorksheet, Worksheet $docSheet, array &$unparsedLoadedData): void { $zip = $this->zip; @@ -2212,6 +2245,7 @@ class Xlsx extends BaseReader } } + /** @var mixed[][] */ $unparsedPrinterSettings = &$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['printerSettings']; foreach ($sheetPrinterSettings as $rId => $printerSettings) { $rId = substr($rId, 3); // rIdXXX @@ -2227,6 +2261,7 @@ class Xlsx extends BaseReader unset($unparsedPrinterSettings); } + /** @return array{string, string} */ private function getWorkbookBaseName(): array { $workbookBasename = ''; @@ -2308,6 +2343,10 @@ class Xlsx extends BaseReader } } + /** + * @param TableDxfsStyle[] $tableStyles + * @param mixed[] $dxfs + */ private function readTables( SimpleXMLElement $xmlSheet, Worksheet $docSheet, @@ -2319,6 +2358,7 @@ class Xlsx extends BaseReader array $dxfs ): void { if ($xmlSheet && $xmlSheet->tableParts) { + /** @var array{count: scalar} */ $attributes = $xmlSheet->tableParts->attributes() ?? ['count' => 0]; if (((int) $attributes['count']) > 0) { $this->readTablesInTablesFile($xmlSheet, $dir, $fileWorksheet, $zip, $docSheet, $namespaceTable, $tableStyles, $dxfs); @@ -2326,6 +2366,10 @@ class Xlsx extends BaseReader } } + /** + * @param TableDxfsStyle[] $tableStyles + * @param mixed[] $dxfs + */ private function readTablesInTablesFile( SimpleXMLElement $xmlSheet, string $dir, @@ -2361,11 +2405,14 @@ class Xlsx extends BaseReader } } + /** @return mixed[] */ private static function extractStyles(?SimpleXMLElement $sxml, string $node1, string $node2): array { $array = []; if ($sxml && $sxml->{$node1}->{$node2}) { - foreach ($sxml->{$node1}->{$node2} as $node) { + /** @var SimpleXMLElement */ + $temp = $sxml->{$node1}->{$node2}; + foreach ($temp as $node) { $array[] = $node; } } @@ -2373,6 +2420,7 @@ class Xlsx extends BaseReader return $array; } + /** @return string[] */ private static function extractPalette(?SimpleXMLElement $sxml): array { $array = []; @@ -2406,9 +2454,8 @@ class Xlsx extends BaseReader $firstRow = $matches[2]; $firstCol = $matches[1]; if (array_key_exists(3, $matches)) { - // https://github.com/phpstan/phpstan/issues/11602 - $lastCol = $matches[4]; // @phpstan-ignore-line - $lastRow = $matches[5]; // @phpstan-ignore-line + $lastCol = $matches[4]; + $lastRow = $matches[5]; } else { $lastCol = $firstCol; $lastRow = $firstRow; @@ -2416,6 +2463,7 @@ class Xlsx extends BaseReader ++$lastCol; for ($row = $firstRow; $row <= $lastRow; ++$row) { for ($col = $firstCol; $col !== $lastCol; ++$col) { + /** @var string $col */ if (!$cellCollection->has2("$col$row")) { continue; } diff --git a/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php b/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php index 49fe3609d..88a08318e 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php @@ -51,6 +51,7 @@ class AutoFilter // Entries can be either filter elements foreach ($filterColumn->filters->filter as $filterRule) { // Operator is undefined, but always treated as EQUAL + /** @var SimpleXMLElement */ $attr2 = $filterRule->attributes() ?? ['val' => '']; $column->createRule()->setRule('', (string) $attr2['val'])->setRuleType(Rule::AUTOFILTER_RULETYPE_FILTER); } @@ -103,6 +104,7 @@ class AutoFilter $column->setJoin(Column::AUTOFILTER_COLUMN_JOIN_AND); } foreach ($customFilters->customFilter as $filterRule) { + /** @var SimpleXMLElement */ $attr2 = $filterRule->attributes() ?? ['operator' => '', 'val' => '']; $column->createRule()->setRule( (string) $attr2['operator'], diff --git a/src/PhpSpreadsheet/Reader/Xlsx/Chart.php b/src/PhpSpreadsheet/Reader/Xlsx/Chart.php index beca090da..22233c76b 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/Chart.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/Chart.php @@ -858,6 +858,7 @@ class Chart $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, 0, null, $marker, $fillColor, "$pointSize"); if (isset($seriesDetail->strRef->strCache)) { + /** @var array{formatCode: string, dataValues: mixed[]} */ $seriesData = $this->chartDataSeriesValues($seriesDetail->strRef->strCache->children($this->cNamespace), 's'); $seriesValues ->setFormatCode($seriesData['formatCode']) @@ -869,6 +870,7 @@ class Chart $seriesSource = (string) $seriesDetail->numRef->f; $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, $seriesSource, null, 0, null, $marker, $fillColor, "$pointSize"); if (isset($seriesDetail->numRef->numCache)) { + /** @var array{formatCode: string, dataValues: mixed[]} */ $seriesData = $this->chartDataSeriesValues($seriesDetail->numRef->numCache->children($this->cNamespace)); $seriesValues ->setFormatCode($seriesData['formatCode']) @@ -881,6 +883,7 @@ class Chart $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, 0, null, $marker, $fillColor, "$pointSize"); if (isset($seriesDetail->multiLvlStrRef->multiLvlStrCache)) { + /** @var array{formatCode: string, dataValues: mixed[]} */ $seriesData = $this->chartDataSeriesValuesMultiLevel($seriesDetail->multiLvlStrRef->multiLvlStrCache->children($this->cNamespace), 's'); $seriesValues ->setFormatCode($seriesData['formatCode']) @@ -893,6 +896,7 @@ class Chart $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, 0, null, $marker, $fillColor, "$pointSize"); if (isset($seriesDetail->multiLvlNumRef->multiLvlNumCache)) { + /** @var array{formatCode: string, dataValues: mixed[]} */ $seriesData = $this->chartDataSeriesValuesMultiLevel($seriesDetail->multiLvlNumRef->multiLvlNumCache->children($this->cNamespace), 's'); $seriesValues ->setFormatCode($seriesData['formatCode']) @@ -915,6 +919,7 @@ class Chart return null; } + /** @return mixed[] */ private function chartDataSeriesValues(SimpleXMLElement $seriesValueSet, string $dataType = 'n'): array { $seriesVal = []; @@ -953,6 +958,7 @@ class Chart ]; } + /** @return mixed[] */ private function chartDataSeriesValuesMultiLevel(SimpleXMLElement $seriesValueSet, string $dataType = 'n'): array { $seriesVal = []; @@ -1220,6 +1226,7 @@ class Chart return $font; } + /** @return mixed[] */ private function readChartAttributes(?SimpleXMLElement $chartDetail): array { $plotAttributes = []; @@ -1277,9 +1284,11 @@ class Chart return $plotAttributes; } + /** @param array $plotAttributes */ private function setChartAttributes(Layout $plotArea, array $plotAttributes): void { foreach ($plotAttributes as $plotAttributeKey => $plotAttributeValue) { + /** @var ?bool $plotAttributeValue */ switch ($plotAttributeKey) { case 'showLegendKey': $plotArea->setShowLegendKey($plotAttributeValue); @@ -1310,6 +1319,7 @@ class Chart break; case 'labelFont': + /** @var ?Font $plotAttributeValue */ $plotArea->setLabelFont($plotAttributeValue); break; @@ -1397,6 +1407,7 @@ class Chart 'innerShdw', ]; + /** @return array{type: ?string, value: ?string, alpha: ?int, brightness: ?int} */ private function readColor(SimpleXMLElement $colorXml): array { $result = [ diff --git a/src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php b/src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php index 63dd4ade5..b82b5558f 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php @@ -24,8 +24,7 @@ class ColumnAndRowAttributes extends BaseParserClass * Set Worksheet column attributes by attributes array passed. * * @param string $columnAddress A, B, ... DX, ... - * @param array $columnAttributes array of attributes (indexes are attribute name, values are value) - * 'xfIndex', 'visible', 'collapsed', 'outlineLevel', 'width', ... ? + * @param array{xfIndex?: int, visible?: bool, collapsed?: bool, collapsed?: bool, outlineLevel?: int, rowHeight?: float, width?: int} $columnAttributes array of attributes (indexes are attribute name, values are value) */ private function setColumnAttributes(string $columnAddress, array $columnAttributes): void { @@ -50,7 +49,7 @@ class ColumnAndRowAttributes extends BaseParserClass * Set Worksheet row attributes by attributes array passed. * * @param int $rowNumber 1, 2, 3, ... 99, ... - * @param array $rowAttributes array of attributes (indexes are attribute name, values are value) + * @param array{xfIndex?: int, visible?: bool, collapsed?: bool, collapsed?: bool, outlineLevel?: int, rowHeight?: float} $rowAttributes array of attributes (indexes are attribute name, values are value) * 'xfIndex', 'visible', 'collapsed', 'outlineLevel', 'rowHeight', ... ? */ private function setRowAttributes(int $rowNumber, array $rowAttributes): void @@ -99,6 +98,7 @@ class ColumnAndRowAttributes extends BaseParserClass || !$this->isFilteredColumn($readFilter, $columnCoordinate, $rowsAttributes) ) { if (!isset($columnsAttributesAreSet[$columnCoordinate])) { + /** @var array{xfIndex?: int, visible?: bool, collapsed?: bool, collapsed?: bool, outlineLevel?: int, rowHeight?: float, width?: int} $columnAttributes */ $this->setColumnAttributes($columnCoordinate, $columnAttributes); $columnsAttributesAreSet[$columnCoordinate] = true; } @@ -112,6 +112,7 @@ class ColumnAndRowAttributes extends BaseParserClass || !$this->isFilteredRow($readFilter, $rowCoordinate, $columnsAttributes) ) { if (!isset($rowsAttributesAreSet[$rowCoordinate])) { + /** @var array{xfIndex?: int, visible?: bool, collapsed?: bool, collapsed?: bool, outlineLevel?: int, rowHeight?: float} $rowAttributes */ $this->setRowAttributes($rowCoordinate, $rowAttributes); $rowsAttributesAreSet[$rowCoordinate] = true; } @@ -119,6 +120,7 @@ class ColumnAndRowAttributes extends BaseParserClass } } + /** @param mixed[] $rowsAttributes */ private function isFilteredColumn(IReadFilter $readFilter, string $columnCoordinate, array $rowsAttributes): bool { foreach ($rowsAttributes as $rowCoordinate => $rowAttributes) { @@ -130,6 +132,7 @@ class ColumnAndRowAttributes extends BaseParserClass return true; } + /** @return mixed[] */ private function readColumnAttributes(SimpleXMLElement $worksheetCols, bool $readDataOnly): array { $columnAttributes = []; @@ -153,6 +156,7 @@ class ColumnAndRowAttributes extends BaseParserClass return $columnAttributes; } + /** @return mixed[] */ private function readColumnRangeAttributes(?SimpleXMLElement $column, bool $readDataOnly): array { $columnAttributes = []; @@ -177,6 +181,7 @@ class ColumnAndRowAttributes extends BaseParserClass return $columnAttributes; } + /** @param mixed[] $columnsAttributes */ private function isFilteredRow(IReadFilter $readFilter, int $rowCoordinate, array $columnsAttributes): bool { foreach ($columnsAttributes as $columnCoordinate => $columnAttributes) { @@ -188,6 +193,7 @@ class ColumnAndRowAttributes extends BaseParserClass return false; } + /** @return mixed[] */ private function readRowAttributes(SimpleXMLElement $worksheetRow, bool $readDataOnly, bool $ignoreRowsWithNoCells, bool $readFilterIsNotNull): array { $rowAttributes = []; diff --git a/src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php b/src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php index 436d9ffb8..b09de2ad6 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php @@ -20,12 +20,15 @@ class ConditionalStyles private SimpleXMLElement $worksheetXml; + /** @var string[] */ private array $ns; + /** @var Style[] */ private array $dxfs; private StyleReader $styleReader; + /** @param Style[] $dxfs */ public function __construct(Worksheet $workSheet, SimpleXMLElement $worksheetXml, array $dxfs, StyleReader $styleReader) { $this->worksheet = $workSheet; @@ -59,6 +62,7 @@ class ConditionalStyles $this->worksheet->setSelectedCells($selectedCells); } + /** @param Conditional[][] $conditionals */ private function setConditionalsFromExt(array $conditionals): void { foreach ($conditionals as $conditionalRange => $cfRules) { @@ -70,6 +74,7 @@ class ConditionalStyles } } + /** @return array> */ private function readConditionalsFromExt(SimpleXMLElement $extLst): array { $conditionals = []; @@ -167,6 +172,7 @@ class ConditionalStyles return $cfStyle; } + /** @return mixed[] */ private function readConditionalStyles(SimpleXMLElement $xmlSheet): array { $conditionals = []; @@ -183,9 +189,11 @@ class ConditionalStyles return $conditionals; } + /** @param mixed[] $conditionals */ private function setConditionalStyles(Worksheet $worksheet, array $conditionals, SimpleXMLElement $xmlExtLst): void { foreach ($conditionals as $cellRangeReference => $cfRules) { + /** @var mixed[] $cfRules */ ksort($cfRules); // no longer needed for Xlsx, but helps Xls $conditionalStyles = $this->readStyleRules($cfRules, $xmlExtLst); @@ -204,8 +212,14 @@ class ConditionalStyles } } + /** + * @param mixed[] $cfRules + * + * @return Conditional[] + */ private function readStyleRules(array $cfRules, SimpleXMLElement $extLst): array { + /** @var ConditionalFormattingRuleExtension[] */ $conditionalFormattingRuleExtensions = ConditionalFormattingRuleExtension::parseExtLstXml($extLst); $conditionalStyles = []; @@ -261,6 +275,7 @@ class ConditionalStyles return $conditionalStyles; } + /** @param ConditionalFormattingRuleExtension[] $conditionalFormattingRuleExtensions */ private function readDataBarOfConditionalRule(SimpleXMLElement $cfRule, array $conditionalFormattingRuleExtensions): ConditionalDataBar { $dataBar = new ConditionalDataBar(); @@ -274,6 +289,7 @@ class ConditionalStyles $cfvoXml = $cfRule->dataBar->cfvo; $cfvoIndex = 0; foreach ((count($cfvoXml) > 1 ? $cfvoXml : [$cfvoXml]) as $cfvo) { //* @phpstan-ignore-line + /** @var SimpleXMLElement $cfvo */ if ($cfvoIndex === 0) { $dataBar->setMinimumConditionalFormatValueObject(new ConditionalFormatValueObject((string) $cfvo['type'], (string) $cfvo['val'])); } @@ -296,6 +312,7 @@ class ConditionalStyles private function readColorScale(SimpleXMLElement|stdClass $cfRule): ConditionalColorScale { $colorScale = new ConditionalColorScale(); + /** @var SimpleXMLElement $cfRule */ $count = count($cfRule->colorScale->cfvo); $idx = 0; foreach ($cfRule->colorScale->cfvo as $cfvoXml) { @@ -332,11 +349,13 @@ class ConditionalStyles return $colorScale; } + /** @param ConditionalFormattingRuleExtension[] $conditionalFormattingRuleExtensions */ private function readDataBarExtLstOfConditionalRule(ConditionalDataBar $dataBar, SimpleXMLElement $cfRule, array $conditionalFormattingRuleExtensions): void { if (isset($cfRule->extLst)) { $ns = $cfRule->extLst->getNamespaces(true); foreach ((count($cfRule->extLst) > 0 ? $cfRule->extLst->ext : [$cfRule->extLst->ext]) as $ext) { //* @phpstan-ignore-line + /** @var SimpleXMLElement $ext */ $extId = (string) $ext->children($ns['x14'])->id; if (isset($conditionalFormattingRuleExtensions[$extId]) && (string) $ext['uri'] === '{B025F937-C7B1-47D3-B67F-A62EFF666E3E}') { $dataBar->setConditionalFormattingRuleExt($conditionalFormattingRuleExtensions[$extId]); diff --git a/src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php b/src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php index 3eae5bb39..2c2d4af4b 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php @@ -11,6 +11,7 @@ class Hyperlinks { private Worksheet $worksheet; + /** @var string[] */ private array $hyperlinks = []; public function __construct(Worksheet $workSheet) @@ -44,7 +45,7 @@ class Hyperlinks foreach (Coordinate::extractAllCellReferencesInRange($attributes->ref) as $cellReference) { $cell = $worksheet->getCell($cellReference); if (isset($linkRel['id'])) { - $hyperlinkUrl = $this->hyperlinks[(string) $linkRel['id']] ?? null; + $hyperlinkUrl = $this->hyperlinks[(string) $linkRel['id']] ?? ''; if (isset($attributes['location'])) { $hyperlinkUrl .= '#' . (string) $attributes['location']; } diff --git a/src/PhpSpreadsheet/Reader/Xlsx/PageSetup.php b/src/PhpSpreadsheet/Reader/Xlsx/PageSetup.php index ca6ae0b09..766154e5b 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/PageSetup.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/PageSetup.php @@ -18,6 +18,11 @@ class PageSetup extends BaseParserClass $this->worksheetXml = $worksheetXml; } + /** + * @param mixed[] $unparsedLoadedData + * + * @return mixed[] + */ public function load(array $unparsedLoadedData): array { $worksheetXml = $this->worksheetXml; @@ -46,6 +51,11 @@ class PageSetup extends BaseParserClass } } + /** + * @param mixed[] $unparsedLoadedData + * + * @return mixed[] + */ private function pageSetup(SimpleXMLElement $xmlSheet, Worksheet $worksheet, array $unparsedLoadedData): array { if ($xmlSheet->pageSetup) { @@ -82,6 +92,7 @@ class PageSetup extends BaseParserClass if (!str_ends_with($relid, 'ps')) { $relid .= 'ps'; } + /** @var mixed[][][] $unparsedLoadedData */ $unparsedLoadedData['sheets'][$worksheet->getCodeName()]['pageSetupRelId'] = $relid; } } diff --git a/src/PhpSpreadsheet/Reader/Xlsx/Properties.php b/src/PhpSpreadsheet/Reader/Xlsx/Properties.php index 1a0517b19..a792235af 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/Properties.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/Properties.php @@ -79,7 +79,9 @@ class Properties $cellDataOfficeChildren = $xmlProperty->children('http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes'); $attributeType = $cellDataOfficeChildren->getName(); - $attributeValue = (string) $cellDataOfficeChildren->{$attributeType}; + /** @var SimpleXMLElement */ + $attributeValue = $cellDataOfficeChildren->{$attributeType}; + $attributeValue = (string) $attributeValue; $attributeValue = DocumentProperties::convertProperty($attributeValue, $attributeType); $attributeType = DocumentProperties::convertPropertyType($attributeType); $this->docProps->setCustomProperty($propertyName, $attributeValue, $attributeType); @@ -88,6 +90,7 @@ class Properties } } + /** @param null|false|scalar[] $array */ private function getArrayItem(null|array|false $array): string { return is_array($array) ? (string) ($array[0] ?? '') : ''; diff --git a/src/PhpSpreadsheet/Reader/Xlsx/Styles.php b/src/PhpSpreadsheet/Reader/Xlsx/Styles.php index c1e27e9e3..5729add76 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/Styles.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/Styles.php @@ -23,10 +23,13 @@ class Styles extends BaseParserClass */ private ?Theme $theme = null; + /** @var mixed[] */ private array $workbookPalette = []; + /** @var mixed[] */ private array $styles = []; + /** @var array */ private array $cellStyles = []; private SimpleXMLElement $styleXml; @@ -38,6 +41,7 @@ class Styles extends BaseParserClass $this->namespace = $namespace; } + /** @param mixed[] $palette */ public function setWorkbookPalette(array $palette): void { $this->workbookPalette = $palette; @@ -63,6 +67,10 @@ class Styles extends BaseParserClass $this->theme = $theme; } + /** + * @param mixed[] $styles + * @param array $cellStyles + */ public function setStyleBaseData(?Theme $theme = null, array $styles = [], array $cellStyles = []): void { $this->theme = $theme; @@ -310,9 +318,12 @@ class Styles extends BaseParserClass if ($style instanceof SimpleXMLElement) { $this->readNumberFormat($docStyle->getNumberFormat(), $style->numFmt); } else { - $docStyle->getNumberFormat()->setFormatCode(self::formatGeneral((string) $style->numFmt)); + /** @var SimpleXMLElement */ + $temp = $style->numFmt; + $docStyle->getNumberFormat()->setFormatCode(self::formatGeneral((string) $temp)); } + /** @var SimpleXMLElement $style */ if (isset($style->font)) { $this->readFontStyle($docStyle->getFont(), $style->font); } @@ -416,6 +427,7 @@ class Styles extends BaseParserClass return ($background) ? 'FFFFFFFF' : 'FF000000'; } + /** @return mixed[] */ public function dxfs(bool $readDataOnly = false): array { $dxfs = []; @@ -448,7 +460,7 @@ class Styles extends BaseParserClass return $dxfs; } - // get TableStyles + /** @return TableDxfsStyle[] */ public function tableStyles(bool $readDataOnly = false): array { $tableStyles = []; @@ -488,6 +500,7 @@ class Styles extends BaseParserClass return $tableStyles; } + /** @return mixed[] */ public function styles(): array { return $this->styles; @@ -496,7 +509,7 @@ class Styles extends BaseParserClass /** * Get array item. * - * @param mixed $array (usually array, in theory can be false) + * @param false|mixed[] $array (usually array, in theory can be false) */ private static function getArrayItem(mixed $array): ?SimpleXMLElement { diff --git a/src/PhpSpreadsheet/Reader/Xlsx/TableReader.php b/src/PhpSpreadsheet/Reader/Xlsx/TableReader.php index c84b8198f..c82195866 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/TableReader.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/TableReader.php @@ -3,6 +3,7 @@ namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx; use PhpOffice\PhpSpreadsheet\Worksheet\Table; +use PhpOffice\PhpSpreadsheet\Worksheet\Table\TableDxfsStyle; use PhpOffice\PhpSpreadsheet\Worksheet\Table\TableStyle; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; use SimpleXMLElement; @@ -13,7 +14,7 @@ class TableReader private SimpleXMLElement $tableXml; - /** @var array|SimpleXMLElement */ + /** @var mixed[]|SimpleXMLElement */ private $tableAttributes; public function __construct(Worksheet $workSheet, SimpleXMLElement $tableXml) @@ -24,6 +25,9 @@ class TableReader /** * Loads Table into the Worksheet. + * + * @param TableDxfsStyle[] $tableStyles + * @param mixed[] $dxfs */ public function load(array $tableStyles, array $dxfs): void { @@ -37,13 +41,18 @@ class TableReader /** * Read Table from xml. + * + * @param TableDxfsStyle[] $tableStyles + * @param mixed[] $dxfs */ private function readTable(string $tableRange, array $tableStyles, array $dxfs): void { $table = new Table($tableRange); - $table->setName((string) ($this->tableAttributes['displayName'] ?? '')); - $table->setShowHeaderRow(((string) ($this->tableAttributes['headerRowCount'] ?? '')) !== '0'); - $table->setShowTotalsRow(((string) ($this->tableAttributes['totalsRowCount'] ?? '')) === '1'); + /** @var string[] */ + $attributes = $this->tableAttributes; + $table->setName((string) ($attributes['displayName'] ?? '')); + $table->setShowHeaderRow(((string) ($attributes['headerRowCount'] ?? '')) !== '0'); + $table->setShowTotalsRow(((string) ($attributes['totalsRowCount'] ?? '')) === '1'); $this->readTableAutoFilter($table, $this->tableXml->autoFilter); $this->readTableColumns($table, $this->tableXml->tableColumns); @@ -65,6 +74,7 @@ class TableReader } foreach ($autoFilterXml->filterColumn as $filterColumn) { + /** @var SimpleXMLElement */ $attributes = $filterColumn->attributes() ?? ['colId' => 0, 'hiddenButton' => 0]; $column = $table->getColumnByOffset((int) $attributes['colId']); $column->setShowFilterButton(((string) $attributes['hiddenButton']) !== '1'); @@ -78,6 +88,7 @@ class TableReader { $offset = 0; foreach ($tableColumnsXml->tableColumn as $tableColumn) { + /** @var SimpleXMLElement */ $attributes = $tableColumn->attributes() ?? ['totalsRowLabel' => 0, 'totalsRowFunction' => 0]; $column = $table->getColumnByOffset($offset++); @@ -99,6 +110,9 @@ class TableReader /** * Reads TableStyle from xml. + * + * @param TableDxfsStyle[] $tableStyles + * @param mixed[] $dxfs */ private function readTableStyle(Table $table, SimpleXMLElement $tableStyleInfoXml, array $tableStyles, array $dxfs): void { diff --git a/src/PhpSpreadsheet/Reader/Xlsx/WorkbookView.php b/src/PhpSpreadsheet/Reader/Xlsx/WorkbookView.php index f02de9a84..429efb089 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/WorkbookView.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/WorkbookView.php @@ -14,6 +14,7 @@ class WorkbookView $this->spreadsheet = $spreadsheet; } + /** @param array $mapSheetId */ public function viewSettings(SimpleXMLElement $xmlWorkbook, string $mainNS, array $mapSheetId, bool $readDataOnly): void { // Default active sheet index to the first loaded worksheet from the file diff --git a/src/PhpSpreadsheet/Shared/Escher/DgContainer/SpgrContainer.php b/src/PhpSpreadsheet/Shared/Escher/DgContainer/SpgrContainer.php index 84363ab20..04fb2234d 100644 --- a/src/PhpSpreadsheet/Shared/Escher/DgContainer/SpgrContainer.php +++ b/src/PhpSpreadsheet/Shared/Escher/DgContainer/SpgrContainer.php @@ -65,6 +65,7 @@ class SpgrContainer $allSpContainers[] = $child; } } + /** @var SpgrContainer\SpContainer[] $allSpContainers */ return $allSpContainers; } diff --git a/src/PhpSpreadsheet/Style/Font.php b/src/PhpSpreadsheet/Style/Font.php index 7d70dc4f7..89a099ee1 100644 --- a/src/PhpSpreadsheet/Style/Font.php +++ b/src/PhpSpreadsheet/Style/Font.php @@ -560,6 +560,7 @@ class Font extends Supervisor return $this->underlineColor; } + /** @param array{value: ?string, alpha: null|int|string, brightness?: null|int|string, type: ?string} $colorArray */ public function setUnderlineColor(array $colorArray): self { if (!$this->isSupervisor) { @@ -584,6 +585,7 @@ class Font extends Supervisor return $this->chartColor; } + /** @param array{value: ?string, alpha: null|int|string, brightness?: null|int|string, type: ?string} $colorArray */ public function setChartColor(array $colorArray): self { if (!$this->isSupervisor) { diff --git a/src/PhpSpreadsheet/Worksheet/Worksheet.php b/src/PhpSpreadsheet/Worksheet/Worksheet.php index 9863f7223..33792a4da 100644 --- a/src/PhpSpreadsheet/Worksheet/Worksheet.php +++ b/src/PhpSpreadsheet/Worksheet/Worksheet.php @@ -355,7 +355,7 @@ class Worksheet */ public function disconnectCells(): void { - if (isset($this->cellCollection)) { + if (isset($this->cellCollection)) { //* @phpstan-ignore-line $this->cellCollection->unsetWorksheetCells(); unset($this->cellCollection); } @@ -456,7 +456,7 @@ class Worksheet */ public function getCoordinates(bool $sorted = true): array { - if (!isset($this->cellCollection)) { + if (!isset($this->cellCollection)) { //* @phpstan-ignore-line return []; } @@ -2897,6 +2897,8 @@ class Worksheet * True - Return rows and columns indexed by their actual row and column IDs * @param bool $ignoreHidden False - Return values for rows/columns even if they are defined as hidden. * True - Don't return values for rows/columns that are defined as hidden. + * + * @return mixed[][] */ public function rangeToArray( string $range, @@ -3116,6 +3118,8 @@ class Worksheet * True - Return rows and columns indexed by their actual row and column IDs * @param bool $ignoreHidden False - Return values for rows/columns even if they are defined as hidden. * True - Don't return values for rows/columns that are defined as hidden. + * + * @return mixed[][] */ public function namedRangeToArray( string $definedName, @@ -3150,6 +3154,8 @@ class Worksheet * True - Return rows and columns indexed by their actual row and column IDs * @param bool $ignoreHidden False - Return values for rows/columns even if they are defined as hidden. * True - Don't return values for rows/columns that are defined as hidden. + * + * @return mixed[][] */ public function toArray( mixed $nullValue = null, diff --git a/src/PhpSpreadsheet/Writer/Html.php b/src/PhpSpreadsheet/Writer/Html.php index 4a85955c1..01a35f4d1 100644 --- a/src/PhpSpreadsheet/Writer/Html.php +++ b/src/PhpSpreadsheet/Writer/Html.php @@ -1330,7 +1330,7 @@ class Html extends BaseWriter return $html; } - /** @return array{null|''|Cell, array{}|non-falsy-string, non-empty-string} */ + /** @return array{null|''|Cell, array{}|string, non-empty-string} */ private function generateRowCellCss(Worksheet $worksheet, string $cellAddress, int $row, int $columnNumber): array { $cell = ($cellAddress > '') ? $worksheet->getCellCollection()->get($cellAddress) : ''; diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/IndirectTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/IndirectTest.php index 176d96a50..b19339c9c 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/IndirectTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/IndirectTest.php @@ -107,13 +107,13 @@ class IndirectTest extends AllSetupTeardown { $reader = new ReaderXlsx(); $file = 'tests/data/Calculation/LookupRef/IndirectFormulaSelection.xlsx'; - $this->spreadsheet = $reader->load($file); - $sheet = $this->spreadsheet->getActiveSheet(); + $spreadsheet = $this->spreadsheet = $reader->load($file); + $sheet = $spreadsheet->getActiveSheet(); $result = $sheet->getCell('A5')->getCalculatedValue(); self::assertSame(100, $result); $value = $sheet->getCell('A5')->getValue(); self::assertSame('=CURRENCY_SELECTOR', $value); - $formula = $this->spreadsheet->getNamedFormula('CURRENCY_SELECTOR'); + $formula = $spreadsheet->getNamedFormula('CURRENCY_SELECTOR'); if ($formula === null) { self::fail('Expected named formula was not defined'); } else { diff --git a/tests/PhpSpreadsheetTests/Custom/ComplexAssert.php b/tests/PhpSpreadsheetTests/Custom/ComplexAssert.php index 45b734958..ddaf16d89 100644 --- a/tests/PhpSpreadsheetTests/Custom/ComplexAssert.php +++ b/tests/PhpSpreadsheetTests/Custom/ComplexAssert.php @@ -15,7 +15,8 @@ class ComplexAssert extends TestCase public function __construct() { - parent::__construct('complexAssert'); + // Phpstan doesn't want you to use "internal" method outside PHPunit namespace + parent::__construct('complexAssert'); //* @phpstan-ignore-line } private function testExpectedExceptions(string|float $expected, string|float $actual): bool diff --git a/tests/PhpSpreadsheetTests/Helper/SampleTest.php b/tests/PhpSpreadsheetTests/Helper/SampleTest.php index 3759ff17a..6020f1d91 100644 --- a/tests/PhpSpreadsheetTests/Helper/SampleTest.php +++ b/tests/PhpSpreadsheetTests/Helper/SampleTest.php @@ -31,6 +31,7 @@ class SampleTest extends TestCase // Unfortunately some tests are too long to run with code-coverage // analysis on GitHub Actions, so we need to exclude them + /** @var string[] */ global $argv; if (in_array('--coverage-clover', $argv)) { $tooLongToBeCovered = [ diff --git a/tests/PhpSpreadsheetTests/Helper/TextGridTest.php b/tests/PhpSpreadsheetTests/Helper/TextGridTest.php index 505e9198d..236326029 100644 --- a/tests/PhpSpreadsheetTests/Helper/TextGridTest.php +++ b/tests/PhpSpreadsheetTests/Helper/TextGridTest.php @@ -11,6 +11,7 @@ use PHPUnit\Framework\TestCase; class TextGridTest extends TestCase { + /** @param mixed[] $expected */ #[DataProvider('providerTextGrid')] public function testTextGrid( bool $cli, @@ -26,8 +27,10 @@ class TextGridTest extends TestCase ['="6"', '=TEXT(A2,"yyyy-mm-dd hh:mm")', null, '1<>2'], ['xyz', '=TEXT(A3,"yyyy-mm-dd hh:mm")'], ], strictNullComparison: true); + /** @var mixed[][] */ + $temp = $sheet->toArray(null, true, true, true); $textGrid = new TextGrid( - $sheet->toArray(null, true, true, true), + $temp, $cli, rowDividers: $rowDividers, rowHeaders: $rowHeaders, @@ -136,8 +139,10 @@ class TextGridTest extends TestCase [true, false], [true, true], ], strictNullComparison: true); + /** @var mixed[][] */ + $temp = $sheet->toArray(null, true, false, true); $textGrid = new TextGrid( - $sheet->toArray(null, true, false, true), + $temp, true, rowDividers: false, rowHeaders: false, diff --git a/tests/PhpSpreadsheetTests/Reader/Html/HtmlBorderTest.php b/tests/PhpSpreadsheetTests/Reader/Html/HtmlBorderTest.php index b516de04c..d3e86fe8c 100644 --- a/tests/PhpSpreadsheetTests/Reader/Html/HtmlBorderTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Html/HtmlBorderTest.php @@ -94,6 +94,7 @@ class HtmlBorderTest extends TestCase } $tests = $this->providerBorderStyle(); foreach ($tests as $test) { + /** @var array $test */ $covered[$test[0]] = 1; } foreach ($covered as $key => $val) { diff --git a/tests/PhpSpreadsheetTests/Reader/Xls/ConditionalFormattingBasicTest.php b/tests/PhpSpreadsheetTests/Reader/Xls/ConditionalFormattingBasicTest.php index ee39c7ce2..ead2d4a26 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xls/ConditionalFormattingBasicTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Xls/ConditionalFormattingBasicTest.php @@ -7,11 +7,13 @@ namespace PhpOffice\PhpSpreadsheetTests\Reader\Xls; use PhpOffice\PhpSpreadsheet\Reader\Xls; use PhpOffice\PhpSpreadsheet\Style\Border; use PhpOffice\PhpSpreadsheet\Style\Conditional; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; class ConditionalFormattingBasicTest extends TestCase { - #[\PHPUnit\Framework\Attributes\DataProvider('conditionalFormattingProvider')] + /** @param mixed[][] $expectedRules */ + #[DataProvider('conditionalFormattingProvider')] public function testReadConditionalFormatting(string $expectedRange, array $expectedRules): void { $filename = 'tests/data/Reader/XLS/CF_Basic_Comparisons.xls'; diff --git a/tests/PhpSpreadsheetTests/Reader/Xls/ConditionalFormattingExpressionTest.php b/tests/PhpSpreadsheetTests/Reader/Xls/ConditionalFormattingExpressionTest.php index eb7a67916..62e319356 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xls/ConditionalFormattingExpressionTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Xls/ConditionalFormattingExpressionTest.php @@ -6,11 +6,13 @@ namespace PhpOffice\PhpSpreadsheetTests\Reader\Xls; use PhpOffice\PhpSpreadsheet\Reader\Xls; use PhpOffice\PhpSpreadsheet\Style\Conditional; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; class ConditionalFormattingExpressionTest extends TestCase { - #[\PHPUnit\Framework\Attributes\DataProvider('conditionalFormattingProvider')] + /** @param mixed[][] $expectedRule */ + #[DataProvider('conditionalFormattingProvider')] public function testReadConditionalFormatting(string $expectedRange, array $expectedRule): void { $filename = 'tests/data/Reader/XLS/CF_Expression_Comparisons.xls'; diff --git a/tests/PhpSpreadsheetTests/Reader/Xls/DataValidationTest.php b/tests/PhpSpreadsheetTests/Reader/Xls/DataValidationTest.php index 3d30e8ab6..621d0890a 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xls/DataValidationTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Xls/DataValidationTest.php @@ -6,11 +6,13 @@ namespace PhpOffice\PhpSpreadsheetTests\Reader\Xls; use PhpOffice\PhpSpreadsheet\Cell\DataValidation; use PhpOffice\PhpSpreadsheet\Reader\Xls; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; class DataValidationTest extends TestCase { - #[\PHPUnit\Framework\Attributes\DataProvider('dataValidationProvider')] + /** @param mixed[] $expectedRule */ + #[DataProvider('dataValidationProvider')] public function testDataValidation(string $expectedRange, array $expectedRule): void { $filename = 'tests/data/Reader/XLS/DataValidation.xls'; diff --git a/tests/PhpSpreadsheetTests/Reader/Xls/HiddenWorksheetTest.php b/tests/PhpSpreadsheetTests/Reader/Xls/HiddenWorksheetTest.php index 3a2949a51..b9aac4c7f 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xls/HiddenWorksheetTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Xls/HiddenWorksheetTest.php @@ -38,6 +38,7 @@ class HiddenWorksheetTest extends TestCase $spreadsheet->disconnectWorksheets(); } + /** @return string[][] */ private function worksheetAssertions(): array { return [ diff --git a/tests/PhpSpreadsheetTests/Reader/Xls/LoadSheetsOnlyTest.php b/tests/PhpSpreadsheetTests/Reader/Xls/LoadSheetsOnlyTest.php index c8c543afd..2c8f50ecc 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xls/LoadSheetsOnlyTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Xls/LoadSheetsOnlyTest.php @@ -30,9 +30,9 @@ class LoadSheetsOnlyTest extends TestCase //$reader->setLoadSheetsOnly(['Sheet1']); $names = $reader->listWorksheetNames($filename); $reader->setLoadSheetsOnly([$names[0]]); - $this->spreadsheet = $reader->load($filename); - self::assertSame(1, $this->spreadsheet->getSheetCount()); - self::assertSame('Sheet1', $this->spreadsheet->getActiveSheet()->getTitle()); + $spreadsheet = $this->spreadsheet = $reader->load($filename); + self::assertSame(1, $spreadsheet->getSheetCount()); + self::assertSame('Sheet1', $spreadsheet->getActiveSheet()->getTitle()); } public function testLoadSheet2Only(): void @@ -40,9 +40,9 @@ class LoadSheetsOnlyTest extends TestCase $filename = self::$testbook; $reader = new Xls(); $reader->setLoadSheetsOnly(['Sheet2']); - $this->spreadsheet = $reader->load($filename); - self::assertSame(1, $this->spreadsheet->getSheetCount()); - self::assertSame('Sheet2', $this->spreadsheet->getActiveSheet()->getTitle()); + $spreadsheet = $this->spreadsheet = $reader->load($filename); + self::assertSame(1, $spreadsheet->getSheetCount()); + self::assertSame('Sheet2', $spreadsheet->getActiveSheet()->getTitle()); } public function testLoadNoSheet(): void diff --git a/tests/PhpSpreadsheetTests/Reader/Xls/PageSetupTest.php b/tests/PhpSpreadsheetTests/Reader/Xls/PageSetupTest.php index 2eb2bc633..30f5b45ff 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xls/PageSetupTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Xls/PageSetupTest.php @@ -73,6 +73,7 @@ class PageSetupTest extends TestCase $spreadsheet->disconnectWorksheets(); } + /** @return array */ private function pageSetupAssertions(): array { return [ @@ -108,6 +109,7 @@ class PageSetupTest extends TestCase ]; } + /** @return array */ private function pageMarginAssertions(): array { return [ diff --git a/tests/PhpSpreadsheetTests/Reader/Xls/PasswordTest.php b/tests/PhpSpreadsheetTests/Reader/Xls/PasswordTest.php new file mode 100644 index 000000000..bf5a62882 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Xls/PasswordTest.php @@ -0,0 +1,55 @@ +load($filename); + $sheet = $spreadsheet->getActiveSheet(); + self::assertSame('x', $sheet->getCell('A1')->getValue()); + $spreadsheet->disconnectWorksheets(); + } + + public function testWrongPassword(): void + { + $this->expectException(ReaderException::class); + $this->expectExceptionMessage('Decryption password incorrect'); + $filename = 'tests/data/Reader/XLS/pwtest2.xls'; + $reader = new XlsReader(); + $spreadsheet = $reader->load($filename); + $sheet = $spreadsheet->getActiveSheet(); + self::assertSame('test2', $sheet->getCell('A1')->getValue()); + $spreadsheet->disconnectWorksheets(); + } + + public function testCorrectPassword(): void + { + $filename = 'tests/data/Reader/XLS/pwtest2.xls'; + $reader = new XlsReader(); + $reader->setEncryptionPassword('pwtest2'); + $spreadsheet = $reader->load($filename); + $sheet = $spreadsheet->getActiveSheet(); + self::assertSame('test2', $sheet->getCell('A1')->getValue()); + $spreadsheet->disconnectWorksheets(); + } + + public function testUnsupportedEncryption(): void + { + $this->expectException(ReaderException::class); + $this->expectExceptionMessage('Unsupported encryption algorithm'); + $filename = 'tests/data/Reader/XLS/pwtest3.xls'; + $reader = new XlsReader(); + $spreadsheet = $reader->load($filename); + $sheet = $spreadsheet->getActiveSheet(); + self::assertSame('test2', $sheet->getCell('A1')->getValue()); + $spreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/AutoFilter2Test.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/AutoFilter2Test.php index e14c6a24a..1aa9fb7d0 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xlsx/AutoFilter2Test.php +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/AutoFilter2Test.php @@ -13,6 +13,7 @@ class AutoFilter2Test extends TestCase { private const TESTBOOK = 'tests/data/Reader/XLSX/autofilter2.xlsx'; + /** @return int[] */ public function getVisibleSheet(?Worksheet $sheet, int $maxRow): array { $actualVisible = []; diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/AutoFilterEvaluateTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/AutoFilterEvaluateTest.php index 70f332a16..25045bd8c 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xlsx/AutoFilterEvaluateTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/AutoFilterEvaluateTest.php @@ -12,6 +12,7 @@ use PhpOffice\PhpSpreadsheetTests\Functional\AbstractFunctional; class AutoFilterEvaluateTest extends AbstractFunctional { + /** @return int[] */ private function getVisibleSheet(Worksheet $sheet): array { $actualVisible = []; diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/CommentTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/CommentTest.php index 1eb9eca01..13e1996f2 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xlsx/CommentTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/CommentTest.php @@ -66,9 +66,12 @@ class CommentTest extends TestCase // Spreadsheet generated by Flexcel. Make sure we managed // to locate printerSettings since it used an unexpected link. - $pageSetupRel = $spreadsheet->getUnparsedLoadedData()['sheets']['Worksheet']['pageSetupRelId'] ?? ''; + /** @var mixed[][][][][] */ + $unparsedLoadedData = $spreadsheet->getUnparsedLoadedData(); + /** @var string */ + $pageSetupRel = $unparsedLoadedData['sheets']['Worksheet']['pageSetupRelId'] ?? ''; self::assertSame('flId1ps', $pageSetupRel); - $pageSetupPath = $spreadsheet->getUnparsedLoadedData()['sheets']['Worksheet']['printerSettings'][substr($pageSetupRel, 3)]['filePath'] ?? ''; + $pageSetupPath = $unparsedLoadedData['sheets']['Worksheet']['printerSettings'][substr($pageSetupRel, 3)]['filePath'] ?? ''; self::assertSame('xl/printerSettings/printerSettings1.bin', $pageSetupPath); $spreadsheet->disconnectWorksheets(); } diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/HiddenWorksheetTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/HiddenWorksheetTest.php index f6017a120..c7a3b8175 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xlsx/HiddenWorksheetTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/HiddenWorksheetTest.php @@ -35,6 +35,7 @@ class HiddenWorksheetTest extends TestCase $spreadsheet->disconnectWorksheets(); } + /** @return array */ private function worksheetAssertions(): array { return [ diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/Issue3126Test.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/Issue3126Test.php index 8a1d3d7d1..31a98d5a9 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xlsx/Issue3126Test.php +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/Issue3126Test.php @@ -22,9 +22,12 @@ class Issue3126Test extends TestCase self::assertCount(1, $gen1Images); $gen1hfName = array_key_exists('LF', $gen1Images) ? $gen1Images['LF']->getName() : ''; self::assertSame('fleche-verte-up-right', $gen1hfName); - $pageSetupRel = $generation1->getUnparsedLoadedData()['sheets']['Worksheet']['pageSetupRelId'] ?? ''; + /** @var mixed[][][][][] */ + $unparsedLoadedData = $generation1->getUnparsedLoadedData(); + /** @var string */ + $pageSetupRel = $unparsedLoadedData['sheets']['Worksheet']['pageSetupRelId'] ?? ''; self::assertSame('rId1ps', $pageSetupRel); - $pageSetupPath = $generation1->getUnparsedLoadedData()['sheets']['Worksheet']['printerSettings'][substr($pageSetupRel, 3)]['filePath'] ?? ''; + $pageSetupPath = $unparsedLoadedData['sheets']['Worksheet']['printerSettings'][substr($pageSetupRel, 3)]['filePath'] ?? ''; self::assertSame('xl/printerSettings/printerSettings1.bin', $pageSetupPath); $generation2Name = File::temporaryFilename(); @@ -39,9 +42,12 @@ class Issue3126Test extends TestCase self::assertCount(1, $gen2Images); $gen2hfName = array_key_exists('LF', $gen2Images) ? $gen2Images['LF']->getName() : ''; self::assertSame('fleche-verte-up-right', $gen2hfName); - $pageSetupRel = $generation2->getUnparsedLoadedData()['sheets']['Worksheet']['pageSetupRelId'] ?? ''; + /** @var mixed[][][][][] */ + $unparsedLoadedData = $generation2->getUnparsedLoadedData(); + /** @var string */ + $pageSetupRel = $unparsedLoadedData['sheets']['Worksheet']['pageSetupRelId'] ?? ''; self::assertSame('rId1ps', $pageSetupRel); - $pageSetupPath = $generation2->getUnparsedLoadedData()['sheets']['Worksheet']['printerSettings'][substr($pageSetupRel, 3)]['filePath'] ?? ''; + $pageSetupPath = $unparsedLoadedData['sheets']['Worksheet']['printerSettings'][substr($pageSetupRel, 3)]['filePath'] ?? ''; self::assertSame('xl/printerSettings/printerSettings1.bin', $pageSetupPath); $generation3Name = File::temporaryFilename(); @@ -56,9 +62,12 @@ class Issue3126Test extends TestCase self::assertCount(1, $gen3Images); $gen3hfName = array_key_exists('LF', $gen3Images) ? $gen3Images['LF']->getName() : ''; self::assertSame('fleche-verte-up-right', $gen3hfName); - $pageSetupRel = $generation3->getUnparsedLoadedData()['sheets']['Worksheet']['pageSetupRelId'] ?? ''; + /** @var mixed[][][][][] */ + $unparsedLoadedData = $generation3->getUnparsedLoadedData(); + /** @var string */ + $pageSetupRel = $unparsedLoadedData['sheets']['Worksheet']['pageSetupRelId'] ?? ''; self::assertSame('rId1ps', $pageSetupRel); - $pageSetupPath = $generation3->getUnparsedLoadedData()['sheets']['Worksheet']['printerSettings'][substr($pageSetupRel, 3)]['filePath'] ?? ''; + $pageSetupPath = $unparsedLoadedData['sheets']['Worksheet']['printerSettings'][substr($pageSetupRel, 3)]['filePath'] ?? ''; self::assertSame('xl/printerSettings/printerSettings1.bin', $pageSetupPath); unlink($generation2Name); diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/Issue3679ImgTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/Issue3679ImgTest.php index 5c9b38074..ad4937923 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xlsx/Issue3679ImgTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/Issue3679ImgTest.php @@ -6,6 +6,7 @@ namespace PhpOffice\PhpSpreadsheetTests\Reader\Xlsx; use PhpOffice\PhpSpreadsheet\Reader\Xlsx; use PhpOffice\PhpSpreadsheetTests\Functional\AbstractFunctional; +use SimpleXMLElement; class Issue3679ImgTest extends AbstractFunctional { @@ -23,6 +24,7 @@ class Issue3679ImgTest extends AbstractFunctional if ($drawings[0] === null) { self::fail('Unexpected null drawing'); } else { + /** @var SimpleXMLElement */ $srcRect = $drawings[0]->getSrcRect(); self::assertSame('448', (string) ($srcRect['r'] ?? '')); self::assertSame('65769', (string) ($srcRect['b'] ?? '')); diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/LoadSheetsOnlyTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/LoadSheetsOnlyTest.php index 5c70a7521..92f0cd53c 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xlsx/LoadSheetsOnlyTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/LoadSheetsOnlyTest.php @@ -30,9 +30,9 @@ class LoadSheetsOnlyTest extends TestCase //$reader->setLoadSheetsOnly(['Sheet1']); $names = $reader->listWorksheetNames($filename); $reader->setLoadSheetsOnly([$names[0]]); - $this->spreadsheet = $reader->load($filename); - self::assertSame(1, $this->spreadsheet->getSheetCount()); - self::assertSame('Sheet1', $this->spreadsheet->getActiveSheet()->getTitle()); + $spreadsheet = $this->spreadsheet = $reader->load($filename); + self::assertSame(1, $spreadsheet->getSheetCount()); + self::assertSame('Sheet1', $spreadsheet->getActiveSheet()->getTitle()); } public function testLoadSheet2Only(): void @@ -40,9 +40,9 @@ class LoadSheetsOnlyTest extends TestCase $filename = self::$testbook; $reader = new Xlsx(); $reader->setLoadSheetsOnly(['Sheet2']); - $this->spreadsheet = $reader->load($filename); - self::assertSame(1, $this->spreadsheet->getSheetCount()); - self::assertSame('Sheet2', $this->spreadsheet->getActiveSheet()->getTitle()); + $spreadsheet = $this->spreadsheet = $reader->load($filename); + self::assertSame(1, $spreadsheet->getSheetCount()); + self::assertSame('Sheet2', $spreadsheet->getActiveSheet()->getTitle()); } public function testLoadNoSheet(): void diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/PageSetupTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/PageSetupTest.php index 763dcfb07..bac85dbce 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xlsx/PageSetupTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/PageSetupTest.php @@ -67,6 +67,7 @@ class PageSetupTest extends TestCase $spreadsheet->disconnectWorksheets(); } + /** @return array */ private function pageSetupAssertions(): array { return [ @@ -102,6 +103,7 @@ class PageSetupTest extends TestCase ]; } + /** @return array */ private function pageMarginAssertions(): array { return [ diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/SheetProtectionTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/SheetProtectionTest.php index 123b39eb3..870f5bc8d 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xlsx/SheetProtectionTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/SheetProtectionTest.php @@ -43,6 +43,7 @@ class SheetProtectionTest extends AbstractFunctional $spreadsheet->disconnectWorksheets(); } + /** @return array */ private function pageSetupAssertions(): array { return [ diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/XlsxTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/XlsxTest.php index 7ce2ca7c1..bf8644f72 100644 --- a/tests/PhpSpreadsheetTests/Reader/Xlsx/XlsxTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/XlsxTest.php @@ -166,7 +166,9 @@ class XlsxTest extends TestCase self::assertEquals(Conditional::CONDITION_CELLIS, $conditionalRule->getConditionType()); self::assertEquals(Conditional::OPERATOR_BETWEEN, $conditionalRule->getOperatorType()); self::assertEquals(['200', '400'], $conditionalRule->getConditions()); - self::assertSame('#,##0.00_-"€"', $conditionalRule->getStyle()->exportArray()['numberFormat']['formatCode']); + /** @var mixed[][] */ + $temp = $conditionalRule->getStyle()->exportArray(); + self::assertSame('#,##0.00_-"€"', $temp['numberFormat']['formatCode']); $spreadsheet->disconnectWorksheets(); } diff --git a/tests/PhpSpreadsheetTests/Shared/Date2Test.php b/tests/PhpSpreadsheetTests/Shared/Date2Test.php index c109e0279..97eea7933 100644 --- a/tests/PhpSpreadsheetTests/Shared/Date2Test.php +++ b/tests/PhpSpreadsheetTests/Shared/Date2Test.php @@ -8,6 +8,7 @@ use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalculationException; use PhpOffice\PhpSpreadsheet\Cell\Cell; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Style\NumberFormat; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; class Date2Test extends TestCase @@ -37,24 +38,26 @@ class Date2Test extends TestCase Cell::setCalculateDateTimeType(-1); } - #[\PHPUnit\Framework\Attributes\DataProvider('providerTimeOnly')] + #[DataProvider('providerTimeOnly')] public function testTimeOnly(int|float $expectedResult, int|float|string $value, ?string $format = null): void { Cell::setCalculateDateTimeType(Cell::CALCULATE_TIME_FLOAT); - $this->spreadsheet = new Spreadsheet(); - self::assertSame(0, $this->spreadsheet->getActiveSheetIndex()); - $sheet = $this->spreadsheet->getActiveSheet(); - $newSheet = $this->spreadsheet->createSheet(); + $spreadsheet = $this->spreadsheet = new Spreadsheet(); + self::assertSame(0, $spreadsheet->getActiveSheetIndex()); + $sheet = $spreadsheet->getActiveSheet(); + $newSheet = $spreadsheet->createSheet(); $newSheet->getCell('B7')->setValue('Here'); $sheet->getCell('A1')->setValue($value); if ($format !== null) { - $sheet->getStyle('A1')->getNumberFormat()->setFormatCode($format); + $sheet->getStyle('A1') + ->getNumberFormat() + ->setFormatCode($format); } $sheet->setSelectedCells('B7'); - $this->spreadsheet->setActiveSheetIndex(1); + $spreadsheet->setActiveSheetIndex(1); self::assertSame($expectedResult, $sheet->getCell('A1')->getCalculatedValue()); self::assertSame('B7', $sheet->getSelectedCells()); - self::assertSame(1, $this->spreadsheet->getActiveSheetIndex()); + self::assertSame(1, $spreadsheet->getActiveSheetIndex()); } public static function providerTimeOnly(): array @@ -80,24 +83,28 @@ class Date2Test extends TestCase ]; } - #[\PHPUnit\Framework\Attributes\DataProvider('providerDateAndTime')] + #[DataProvider('providerDateAndTime')] public function testDateAndTime(int|float $expectedResult, int|float|string $value, ?string $format = null): void { - Cell::setCalculateDateTimeType(Cell::CALCULATE_DATE_TIME_FLOAT); - $this->spreadsheet = new Spreadsheet(); - self::assertSame(0, $this->spreadsheet->getActiveSheetIndex()); - $sheet = $this->spreadsheet->getActiveSheet(); - $newSheet = $this->spreadsheet->createSheet(); + Cell::setCalculateDateTimeType( + Cell::CALCULATE_DATE_TIME_FLOAT + ); + $spreadsheet = $this->spreadsheet = new Spreadsheet(); + self::assertSame(0, $spreadsheet->getActiveSheetIndex()); + $sheet = $spreadsheet->getActiveSheet(); + $newSheet = $spreadsheet->createSheet(); $newSheet->getCell('B7')->setValue('Here'); $sheet->getCell('A1')->setValue($value); if ($format !== null) { - $sheet->getStyle('A1')->getNumberFormat()->setFormatCode($format); + $sheet->getStyle('A1') + ->getNumberFormat() + ->setFormatCode($format); } $sheet->setSelectedCells('B7'); - $this->spreadsheet->setActiveSheetIndex(1); + $spreadsheet->setActiveSheetIndex(1); self::assertSame($expectedResult, $sheet->getCell('A1')->getCalculatedValue()); self::assertSame('B7', $sheet->getSelectedCells()); - self::assertSame(1, $this->spreadsheet->getActiveSheetIndex()); + self::assertSame(1, $spreadsheet->getActiveSheetIndex()); } public static function providerDateAndTime(): array @@ -123,44 +130,49 @@ class Date2Test extends TestCase ]; } - #[\PHPUnit\Framework\Attributes\DataProvider('providerAsis')] + #[DataProvider('providerAsis')] public function testDefault(int|float $expectedResult, int|float|string $value, ?string $format = null): void { - //Cell::setCalculateDateTimeType(Cell::CALCULATE_DATE_TIME_ASIS); - $this->spreadsheet = new Spreadsheet(); - self::assertSame(0, $this->spreadsheet->getActiveSheetIndex()); - $sheet = $this->spreadsheet->getActiveSheet(); - $newSheet = $this->spreadsheet->createSheet(); + $spreadsheet = $this->spreadsheet = new Spreadsheet(); + self::assertSame(0, $spreadsheet->getActiveSheetIndex()); + $sheet = $spreadsheet->getActiveSheet(); + $newSheet = $spreadsheet->createSheet(); $newSheet->getCell('B7')->setValue('Here'); $sheet->getCell('A1')->setValue($value); if ($format !== null) { - $sheet->getStyle('A1')->getNumberFormat()->setFormatCode($format); + $sheet->getStyle('A1') + ->getNumberFormat() + ->setFormatCode($format); } $sheet->setSelectedCells('B7'); - $this->spreadsheet->setActiveSheetIndex(1); + $spreadsheet->setActiveSheetIndex(1); self::assertSame($expectedResult, $sheet->getCell('A1')->getCalculatedValue()); self::assertSame('B7', $sheet->getSelectedCells()); - self::assertSame(1, $this->spreadsheet->getActiveSheetIndex()); + self::assertSame(1, $spreadsheet->getActiveSheetIndex()); } - #[\PHPUnit\Framework\Attributes\DataProvider('providerAsis')] + #[DataProvider('providerAsis')] public function testAsis(int|float $expectedResult, int|float|string $value, ?string $format = null): void { - Cell::setCalculateDateTimeType(Cell::CALCULATE_DATE_TIME_ASIS); - $this->spreadsheet = new Spreadsheet(); - self::assertSame(0, $this->spreadsheet->getActiveSheetIndex()); - $sheet = $this->spreadsheet->getActiveSheet(); - $newSheet = $this->spreadsheet->createSheet(); + Cell::setCalculateDateTimeType( + Cell::CALCULATE_DATE_TIME_ASIS + ); + $spreadsheet = $this->spreadsheet = new Spreadsheet(); + self::assertSame(0, $spreadsheet->getActiveSheetIndex()); + $sheet = $spreadsheet->getActiveSheet(); + $newSheet = $spreadsheet->createSheet(); $newSheet->getCell('B7')->setValue('Here'); $sheet->getCell('A1')->setValue($value); if ($format !== null) { - $sheet->getStyle('A1')->getNumberFormat()->setFormatCode($format); + $sheet->getStyle('A1') + ->getNumberFormat() + ->setFormatCode($format); } $sheet->setSelectedCells('B7'); - $this->spreadsheet->setActiveSheetIndex(1); + $spreadsheet->setActiveSheetIndex(1); self::assertSame($expectedResult, $sheet->getCell('A1')->getCalculatedValue()); self::assertSame('B7', $sheet->getSelectedCells()); - self::assertSame(1, $this->spreadsheet->getActiveSheetIndex()); + self::assertSame(1, $spreadsheet->getActiveSheetIndex()); } public static function providerAsis(): array diff --git a/tests/PhpSpreadsheetTests/Shared/StringHelperLocaleTest.php b/tests/PhpSpreadsheetTests/Shared/StringHelperLocaleTest.php index c4178f53a..d3c0b43df 100644 --- a/tests/PhpSpreadsheetTests/Shared/StringHelperLocaleTest.php +++ b/tests/PhpSpreadsheetTests/Shared/StringHelperLocaleTest.php @@ -32,12 +32,13 @@ class StringHelperLocaleTest extends TestCase public function testCurrency(): void { + $currentLocale = ($this->currentLocale === false) ? null : $this->currentLocale; if ($this->currentLocale === false || !setlocale(LC_ALL, 'de_DE.UTF-8', 'deu_deu.utf8')) { self::markTestSkipped('Unable to set German UTF8 locale for testing.'); } $result = StringHelper::getCurrencyCode(); self::assertSame('€', $result); - if (!setlocale(LC_ALL, $this->currentLocale)) { + if (!setlocale(LC_ALL, $currentLocale)) { self::markTestSkipped('Unable to restore default locale.'); } $result = StringHelper::getCurrencyCode(); diff --git a/tests/PhpSpreadsheetTests/SpreadsheetCopyCloneTest.php b/tests/PhpSpreadsheetTests/SpreadsheetCopyCloneTest.php index f45ed9385..1752230aa 100644 --- a/tests/PhpSpreadsheetTests/SpreadsheetCopyCloneTest.php +++ b/tests/PhpSpreadsheetTests/SpreadsheetCopyCloneTest.php @@ -29,8 +29,8 @@ class SpreadsheetCopyCloneTest extends TestCase #[DataProvider('providerCopyClone')] public function testCopyClone(string $type): void { - $this->spreadsheet = new Spreadsheet(); - $sheet = $this->spreadsheet->getActiveSheet(); + $spreadsheet = $this->spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); $sheet->setTitle('original'); $sheet->getStyle('A1')->getFont()->setName('font1'); $sheet->getStyle('A2')->getFont()->setName('font2'); @@ -45,14 +45,14 @@ class SpreadsheetCopyCloneTest extends TestCase self::assertSame('font1', $sheet->getStyle('A1')->getFont()->getName()); $sheet->setSelectedCells('A3'); if ($type === 'copy') { - $this->spreadsheet2 = $this->spreadsheet->copy(); + $spreadsheet2 = $this->spreadsheet2 = $spreadsheet->copy(); } else { - $this->spreadsheet2 = clone $this->spreadsheet; + $spreadsheet2 = $this->spreadsheet2 = clone $spreadsheet; } - self::assertSame($this->spreadsheet, $this->spreadsheet->getCalculationEngine()?->getSpreadsheet()); - self::assertSame($this->spreadsheet2, $this->spreadsheet2->getCalculationEngine()?->getSpreadsheet()); + self::assertSame($spreadsheet, $spreadsheet->getCalculationEngine()?->getSpreadsheet()); + self::assertSame($spreadsheet2, $spreadsheet2->getCalculationEngine()?->getSpreadsheet()); self::assertSame('A3', $sheet->getSelectedCells()); - $copysheet = $this->spreadsheet2->getActiveSheet(); + $copysheet = $spreadsheet2->getActiveSheet(); self::assertSame('A3', $copysheet->getSelectedCells()); self::assertSame('original', $copysheet->getTitle()); $copysheet->setTitle('unoriginal'); @@ -130,19 +130,19 @@ class SpreadsheetCopyCloneTest extends TestCase #[DataProvider('providerCopyClone2')] public function testCopyClone2(string $type, bool $suppress, bool $cache, bool $pruning, string $return): void { - $this->spreadsheet = new Spreadsheet(); - $calc = $this->spreadsheet->getCalculationEngine(); + $spreadsheet = $this->spreadsheet = new Spreadsheet(); + $calc = $spreadsheet->getCalculationEngine(); self::assertNotNull($calc); $calc->setSuppressFormulaErrors($suppress); $calc->setCalculationCacheEnabled($cache); $calc->setBranchPruningEnabled($pruning); $calc->setInstanceArrayReturnType($return); if ($type === 'copy') { - $this->spreadsheet2 = $this->spreadsheet->copy(); + $spreadsheet2 = $this->spreadsheet2 = $spreadsheet->copy(); } else { - $this->spreadsheet2 = clone $this->spreadsheet; + $spreadsheet2 = $this->spreadsheet2 = clone $spreadsheet; } - $calc2 = $this->spreadsheet2->getCalculationEngine(); + $calc2 = $spreadsheet2->getCalculationEngine(); self::assertNotNull($calc2); self::assertSame($suppress, $calc2->getSuppressFormulaErrors()); self::assertSame($cache, $calc2->getCalculationCacheEnabled()); diff --git a/tests/PhpSpreadsheetTests/SpreadsheetCoverageTest.php b/tests/PhpSpreadsheetTests/SpreadsheetCoverageTest.php index 3071e1297..338ddd60a 100644 --- a/tests/PhpSpreadsheetTests/SpreadsheetCoverageTest.php +++ b/tests/PhpSpreadsheetTests/SpreadsheetCoverageTest.php @@ -32,50 +32,50 @@ class SpreadsheetCoverageTest extends TestCase $properties = $this->spreadsheet->getProperties(); $properties->setCreator('Anyone'); $properties->setTitle('Description'); - $this->spreadsheet2 = new Spreadsheet(); + $spreadsheet2 = $this->spreadsheet2 = new Spreadsheet(); self::assertNotEquals($properties, $this->spreadsheet2->getProperties()); $properties2 = clone $properties; - $this->spreadsheet2->setProperties($properties2); - self::assertEquals($properties, $this->spreadsheet2->getProperties()); + $spreadsheet2->setProperties($properties2); + self::assertEquals($properties, $spreadsheet2->getProperties()); } public function testDocumentSecurity(): void { - $this->spreadsheet = new Spreadsheet(); - $security = $this->spreadsheet->getSecurity(); + $spreadsheet = $this->spreadsheet = new Spreadsheet(); + $security = $spreadsheet->getSecurity(); $security->setLockRevision(true); $revisionsPassword = 'revpasswd'; $security->setRevisionsPassword($revisionsPassword); - $this->spreadsheet2 = new Spreadsheet(); - self::assertNotEquals($security, $this->spreadsheet2->getSecurity()); + $spreadsheet2 = $this->spreadsheet2 = new Spreadsheet(); + self::assertNotEquals($security, $spreadsheet2->getSecurity()); $security2 = clone $security; - $this->spreadsheet2->setSecurity($security2); - self::assertEquals($security, $this->spreadsheet2->getSecurity()); + $spreadsheet2->setSecurity($security2); + self::assertEquals($security, $spreadsheet2->getSecurity()); } public function testCellXfCollection(): void { - $this->spreadsheet = new Spreadsheet(); - $sheet = $this->spreadsheet->getActiveSheet(); + $spreadsheet = $this->spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); $sheet->getStyle('A1')->getFont()->setName('font1'); $sheet->getStyle('A2')->getFont()->setName('font2'); $sheet->getStyle('A3')->getFont()->setName('font3'); $sheet->getStyle('B1')->getFont()->setName('font1'); $sheet->getStyle('B2')->getFont()->setName('font2'); - $collection = $this->spreadsheet->getCellXfCollection(); + $collection = $spreadsheet->getCellXfCollection(); self::assertCount(4, $collection); $font1Style = $collection[1]; - self::assertTrue($this->spreadsheet->cellXfExists($font1Style)); - self::assertSame('font1', $this->spreadsheet->getCellXfCollection()[1]->getFont()->getName()); + self::assertTrue($spreadsheet->cellXfExists($font1Style)); + self::assertSame('font1', $spreadsheet->getCellXfCollection()[1]->getFont()->getName()); self::assertSame('font1', $sheet->getStyle('A1')->getFont()->getName()); self::assertSame('font2', $sheet->getStyle('A2')->getFont()->getName()); self::assertSame('font3', $sheet->getStyle('A3')->getFont()->getName()); self::assertSame('font1', $sheet->getStyle('B1')->getFont()->getName()); self::assertSame('font2', $sheet->getStyle('B2')->getFont()->getName()); - $this->spreadsheet->removeCellXfByIndex(1); - self::assertFalse($this->spreadsheet->cellXfExists($font1Style)); - self::assertSame('font2', $this->spreadsheet->getCellXfCollection()[1]->getFont()->getName()); + $spreadsheet->removeCellXfByIndex(1); + self::assertFalse($spreadsheet->cellXfExists($font1Style)); + self::assertSame('font2', $spreadsheet->getCellXfCollection()[1]->getFont()->getName()); self::assertSame('Calibri', $sheet->getStyle('A1')->getFont()->getName()); self::assertSame('font2', $sheet->getStyle('A2')->getFont()->getName()); self::assertSame('font3', $sheet->getStyle('A3')->getFont()->getName()); @@ -109,14 +109,14 @@ class SpreadsheetCoverageTest extends TestCase public function testCellStyleXF(): void { - $this->spreadsheet = new Spreadsheet(); - $collection = $this->spreadsheet->getCellStyleXfCollection(); + $spreadsheet = $this->spreadsheet = new Spreadsheet(); + $collection = $spreadsheet->getCellStyleXfCollection(); self::assertCount(1, $collection); $styleXf = $collection[0]; - self::assertSame($styleXf, $this->spreadsheet->getCellStyleXfByIndex(0)); + self::assertSame($styleXf, $spreadsheet->getCellStyleXfByIndex(0)); $hash = $styleXf->getHashCode(); - self::assertSame($styleXf, $this->spreadsheet->getCellStyleXfByHashCode($hash)); - self::assertFalse($this->spreadsheet->getCellStyleXfByHashCode($hash . 'x')); + self::assertSame($styleXf, $spreadsheet->getCellStyleXfByHashCode($hash)); + self::assertFalse($spreadsheet->getCellStyleXfByHashCode($hash . 'x')); } public function testInvalidRemoveCellStyleXfByIndex(): void @@ -139,12 +139,13 @@ class SpreadsheetCoverageTest extends TestCase { $this->expectException(SSException::class); $this->expectExceptionMessage('Invalid visibility value.'); - $this->spreadsheet = new Spreadsheet(); - $this->spreadsheet->setVisibility(Spreadsheet::VISIBILITY_HIDDEN); - self::assertSame(Spreadsheet::VISIBILITY_HIDDEN, $this->spreadsheet->getVisibility()); - $this->spreadsheet->setVisibility(null); - self::assertSame(Spreadsheet::VISIBILITY_VISIBLE, $this->spreadsheet->getVisibility()); - $this->spreadsheet->setVisibility('badvalue'); + $spreadsheet = $this->spreadsheet = new Spreadsheet(); + $spreadsheet + ->setVisibility(Spreadsheet::VISIBILITY_HIDDEN); + self::assertSame(Spreadsheet::VISIBILITY_HIDDEN, $spreadsheet->getVisibility()); + $spreadsheet->setVisibility(null); + self::assertSame(Spreadsheet::VISIBILITY_VISIBLE, $spreadsheet->getVisibility()); + $spreadsheet->setVisibility('badvalue'); } public function testInvalidTabRatio(): void diff --git a/tests/PhpSpreadsheetTests/SpreadsheetDuplicateSheetTest.php b/tests/PhpSpreadsheetTests/SpreadsheetDuplicateSheetTest.php index b84026700..f7b033233 100644 --- a/tests/PhpSpreadsheetTests/SpreadsheetDuplicateSheetTest.php +++ b/tests/PhpSpreadsheetTests/SpreadsheetDuplicateSheetTest.php @@ -21,7 +21,7 @@ class SpreadsheetDuplicateSheetTest extends TestCase public function testDuplicate(): void { - $this->spreadsheet = new Spreadsheet(); + $spreadsheet = $this->spreadsheet = new Spreadsheet(); $sheet = $this->spreadsheet->getActiveSheet(); $sheet->setTitle('original'); $sheet->getCell('A1')->setValue('text1'); @@ -50,7 +50,7 @@ class SpreadsheetDuplicateSheetTest extends TestCase $newSheet->getStyle('A2')->getFont()->getBold() ); $sheetNames = []; - foreach ($this->spreadsheet->getWorksheetIterator() as $worksheet) { + foreach ($spreadsheet->getWorksheetIterator() as $worksheet) { $sheetNames[] = $worksheet->getTitle(); } $expected = ['original', 'original 1', 'added', 'added 1']; diff --git a/tests/PhpSpreadsheetTests/Style/FontTest.php b/tests/PhpSpreadsheetTests/Style/FontTest.php index 20df75b1a..389454527 100644 --- a/tests/PhpSpreadsheetTests/Style/FontTest.php +++ b/tests/PhpSpreadsheetTests/Style/FontTest.php @@ -102,6 +102,7 @@ class FontTest extends TestCase [ 'type' => 'srgbClr', 'value' => 'FF0000', + 'alpha' => null, ] ); $font2bHash = $font2->getHashCode(); diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/CalculationErrorTest.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/CalculationErrorTest.php index 8a22768ff..1bb4fc0c9 100644 --- a/tests/PhpSpreadsheetTests/Writer/Xlsx/CalculationErrorTest.php +++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/CalculationErrorTest.php @@ -29,18 +29,18 @@ class CalculationErrorTest extends AbstractFunctional public function testCalculationExceptionSuppressed(): void { - $this->spreadsheet = new Spreadsheet(); + $spreadsheet = $this->spreadsheet = new Spreadsheet(); $sheet = $this->spreadsheet->getActiveSheet(); - $calculation = Calculation::getInstance($this->spreadsheet); + $calculation = Calculation::getInstance($spreadsheet); self::assertFalse($calculation->getSuppressFormulaErrors()); $calculation->setSuppressFormulaErrors(true); $sheet->getCell('A1')->setValue('=SUM('); $sheet->getCell('A2')->setValue('=2+3'); - $this->reloadedSpreadsheet = $this->writeAndReload($this->spreadsheet, 'Xlsx'); + $spreadsheet2 = $this->reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx'); $rcalculation = Calculation::getInstance($this->reloadedSpreadsheet); self::assertFalse($rcalculation->getSuppressFormulaErrors()); $rcalculation->setSuppressFormulaErrors(true); - $rsheet = $this->reloadedSpreadsheet->getActiveSheet(); + $rsheet = $spreadsheet2->getActiveSheet(); self::assertSame('=SUM(', $rsheet->getCell('A1')->getValue()); self::assertFalse($rsheet->getCell('A1')->getCalculatedValue()); self::assertSame('=2+3', $rsheet->getCell('A2')->getValue()); @@ -53,12 +53,12 @@ class CalculationErrorTest extends AbstractFunctional { $this->expectException(CalcException::class); $this->expectExceptionMessage("Formula Error: Expecting ')'"); - $this->spreadsheet = new Spreadsheet(); - $sheet = $this->spreadsheet->getActiveSheet(); - $calculation = Calculation::getInstance($this->spreadsheet); + $spreadsheet = $this->spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $calculation = Calculation::getInstance($spreadsheet); self::assertFalse($calculation->getSuppressFormulaErrors()); $sheet->getCell('A1')->setValue('=SUM('); $sheet->getCell('A2')->setValue('=2+3'); - $this->reloadedSpreadsheet = $this->writeAndReload($this->spreadsheet, 'Xlsx'); + $this->reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx'); } } diff --git a/tests/data/Reader/XLS/pwtest.xls b/tests/data/Reader/XLS/pwtest.xls new file mode 100644 index 0000000000000000000000000000000000000000..a82e0f6fe4544202c94908f3da96f12484cd35e8 GIT binary patch literal 13824 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

FW!OOP~VZ!B_wMP7VZg>8ESlxYIhXneHY%~O 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 literal 0 HcmV?d00001