diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f13b39286..7900e663f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org). - Micro-optimization for excelToDateTimeObject. [Issue #4438](https://github.com/PHPOffice/PhpSpreadsheet/issues/4438) [PR #4442](https://github.com/PHPOffice/PhpSpreadsheet/pull/4442) - Print Area and Row Break. [Issue #1275](https://github.com/PHPOffice/PhpSpreadsheet/issues/1275) [PR #4450](https://github.com/PHPOffice/PhpSpreadsheet/pull/4450) +- Xls Writer Treat Hyperlink Starting with # as Internal. [Issue #56](https://github.com/PHPOffice/PhpSpreadsheet/issues/56) [PR #4453](https://github.com/PHPOffice/PhpSpreadsheet/pull/4453) ## 2025-04-16 - 4.2.0 diff --git a/src/PhpSpreadsheet/Cell/Hyperlink.php b/src/PhpSpreadsheet/Cell/Hyperlink.php index 3117a7d86d..5f1c521620 100644 --- a/src/PhpSpreadsheet/Cell/Hyperlink.php +++ b/src/PhpSpreadsheet/Cell/Hyperlink.php @@ -68,11 +68,11 @@ public function setTooltip(string $tooltip): static } /** - * Is this hyperlink internal? (to another worksheet). + * Is this hyperlink internal? (to another worksheet or a cell in this worksheet). */ public function isInternal(): bool { - return str_contains($this->url, 'sheet://'); + return str_starts_with($this->url, 'sheet://') || str_starts_with($this->url, '#'); } public function getTypeHyperlink(): string diff --git a/src/PhpSpreadsheet/Writer/Xls/Worksheet.php b/src/PhpSpreadsheet/Writer/Xls/Worksheet.php index 0207a14a2b..8744f9feee 100644 --- a/src/PhpSpreadsheet/Writer/Xls/Worksheet.php +++ b/src/PhpSpreadsheet/Writer/Xls/Worksheet.php @@ -467,8 +467,9 @@ public function close(): void [$column, $row] = Coordinate::indexesFromString($coordinate); $url = $hyperlink->getUrl(); - - if (str_contains($url, 'sheet://')) { + if ($url[0] === '#') { + $url = "internal:$url"; + } elseif (str_starts_with($url, 'sheet://')) { // internal to current workbook $url = str_replace('sheet://', 'internal:', $url); } elseif (Preg::isMatch('/^(http:|https:|ftp:|mailto:)/', $url)) { @@ -955,12 +956,11 @@ private function writeUrlRange(int $row1, int $col1, int $row2, int $col2, strin // Check for internal/external sheet links or default to web link if (Preg::isMatch('[^internal:]', $url)) { $this->writeUrlInternal($row1, $col1, $row2, $col2, $url); - } - if (Preg::isMatch('[^external:]', $url)) { + } elseif (Preg::isMatch('[^external:]', $url)) { $this->writeUrlExternal($row1, $col1, $row2, $col2, $url); + } else { + $this->writeUrlWeb($row1, $col1, $row2, $col2, $url); } - - $this->writeUrlWeb($row1, $col1, $row2, $col2, $url); } /** diff --git a/tests/PhpSpreadsheetTests/Writer/Xls/HyperlinkTest.php b/tests/PhpSpreadsheetTests/Writer/Xls/HyperlinkTest.php new file mode 100644 index 0000000000..6b8cbb5bf8 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Writer/Xls/HyperlinkTest.php @@ -0,0 +1,38 @@ +getActiveSheet(); + $sheet1->setTitle('First'); + $sheet2 = $spreadsheet->createSheet(); + $sheet2->setTitle('Second'); + $sheet2->setCellValue('A100', 'other sheet'); + $sheet1->setCellValue('A100', 'this sheet'); + $sheet1->setCellValue('A1', '=HYPERLINK("#A100", "here")'); + $sheet1->setCellValue('A2', '=HYPERLINK("#Second!A100", "there")'); + $sheet1->setCellValue('A3', '=HYPERLINK("http://example.com", "external")'); + $sheet1->setCellValue('A4', 'gotoA101'); + $sheet1->getCell('A4') + ->getHyperlink() + ->setUrl('#A101'); + + $robj = $this->writeAndReload($spreadsheet, 'Xls'); + $spreadsheet->disconnectWorksheets(); + $sheet0 = $robj->setActiveSheetIndex(0); + self::assertSame('sheet://#A100', $sheet0->getCell('A1')->getHyperlink()->getUrl()); + self::assertSame('sheet://#Second!A100', $sheet0->getCell('A2')->getHyperlink()->getUrl()); + self::assertSame('http://example.com', $sheet0->getCell('A3')->getHyperlink()->getUrl()); + self::assertSame('sheet://#A101', $sheet0->getCell('A4')->getHyperlink()->getUrl()); + $robj->disconnectWorksheets(); + } +} diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/HyperlinkTest.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/HyperlinkTest.php new file mode 100644 index 0000000000..ba7fa2087e --- /dev/null +++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/HyperlinkTest.php @@ -0,0 +1,38 @@ +getActiveSheet(); + $sheet1->setTitle('First'); + $sheet2 = $spreadsheet->createSheet(); + $sheet2->setTitle('Second'); + $sheet2->setCellValue('A100', 'other sheet'); + $sheet1->setCellValue('A100', 'this sheet'); + $sheet1->setCellValue('A1', '=HYPERLINK("#A100", "here")'); + $sheet1->setCellValue('A2', '=HYPERLINK("#Second!A100", "there")'); + $sheet1->setCellValue('A3', '=HYPERLINK("http://example.com", "external")'); + $sheet1->setCellValue('A4', 'gotoA101'); + $sheet1->getCell('A4') + ->getHyperlink() + ->setUrl('#A101'); + + $robj = $this->writeAndReload($spreadsheet, 'Xlsx'); + $spreadsheet->disconnectWorksheets(); + $sheet0 = $robj->setActiveSheetIndex(0); + self::assertSame('sheet://#A100', $sheet0->getCell('A1')->getHyperlink()->getUrl()); + self::assertSame('sheet://#Second!A100', $sheet0->getCell('A2')->getHyperlink()->getUrl()); + self::assertSame('http://example.com', $sheet0->getCell('A3')->getHyperlink()->getUrl()); + self::assertSame('sheet://#A101', $sheet0->getCell('A4')->getHyperlink()->getUrl()); + $robj->disconnectWorksheets(); + } +}