diff --git a/CHANGELOG.md b/CHANGELOG.md index 6decabdc3..274981073 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,8 @@ and this project adheres to [Semantic Versioning](https://semver.org). - Support for Conditional Formatting Color Scale. [PR #3738](https://github.com/PHPOffice/PhpSpreadsheet/pull/3738) - Support Additional Tags in Helper/Html. [Issue #3751](https://github.com/PHPOffice/PhpSpreadsheet/issues/3751) [PR #3752](https://github.com/PHPOffice/PhpSpreadsheet/pull/3752) - Writer ODS : Write Border Style for cells [Issue #3690](https://github.com/PHPOffice/PhpSpreadsheet/issues/3690) [PR #3693](https://github.com/PHPOffice/PhpSpreadsheet/pull/3693) +- Sheet Background Images [Issue #1649](https://github.com/PHPOffice/PhpSpreadsheet/issues/1649) [PR #3795](https://github.com/PHPOffice/PhpSpreadsheet/pull/3795) +- Check if Coordinate is Inside Range [PR #3779](https://github.com/PHPOffice/PhpSpreadsheet/pull/3779) ### Changed @@ -72,6 +74,7 @@ and this project adheres to [Semantic Versioning](https://semver.org). - Theme File Missing but Referenced in Spreadsheet. [Issue #3770](https://github.com/PHPOffice/PhpSpreadsheet/issues/3770) [PR #3772](https://github.com/PHPOffice/PhpSpreadsheet/pull/3772) - Slk Shared Formulas. [Issue #2267](https://github.com/PHPOffice/PhpSpreadsheet/issues/2267) [PR #3776](https://github.com/PHPOffice/PhpSpreadsheet/pull/3776) - Html omitting some charts. [Issue #3767](https://github.com/PHPOffice/PhpSpreadsheet/issues/3767) [PR #3771](https://github.com/PHPOffice/PhpSpreadsheet/pull/3771) +- Case Insensitive Comparison for Sheet Names [PR #3791](https://github.com/PHPOffice/PhpSpreadsheet/pull/3791) ## 1.29.0 - 2023-06-15 diff --git a/src/PhpSpreadsheet/Reader/Xlsx.php b/src/PhpSpreadsheet/Reader/Xlsx.php index 99fb3937d..73ac43afd 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx.php +++ b/src/PhpSpreadsheet/Reader/Xlsx.php @@ -973,6 +973,7 @@ class Xlsx extends BaseReader if ($this->readDataOnly === false) { $this->readAutoFilter($xmlSheetNS, $docSheet); + $this->readBackgroundImage($xmlSheetNS, $docSheet, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels'); } $this->readTables($xmlSheetNS, $docSheet, $dir, $fileWorksheet, $zip, $mainNS); @@ -2201,6 +2202,27 @@ class Xlsx extends BaseReader } } + private function readBackgroundImage( + SimpleXMLElement $xmlSheet, + Worksheet $docSheet, + string $relsName + ): void { + if ($xmlSheet && $xmlSheet->picture) { + $id = (string) self::getArrayItem(self::getAttributes($xmlSheet->picture, Namespaces::SCHEMA_OFFICE_DOCUMENT), 'id'); + $rels = $this->loadZip($relsName); + foreach ($rels->Relationship as $rel) { + $attrs = $rel->attributes() ?? []; + $rid = (string) ($attrs['Id'] ?? ''); + $target = (string) ($attrs['Target'] ?? ''); + if ($rid === $id && substr($target, 0, 2) === '..') { + $target = 'xl' . substr($target, 2); + $content = $this->getFromZipArchive($this->zip, $target); + $docSheet->setBackgroundImage($content); + } + } + } + } + private function readTables( SimpleXMLElement $xmlSheet, Worksheet $docSheet, diff --git a/src/PhpSpreadsheet/Worksheet/Worksheet.php b/src/PhpSpreadsheet/Worksheet/Worksheet.php index 8ed1d22eb..89c545852 100644 --- a/src/PhpSpreadsheet/Worksheet/Worksheet.php +++ b/src/PhpSpreadsheet/Worksheet/Worksheet.php @@ -3879,4 +3879,47 @@ class Worksheet implements IComparable return $xfIndex; } + + private string $backgroundImage = ''; + + private string $backgroundMime = ''; + + private string $backgroundExtension = ''; + + public function getBackgroundImage(): string + { + return $this->backgroundImage; + } + + public function getBackgroundMime(): string + { + return $this->backgroundMime; + } + + public function getBackgroundExtension(): string + { + return $this->backgroundExtension; + } + + /** + * Set background image. + * Used on read/write for Xlsx. + * Used on write for Html. + * + * @param string $backgroundImage Image represented as a string, e.g. results of file_get_contents + */ + public function setBackgroundImage(string $backgroundImage): self + { + $imageArray = getimagesizefromstring($backgroundImage) ?: ['mime' => '']; + $mime = $imageArray['mime']; + if ($mime !== '') { + $extension = explode('/', $mime); + $extension = $extension[1]; + $this->backgroundImage = $backgroundImage; + $this->backgroundMime = $mime; + $this->backgroundExtension = $extension; + } + + return $this; + } } diff --git a/src/PhpSpreadsheet/Writer/Html.php b/src/PhpSpreadsheet/Writer/Html.php index ecb99219d..b4547637c 100644 --- a/src/PhpSpreadsheet/Writer/Html.php +++ b/src/PhpSpreadsheet/Writer/Html.php @@ -878,6 +878,11 @@ class Html extends BaseWriter $css["table.sheet$sheetIndex"]['page-break-inside'] = 'avoid'; $css["table.sheet$sheetIndex"]['break-inside'] = 'avoid'; } + $picture = $sheet->getBackgroundImage(); + if ($picture !== '') { + $base64 = base64_encode($picture); + $css["table.sheet$sheetIndex"]['background-image'] = 'url(data:' . $sheet->getBackgroundMime() . ';base64,' . $base64 . ')'; + } // Build styles // Calculate column widths diff --git a/src/PhpSpreadsheet/Writer/Xlsx.php b/src/PhpSpreadsheet/Writer/Xlsx.php index c18bc8586..e127ccac9 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx.php +++ b/src/PhpSpreadsheet/Writer/Xlsx.php @@ -365,7 +365,7 @@ class Xlsx extends BaseWriter // Add worksheet relationships (drawings, ...) for ($i = 0; $i < $this->spreadSheet->getSheetCount(); ++$i) { // Add relationships - $zipContent['xl/worksheets/_rels/sheet' . ($i + 1) . '.xml.rels'] = $this->getWriterPartRels()->writeWorksheetRelationships($this->spreadSheet->getSheet($i), ($i + 1), $this->includeCharts, $tableRef1); + $zipContent['xl/worksheets/_rels/sheet' . ($i + 1) . '.xml.rels'] = $this->getWriterPartRels()->writeWorksheetRelationships($this->spreadSheet->getSheet($i), ($i + 1), $this->includeCharts, $tableRef1, $zipContent); // Add unparsedLoadedData $sheetCodeName = $this->spreadSheet->getSheet($i)->getCodeName(); diff --git a/src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php b/src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php index 8faa3ffc2..3357e657f 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php @@ -186,6 +186,13 @@ class ContentTypes extends WriterPart } } } + + $bgImage = $spreadsheet->getSheet($i)->getBackgroundImage(); + $mimeType = $spreadsheet->getSheet($i)->getBackgroundMime(); + $extension = $spreadsheet->getSheet($i)->getBackgroundExtension(); + if ($bgImage !== '' && !isset($aMediaContentTypes[$mimeType])) { + $this->writeDefaultContentType($objWriter, $extension, $mimeType); + } } // unparsed defaults diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Rels.php b/src/PhpSpreadsheet/Writer/Xlsx/Rels.php index 977dc9abf..dce5882ce 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Rels.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Rels.php @@ -169,7 +169,7 @@ class Rels extends WriterPart * * @return string XML Output */ - public function writeWorksheetRelationships(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $worksheet, $worksheetId = 1, $includeCharts = false, $tableRef = 1) + public function writeWorksheetRelationships(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $worksheet, $worksheetId = 1, $includeCharts = false, $tableRef = 1, array &$zipContent = []) { // Create XML writer $objWriter = null; @@ -221,6 +221,20 @@ class Rels extends WriterPart ); } + $backgroundImage = $worksheet->getBackgroundImage(); + if ($backgroundImage !== '') { + $rId = 'Bg'; + $uniqueName = md5(mt_rand(0, 9999) . time() . mt_rand(0, 9999)); + $relPath = "../media/$uniqueName." . $worksheet->getBackgroundExtension(); + $this->writeRelationship( + $objWriter, + $rId, + Namespaces::IMAGE, + $relPath + ); + $zipContent["xl/media/$uniqueName." . $worksheet->getBackgroundExtension()] = $backgroundImage; + } + // Write hyperlink relationships? $i = 1; foreach ($worksheet->getHyperlinkCollection() as $hyperlink) { diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php index 7efaaf939..de9cc49e7 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php @@ -139,6 +139,9 @@ class Worksheet extends WriterPart // IgnoredErrors $this->writeIgnoredErrors($objWriter); + // BackgroundImage must come after ignored, before table + $this->writeBackgroundImage($objWriter, $worksheet); + // Table $this->writeTable($objWriter, $worksheet); @@ -1042,6 +1045,9 @@ class Worksheet extends WriterPart private function writeTable(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void { $tableCount = $worksheet->getTableCollection()->count(); + if ($tableCount === 0) { + return; + } $objWriter->startElement('tableParts'); $objWriter->writeAttribute('count', (string) $tableCount); @@ -1055,6 +1061,18 @@ class Worksheet extends WriterPart $objWriter->endElement(); } + /** + * Write Background Image. + */ + private function writeBackgroundImage(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void + { + if ($worksheet->getBackgroundImage() !== '') { + $objWriter->startElement('picture'); + $objWriter->writeAttribute('r:id', 'rIdBg'); + $objWriter->endElement(); + } + } + /** * Write PageSetup. */ @@ -1098,19 +1116,31 @@ class Worksheet extends WriterPart private function writeHeaderFooter(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void { // headerFooter + $headerFooter = $worksheet->getHeaderFooter(); + $oddHeader = $headerFooter->getOddHeader(); + $oddFooter = $headerFooter->getOddFooter(); + $evenHeader = $headerFooter->getEvenHeader(); + $evenFooter = $headerFooter->getEvenFooter(); + $firstHeader = $headerFooter->getFirstHeader(); + $firstFooter = $headerFooter->getFirstFooter(); + if ("$oddHeader$oddFooter$evenHeader$evenFooter$firstHeader$firstFooter" === '') { + return; + } + $objWriter->startElement('headerFooter'); $objWriter->writeAttribute('differentOddEven', ($worksheet->getHeaderFooter()->getDifferentOddEven() ? 'true' : 'false')); $objWriter->writeAttribute('differentFirst', ($worksheet->getHeaderFooter()->getDifferentFirst() ? 'true' : 'false')); $objWriter->writeAttribute('scaleWithDoc', ($worksheet->getHeaderFooter()->getScaleWithDocument() ? 'true' : 'false')); $objWriter->writeAttribute('alignWithMargins', ($worksheet->getHeaderFooter()->getAlignWithMargins() ? 'true' : 'false')); - $objWriter->writeElement('oddHeader', $worksheet->getHeaderFooter()->getOddHeader()); - $objWriter->writeElement('oddFooter', $worksheet->getHeaderFooter()->getOddFooter()); - $objWriter->writeElement('evenHeader', $worksheet->getHeaderFooter()->getEvenHeader()); - $objWriter->writeElement('evenFooter', $worksheet->getHeaderFooter()->getEvenFooter()); - $objWriter->writeElement('firstHeader', $worksheet->getHeaderFooter()->getFirstHeader()); - $objWriter->writeElement('firstFooter', $worksheet->getHeaderFooter()->getFirstFooter()); - $objWriter->endElement(); + self::writeElementIf($objWriter, $oddHeader !== '', 'oddHeader', $oddHeader); + self::writeElementIf($objWriter, $oddFooter !== '', 'oddFooter', $oddFooter); + self::writeElementIf($objWriter, $evenHeader !== '', 'evenHeader', $evenHeader); + self::writeElementIf($objWriter, $evenFooter !== '', 'evenFooter', $evenFooter); + self::writeElementIf($objWriter, $firstHeader !== '', 'firstHeader', $firstHeader); + self::writeElementIf($objWriter, $firstFooter !== '', 'firstFooter', $firstFooter); + + $objWriter->endElement(); // headerFooter } /** diff --git a/tests/PhpSpreadsheetTests/Writer/Html/BackgroundImageTest.php b/tests/PhpSpreadsheetTests/Writer/Html/BackgroundImageTest.php new file mode 100644 index 000000000..df73c9cd8 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Writer/Html/BackgroundImageTest.php @@ -0,0 +1,31 @@ +getActiveSheet(); + $sheet->getCell('A1')->setValue(1); + $sheet->getCell('B1')->setValue(2); + $sheet->getCell('A2')->setValue(3); + $sheet->getCell('B2')->setValue(4); + $imageFile = 'tests/data/Writer/XLSX/backgroundtest.png'; + $image = (string) file_get_contents($imageFile); + $sheet->setBackgroundImage($image); + self::assertSame('image/png', $sheet->getBackgroundMime()); + self::assertSame('png', $sheet->getBackgroundExtension()); + $writer = new Html($spreadsheet); + $header = $writer->generateHTMLHeader(true); + self::assertStringContainsString('table.sheet0 { background-image:url(data:image/png;base64,', $header); + $spreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/BackgroundImageTest.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/BackgroundImageTest.php new file mode 100644 index 000000000..73a32db3a --- /dev/null +++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/BackgroundImageTest.php @@ -0,0 +1,50 @@ +getActiveSheet(); + $sheet->getCell('A1')->setValue(1); + $sheet->getCell('B1')->setValue(2); + $sheet->getCell('A2')->setValue(3); + $sheet->getCell('B2')->setValue(4); + $imageFile = 'tests/data/Writer/XLSX/backgroundtest.png'; + $image = (string) file_get_contents($imageFile); + $sheet->setBackgroundImage($image); + self::assertSame('image/png', $sheet->getBackgroundMime()); + self::assertSame('png', $sheet->getBackgroundExtension()); + + $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx'); + $spreadsheet->disconnectWorksheets(); + $reloadedWorksheet = $reloadedSpreadsheet->getActiveSheet(); + self::assertSame($image, $reloadedWorksheet->getBackgroundImage()); + self::assertSame('image/png', $reloadedWorksheet->getBackgroundMime()); + self::assertSame('png', $reloadedWorksheet->getBackgroundExtension()); + self::assertSame(2, $reloadedWorksheet->getCell('B1')->getValue()); + $reloadedSpreadsheet->disconnectWorksheets(); + } + + public function testInvalidImage(): void + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->getCell('A1')->setValue(1); + $imageFile = __FILE__; + $image = (string) file_get_contents($imageFile); + self::assertNotSame('', $image); + $sheet->setBackgroundImage($image); + self::assertSame('', $sheet->getBackgroundImage()); + self::assertSame('', $sheet->getBackgroundMime()); + self::assertSame('', $sheet->getBackgroundExtension()); + $spreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/data/Writer/XLSX/backgroundtest.png b/tests/data/Writer/XLSX/backgroundtest.png new file mode 100644 index 000000000..3f14e4b3f Binary files /dev/null and b/tests/data/Writer/XLSX/backgroundtest.png differ