diff --git a/CHANGELOG.md b/CHANGELOG.md index ad246e48c8..e5633d7984 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org). Thia is a ### Fixed +- Protected ranges and insert/delete rows/columns. [Issue #4695](https://github.com/PHPOffice/PhpSpreadsheet/issues/4695) [PR #4702](https://github.com/PHPOffice/PhpSpreadsheet/pull/4702) - Unexpected Exception in Php DateTime. [Issue #4696](https://github.com/PHPOffice/PhpSpreadsheet/issues/4696) [Issue #917](https://github.com/PHPOffice/PhpSpreadsheet/issues/917) [PR #4697](https://github.com/PHPOffice/PhpSpreadsheet/pull/4697) ## 2025-10-25 - 5.2.0 diff --git a/src/PhpSpreadsheet/ReferenceHelper.php b/src/PhpSpreadsheet/ReferenceHelper.php index be6539806e..65cb5e61bf 100644 --- a/src/PhpSpreadsheet/ReferenceHelper.php +++ b/src/PhpSpreadsheet/ReferenceHelper.php @@ -330,15 +330,37 @@ protected function adjustMergeCells(Worksheet $worksheet): void protected function adjustProtectedCells(Worksheet $worksheet, int $numberOfColumns, int $numberOfRows): void { $aProtectedCells = $worksheet->getProtectedCellRanges(); - ($numberOfColumns > 0 || $numberOfRows > 0) - ? uksort($aProtectedCells, [self::class, 'cellReverseSort']) - : uksort($aProtectedCells, [self::class, 'cellSort']); - foreach ($aProtectedCells as $cellAddress => $protectedRange) { - $newReference = $this->updateCellReference($cellAddress); - if ($cellAddress !== $newReference) { - $worksheet->unprotectCells($cellAddress); - if ($newReference) { - $worksheet->protectCells($newReference, $protectedRange->getPassword(), true); + /** @var CellReferenceHelper */ + $cellReferenceHelper = $this->cellReferenceHelper; + if ($numberOfRows >= 0 && $numberOfColumns >= 0) { + foreach ($aProtectedCells as $key2 => $value) { + $ranges = $value->allRanges(); + $newKey = $separator = ''; + foreach ($ranges as $key => $range) { + $oldKey = $range[0] . (array_key_exists(1, $range) ? (':' . $range[1]) : ''); + $newKey .= $separator . $this->updateCellReference($oldKey); + $separator = ' '; + } + if ($key2 !== $newKey) { + $worksheet->unprotectCells($key2); + $worksheet->protectCells($newKey, $value->getPassword(), true, $value->getName(), $value->getSecurityDescriptor()); + } + } + } else { + foreach ($aProtectedCells as $key2 => $value) { + $range = str_replace([' ', ',', "\0"], ["\0", ' ', ','], $key2); + $extracted = Coordinate::extractAllCellReferencesInRange($range); + $outArray = []; + foreach ($extracted as $cellAddress) { + if (!$cellReferenceHelper->cellAddressInDeleteRange($cellAddress)) { + $outArray[$this->updateCellReference($cellAddress)] = 'x'; + } + } + $outArray2 = Coordinate::mergeRangesInCollection($outArray); + $newKey = implode(' ', array_keys($outArray2)); + if ($key2 !== $newKey) { + $worksheet->unprotectCells($key2); + $worksheet->protectCells($newKey, $value->getPassword(), true, $value->getName(), $value->getSecurityDescriptor()); } } } diff --git a/tests/PhpSpreadsheetTests/Worksheet/ProtectedRangeTest.php b/tests/PhpSpreadsheetTests/Worksheet/ProtectedRangeTest.php new file mode 100644 index 0000000000..4210518b59 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Worksheet/ProtectedRangeTest.php @@ -0,0 +1,59 @@ +getActiveSheet(); + $sheet->protectCells('B2:D4 J2:L4 F2:H4', name: 'ProtectedBlock1'); + $sheet->protectCells('M2:O4 Q7:R9 T1:T3'); + $sheet->protectCells('B8 C9 D1', name: 'ProtectedBlock3'); + $sheet->insertNewRowBefore(2); + $ranges = $sheet->getProtectedCellRanges(); + $rangeKeys = array_keys($ranges); + self::assertSame( + [ + 'B3:D5 J3:L5 F3:H5', + 'M3:O5 Q8:R10 T1:T4', + 'B9 C10 D1', + ], + $rangeKeys + ); + self::assertSame('ProtectedBlock1', $ranges[$rangeKeys[0]]->getName()); + self::assertSame('ProtectedBlock3', $ranges[$rangeKeys[2]]->getName()); + $spreadsheet->disconnectWorksheets(); + } + + public function testRemoveRow1(): void + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->protectCells('B2:D4 J2:L4 F2:H4'); + $sheet->protectCells('M2:O4 Q7:R9 T1:T3', name: 'ProtectedBlock2'); + $sheet->protectCells('B8 C9 D1 E3'); + $sheet->removeRow(3); + $ranges = $sheet->getProtectedCellRanges(); + $rangeKeys = array_keys($ranges); + // PhpSpreadsheet has methods to merge cell addresses in a row, + // but not in a column. So the results here are not as concise as + // they might be, but they are nevertheless accurate. + self::assertSame( + [ + 'B2:B3 C2:C3 D2:D3 F2:F3 G2:G3 H2:H3 J2:J3 K2:K3 L2:L3', + 'M2:M3 N2:N3 O2:O3 Q6:Q8 R6:R8 T1:T2', + 'B7 C8 D1', + ], + $rangeKeys + ); + self::assertSame('ProtectedBlock2', $ranges[$rangeKeys[1]]->getName()); + $spreadsheet->disconnectWorksheets(); + } +}