diff --git a/src/PhpSpreadsheet/Reader/Xlsx.php b/src/PhpSpreadsheet/Reader/Xlsx.php index b54622cd37..1c8e66bce2 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx.php +++ b/src/PhpSpreadsheet/Reader/Xlsx.php @@ -655,6 +655,32 @@ public function load($pFilename) } } + // Set protection + if ($xmlWorkbook->workbookProtection) { + if ($xmlWorkbook->workbookProtection['lockRevision']) { + $excel->getSecurity()->setLockRevision((bool) $xmlWorkbook->workbookProtection['lockRevision']); + } + if ($xmlWorkbook->workbookProtection['lockStructure']) { + $excel->getSecurity()->setLockStructure((bool) $xmlWorkbook->workbookProtection['lockStructure']); + } + if ($xmlWorkbook->workbookProtection['lockWindows']) { + $excel->getSecurity()->setLockWindows((bool) $xmlWorkbook->workbookProtection['lockWindows']); + } + if ($xmlWorkbook->workbookProtection['revisionsPassword']) { + $excel->getSecurity()->setRevisionPassword((string) $xmlWorkbook->workbookProtection['revisionsPassword'], true); + } + if ($xmlWorkbook->workbookProtection['workbookPassword']) { + $excel->getSecurity()->setWorkbookPassword((string) $xmlWorkbook->workbookProtection['workbookPassword'], true); + } + } + + // Set workbookView + /* + if ($xmlWorkbook->bookViews && $xmlWorkbook->bookViews->workbookView) { + // activeTab will be setup later + } + */ + $sheetId = 0; // keep track of new sheet id in final workbook $oldSheetId = -1; // keep track of old sheet id in final workbook $countSkippedSheets = 0; // keep track of number of skipped sheets @@ -1185,6 +1211,11 @@ public function load($pFilename) self::boolean((string) $xmlSheet->pageSetup['useFirstPageNumber'])) { $docPageSetup->setFirstPageNumber((int) ($xmlSheet->pageSetup['firstPageNumber'])); } + + $relAttributes = $xmlSheet->pageSetup->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + if (isset($relAttributes['id'])) { + $excel->unparsedLoadedData['sheets'][$docSheet->getCodeName()]['pageSetupRelId'] = (string) $relAttributes['id']; + } } if ($xmlSheet && $xmlSheet->headerFooter && !$this->readDataOnly) { @@ -1268,6 +1299,16 @@ public function load($pFilename) } } + // unparsed sheet AlternateContent + if ($xmlSheet && !$this->readDataOnly) { + $mc = $xmlSheet->children('http://schemas.openxmlformats.org/markup-compatibility/2006'); + if ($mc->AlternateContent) { + foreach ($mc->AlternateContent as $alternateContent) { + $excel->unparsedLoadedData['sheets'][$docSheet->getCodeName()]['AlternateContents'][] = $alternateContent->asXML(); + } + } + } + // Add hyperlinks $hyperlinks = []; if (!$this->readDataOnly) { @@ -1367,6 +1408,9 @@ public function load($pFilename) } } + // later we will remove from it real vmlComments + $unparsedVmlDrawings = $vmlComments; + // Loop through VML comments foreach ($vmlComments as $relName => $relPath) { // Load VML comments file @@ -1431,11 +1475,26 @@ public function load($pFilename) $comment->setVisible($stylePair[1] == 'visible'); } } + + unset($unparsedVmlDrawings[$relName]); } } } } + // unparsed vmlDrawing + if ($unparsedVmlDrawings) { + foreach ($unparsedVmlDrawings as $rId => $relPath) { + $rId = substr($rId, 3); // rIdXXX + $unparsedVmlDrawing = &$excel->unparsedLoadedData['sheets'][$docSheet->getCodeName()]['vmlDrawings']; + $unparsedVmlDrawing[$rId] = []; + $unparsedVmlDrawing[$rId]['filePath'] = self::dirAdd("$dir/$fileWorksheet", $relPath); + $unparsedVmlDrawing[$rId]['relFilePath'] = $relPath; + $unparsedVmlDrawing[$rId]['content'] = $this->securityScan($this->getFromZipArchive($zip, $unparsedVmlDrawing[$rId]['filePath'])); + unset($unparsedVmlDrawing); + } + } + // Header/footer images if ($xmlSheet && $xmlSheet->legacyDrawingHF && !$this->readDataOnly) { if ($zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) { @@ -1680,9 +1739,86 @@ public function load($pFilename) } } } + + // store original rId of drawing files + $excel->unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingOriginalIds'] = []; + foreach ($relsWorksheet->Relationship as $ele) { + if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing') { + $excel->unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingOriginalIds'][(string) $ele['Target']] = (string) $ele['Id']; + } + } + + // unparsed drawing AlternateContent + $xmlAltDrawing = simplexml_load_string( + $this->securityScan($this->getFromZipArchive($zip, $fileDrawing)), + 'SimpleXMLElement', + Settings::getLibXmlLoaderOptions() + )->children('http://schemas.openxmlformats.org/markup-compatibility/2006'); + + if ($xmlAltDrawing->AlternateContent) { + foreach ($xmlAltDrawing->AlternateContent as $alternateContent) { + $excel->unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingAlternateContents'][] = $alternateContent->asXML(); + } + } } } + // form control properties + if ($zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) { + $relsWorksheet = simplexml_load_string( + //~ http://schemas.openxmlformats.org/package/2006/relationships" + $this->securityScan( + $this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels') + ), + 'SimpleXMLElement', + Settings::getLibXmlLoaderOptions() + ); + $ctrlProps = []; + foreach ($relsWorksheet->Relationship as $ele) { + if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/ctrlProp') { + $ctrlProps[(string) $ele['Id']] = $ele; + } + } + + $unparsedCtrlProps = &$excel->unparsedLoadedData['sheets'][$docSheet->getCodeName()]['ctrlProps']; + foreach ($ctrlProps as $rId => $ctrlProp) { + $rId = substr($rId, 3); // rIdXXX + $unparsedCtrlProps[$rId] = []; + $unparsedCtrlProps[$rId]['filePath'] = self::dirAdd("$dir/$fileWorksheet", $ctrlProp['Target']); + $unparsedCtrlProps[$rId]['relFilePath'] = (string) $ctrlProp['Target']; + $unparsedCtrlProps[$rId]['content'] = $this->securityScan($this->getFromZipArchive($zip, $unparsedCtrlProps[$rId]['filePath'])); + } + unset($unparsedCtrlProps); + } + + // printer settings + if ($zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) { + $relsWorksheet = simplexml_load_string( + //~ http://schemas.openxmlformats.org/package/2006/relationships" + $this->securityScan( + $this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels') + ), + 'SimpleXMLElement', + Settings::getLibXmlLoaderOptions() + ); + $sheetPrinterSettings = []; + foreach ($relsWorksheet->Relationship as $ele) { + if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/printerSettings') { + $sheetPrinterSettings[(string) $ele['Id']] = $ele; + } + } + + $unparsedPrinterSettings = &$excel->unparsedLoadedData['sheets'][$docSheet->getCodeName()]['printerSettings']; + foreach ($sheetPrinterSettings as $rId => $printerSettings) { + $rId = substr($rId, 3); // rIdXXX + $unparsedPrinterSettings[$rId] = []; + $unparsedPrinterSettings[$rId]['filePath'] = self::dirAdd("$dir/$fileWorksheet", $printerSettings['Target']); + $unparsedPrinterSettings[$rId]['relFilePath'] = (string) $printerSettings['Target']; + $unparsedPrinterSettings[$rId]['content'] = $this->securityScan($this->getFromZipArchive($zip, $unparsedPrinterSettings[$rId]['filePath'])); + } + unset($unparsedPrinterSettings); + } + // Loop through definedNames if ($xmlWorkbook->definedNames) { foreach ($xmlWorkbook->definedNames->definedName as $definedName) { @@ -1852,6 +1988,16 @@ public function load($pFilename) 'SimpleXMLElement', Settings::getLibXmlLoaderOptions() ); + // Default content types + foreach ($contentTypes->Default as $contentType) { + switch ($contentType['ContentType']) { + case 'application/vnd.openxmlformats-officedocument.spreadsheetml.printerSettings': + $excel->unparsedLoadedData['default_content_types'][(string) $contentType['Extension']] = (string) $contentType['ContentType']; + + break; + } + } + // Override content types foreach ($contentTypes->Override as $contentType) { switch ($contentType['ContentType']) { case 'application/vnd.openxmlformats-officedocument.drawingml.chart+xml': @@ -1876,6 +2022,14 @@ public function load($pFilename) } } } + + break; + + // unparsed + case 'application/vnd.ms-excel.controlproperties+xml': + $excel->unparsedLoadedData['override_content_types'][(string) $contentType['PartName']] = (string) $contentType['ContentType']; + + break; } } } diff --git a/src/PhpSpreadsheet/Spreadsheet.php b/src/PhpSpreadsheet/Spreadsheet.php index 659b3100d7..eda7951a44 100644 --- a/src/PhpSpreadsheet/Spreadsheet.php +++ b/src/PhpSpreadsheet/Spreadsheet.php @@ -115,6 +115,14 @@ class Spreadsheet */ private $ribbonBinObjects; + /** + * unparsedLoadedData : list of unparsed loaded data for export to same format with better compatibility + * has to be minimized, when the library start to support currently unparsed data. + * + * @var array + */ + public $unparsedLoadedData = []; + /** * The workbook has macros ? * diff --git a/src/PhpSpreadsheet/Writer/Xlsx.php b/src/PhpSpreadsheet/Writer/Xlsx.php index 0982c06826..f0072918e6 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx.php +++ b/src/PhpSpreadsheet/Writer/Xlsx.php @@ -290,12 +290,25 @@ public function save($pFilename) } } - $chartRef1 = $chartRef2 = 0; + $chartRef1 = 0; // Add worksheet relationships (drawings, ...) for ($i = 0; $i < $this->spreadSheet->getSheetCount(); ++$i) { // Add relationships $zip->addFromString('xl/worksheets/_rels/sheet' . ($i + 1) . '.xml.rels', $this->getWriterPart('Rels')->writeWorksheetRelationships($this->spreadSheet->getSheet($i), ($i + 1), $this->includeCharts)); + // Add unparsedLoadedData + $sheetCodeName = $this->spreadSheet->getSheet($i)->getCodeName(); + if (isset($this->spreadSheet->unparsedLoadedData['sheets'][$sheetCodeName]['ctrlProps'])) { + foreach ($this->spreadSheet->unparsedLoadedData['sheets'][$sheetCodeName]['ctrlProps'] as $ctrlProp) { + $zip->addFromString($ctrlProp['filePath'], $ctrlProp['content']); + } + } + if (isset($this->spreadSheet->unparsedLoadedData['sheets'][$sheetCodeName]['printerSettings'])) { + foreach ($this->spreadSheet->unparsedLoadedData['sheets'][$sheetCodeName]['printerSettings'] as $ctrlProp) { + $zip->addFromString($ctrlProp['filePath'], $ctrlProp['content']); + } + } + $drawings = $this->spreadSheet->getSheet($i)->getDrawingCollection(); $drawingCount = count($drawings); if ($this->includeCharts) { @@ -307,6 +320,9 @@ public function save($pFilename) // Drawing relationships $zip->addFromString('xl/drawings/_rels/drawing' . ($i + 1) . '.xml.rels', $this->getWriterPart('Rels')->writeDrawingRelationships($this->spreadSheet->getSheet($i), $chartRef1, $this->includeCharts)); + // Drawings + $zip->addFromString('xl/drawings/drawing' . ($i + 1) . '.xml', $this->getWriterPart('Drawing')->writeDrawings($this->spreadSheet->getSheet($i), $this->includeCharts)); + } elseif (isset($this->spreadSheet->unparsedLoadedData['sheets'][$sheetCodeName]['drawingAlternateContents'])) { // Drawings $zip->addFromString('xl/drawings/drawing' . ($i + 1) . '.xml', $this->getWriterPart('Drawing')->writeDrawings($this->spreadSheet->getSheet($i), $this->includeCharts)); } @@ -320,6 +336,13 @@ public function save($pFilename) $zip->addFromString('xl/comments' . ($i + 1) . '.xml', $this->getWriterPart('Comments')->writeComments($this->spreadSheet->getSheet($i))); } + // Add unparsed relationship parts + if (isset($this->spreadSheet->unparsedLoadedData['sheets'][$this->spreadSheet->getSheet($i)->getCodeName()]['vmlDrawings'])) { + foreach ($this->spreadSheet->unparsedLoadedData['sheets'][$this->spreadSheet->getSheet($i)->getCodeName()]['vmlDrawings'] as $vmlDrawing) { + $zip->addFromString($vmlDrawing['filePath'], $vmlDrawing['content']); + } + } + // Add header/footer relationship parts if (count($this->spreadSheet->getSheet($i)->getHeaderFooter()->getImages()) > 0) { // VML Drawings diff --git a/src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php b/src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php index a34d34656b..358ceeef5c 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php @@ -57,7 +57,8 @@ public function writeContentTypes(Spreadsheet $spreadsheet, $includeCharts = fal // Yes : not standard content but "macroEnabled" $this->writeOverrideContentType($objWriter, '/xl/workbook.xml', 'application/vnd.ms-excel.sheet.macroEnabled.main+xml'); //... and define a new type for the VBA project - $this->writeDefaultContentType($objWriter, 'bin', 'application/vnd.ms-office.vbaProject'); + // Better use Override, because we can use 'bin' also for xl\printerSettings\printerSettings1.bin + $this->writeOverrideContentType($objWriter, '/xl/vbaProject.bin', 'application/vnd.ms-office.vbaProject'); if ($spreadsheet->hasMacrosCertificate()) { // signed macros ? // Yes : add needed information @@ -93,9 +94,10 @@ public function writeContentTypes(Spreadsheet $spreadsheet, $includeCharts = fal $drawings = $spreadsheet->getSheet($i)->getDrawingCollection(); $drawingCount = count($drawings); $chartCount = ($includeCharts) ? $spreadsheet->getSheet($i)->getChartCount() : 0; + $hasUnparsedDrawing = isset($spreadsheet->unparsedLoadedData['sheets'][$spreadsheet->getSheet($i)->getCodeName()]['drawingOriginalIds']); // We need a drawing relationship for the worksheet if we have either drawings or charts - if (($drawingCount > 0) || ($chartCount > 0)) { + if (($drawingCount > 0) || ($chartCount > 0) || $hasUnparsedDrawing) { $this->writeOverrideContentType($objWriter, '/xl/drawings/drawing' . ($i + 1) . '.xml', 'application/vnd.openxmlformats-officedocument.drawing+xml'); } @@ -160,6 +162,20 @@ public function writeContentTypes(Spreadsheet $spreadsheet, $includeCharts = fal } } + // unparsed defaults + if (isset($spreadsheet->unparsedLoadedData['default_content_types'])) { + foreach ($spreadsheet->unparsedLoadedData['default_content_types'] as $extName => $contentType) { + $this->writeDefaultContentType($objWriter, $extName, $contentType); + } + } + + // unparsed overrides + if (isset($spreadsheet->unparsedLoadedData['override_content_types'])) { + foreach ($spreadsheet->unparsedLoadedData['override_content_types'] as $partName => $overrideType) { + $this->writeOverrideContentType($objWriter, $partName, $overrideType); + } + } + $objWriter->endElement(); // Return diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php b/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php index a8bd8c04ef..d3cc2c9caa 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php @@ -59,6 +59,13 @@ public function writeDrawings(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWo } } + // unparsed AlternateContent + if (isset($pWorksheet->getParent()->unparsedLoadedData['sheets'][$pWorksheet->getCodeName()]['drawingAlternateContents'])) { + foreach ($pWorksheet->getParent()->unparsedLoadedData['sheets'][$pWorksheet->getCodeName()]['drawingAlternateContents'] as $drawingAlternateContent) { + $objWriter->writeRaw($drawingAlternateContent); + } + } + $objWriter->endElement(); // Return diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Rels.php b/src/PhpSpreadsheet/Writer/Xlsx/Rels.php index e5d2aa531d..c98b6c0cbe 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Rels.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Rels.php @@ -195,18 +195,28 @@ public function writeWorksheetRelationships(\PhpOffice\PhpSpreadsheet\Worksheet\ // Write drawing relationships? $d = 0; + $drawingOriginalIds = []; + if (isset($pWorksheet->getParent()->unparsedLoadedData['sheets'][$pWorksheet->getCodeName()]['drawingOriginalIds'])) { + $drawingOriginalIds = $pWorksheet->getParent()->unparsedLoadedData['sheets'][$pWorksheet->getCodeName()]['drawingOriginalIds']; + } if ($includeCharts) { $charts = $pWorksheet->getChartCollection(); } else { $charts = []; } if (($pWorksheet->getDrawingCollection()->count() > 0) || - (count($charts) > 0)) { + (count($charts) > 0) || + $drawingOriginalIds) { + $relPath = '../drawings/drawing' . $pWorksheetId . '.xml'; + $rId = ++$d; + if (isset($drawingOriginalIds[$relPath])) { + $rId = (int) (substr($drawingOriginalIds[$relPath], 3)); + } $this->writeRelationship( $objWriter, - ++$d, + $rId, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing', - '../drawings/drawing' . $pWorksheetId . '.xml' + $relPath ); } @@ -255,6 +265,38 @@ public function writeWorksheetRelationships(\PhpOffice\PhpSpreadsheet\Worksheet\ ); } + // Write unparsed relationship? + if (isset($pWorksheet->getParent()->unparsedLoadedData['sheets'][$pWorksheet->getCodeName()]['ctrlProps'])) { + foreach ($pWorksheet->getParent()->unparsedLoadedData['sheets'][$pWorksheet->getCodeName()]['ctrlProps'] as $rId => $ctrlProp) { + $this->writeRelationship( + $objWriter, + $rId, + 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/ctrlProp', + $ctrlProp['relFilePath'] + ); + } + } + if (isset($pWorksheet->getParent()->unparsedLoadedData['sheets'][$pWorksheet->getCodeName()]['vmlDrawings'])) { + foreach ($pWorksheet->getParent()->unparsedLoadedData['sheets'][$pWorksheet->getCodeName()]['vmlDrawings'] as $rId => $vmlDrawing) { + $this->writeRelationship( + $objWriter, + $rId, + 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing', + $vmlDrawing['relFilePath'] + ); + } + } + if (isset($pWorksheet->getParent()->unparsedLoadedData['sheets'][$pWorksheet->getCodeName()]['printerSettings'])) { + foreach ($pWorksheet->getParent()->unparsedLoadedData['sheets'][$pWorksheet->getCodeName()]['printerSettings'] as $rId => $printerSettings) { + $this->writeRelationship( + $objWriter, + $rId, + 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/printerSettings', + $printerSettings['relFilePath'] + ); + } + } + $objWriter->endElement(); return $objWriter->getData(); diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php index f551c60d73..4522fb850b 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php @@ -51,6 +51,12 @@ public function writeWorksheet(PhpspreadsheetWorksheet $pSheet, $pStringTable = $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'); $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + $objWriter->writeAttribute('xmlns:xdr', 'http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing'); + $objWriter->writeAttribute('xmlns:x14', 'http://schemas.microsoft.com/office/spreadsheetml/2009/9/main'); + $objWriter->writeAttribute('xmlns:mc', 'http://schemas.openxmlformats.org/markup-compatibility/2006'); + $objWriter->writeAttribute('mc:Ignorable', 'x14ac'); + $objWriter->writeAttribute('xmlns:x14ac', 'http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac'); + // sheetPr $this->writeSheetPr($objWriter, $pSheet); @@ -114,6 +120,9 @@ public function writeWorksheet(PhpspreadsheetWorksheet $pSheet, $pStringTable = // LegacyDrawingHF $this->writeLegacyDrawingHF($objWriter, $pSheet); + // AlternateContent + $this->writeAlternateContent($objWriter, $pSheet); + $objWriter->endElement(); // Return @@ -283,7 +292,7 @@ private function writeSheetViews(XMLWriter $objWriter, PhpspreadsheetWorksheet $ $objWriter->writeAttribute('pane', $pane); } $objWriter->writeAttribute('activeCell', $activeCell); - $objWriter->writeAttribute('sqref', $activeCell); + $objWriter->writeAttribute('sqref', $pSheet->getSelectedCells()); $objWriter->endElement(); $objWriter->endElement(); @@ -843,6 +852,10 @@ private function writePageSetup(XMLWriter $objWriter, PhpspreadsheetWorksheet $p $objWriter->writeAttribute('useFirstPageNumber', '1'); } + if (isset($pSheet->getParent()->unparsedLoadedData['sheets'][$pSheet->getCodeName()]['pageSetupRelId'])) { + $objWriter->writeAttribute('r:id', $pSheet->getParent()->unparsedLoadedData['sheets'][$pSheet->getCodeName()]['pageSetupRelId']); + } + $objWriter->endElement(); } @@ -1142,16 +1155,26 @@ private function writeCell(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet * @param PhpspreadsheetWorksheet $pSheet Worksheet * @param bool $includeCharts Flag indicating if we should include drawing details for charts */ - private function writeDrawings(XMLWriter $objWriter = null, PhpspreadsheetWorksheet $pSheet = null, $includeCharts = false) + private function writeDrawings(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet, $includeCharts = false) { + $hasUnparsedDrawing = isset($pSheet->getParent()->unparsedLoadedData['sheets'][$pSheet->getCodeName()]['drawingOriginalIds']); $chartCount = ($includeCharts) ? $pSheet->getChartCollection()->count() : 0; + if ($chartCount == 0 && $pSheet->getDrawingCollection()->count() == 0 && !$hasUnparsedDrawing) { + return; + } + // If sheet contains drawings, add the relationships - if (($pSheet->getDrawingCollection()->count() > 0) || - ($chartCount > 0)) { - $objWriter->startElement('drawing'); - $objWriter->writeAttribute('r:id', 'rId1'); - $objWriter->endElement(); + $objWriter->startElement('drawing'); + + $rId = 'rId1'; + if (isset($pSheet->getParent()->unparsedLoadedData['sheets'][$pSheet->getCodeName()]['drawingOriginalIds'])) { + $drawingOriginalIds = $pSheet->getParent()->unparsedLoadedData['sheets'][$pSheet->getCodeName()]['drawingOriginalIds']; + // take first. In future can be overriten + $rId = reset($drawingOriginalIds); } + + $objWriter->writeAttribute('r:id', $rId); + $objWriter->endElement(); } /** @@ -1185,4 +1208,15 @@ private function writeLegacyDrawingHF(XMLWriter $objWriter, PhpspreadsheetWorksh $objWriter->endElement(); } } + + private function writeAlternateContent(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet) + { + if (empty($pSheet->getParent()->unparsedLoadedData['sheets'][$pSheet->getCodeName()]['AlternateContents'])) { + return; + } + + foreach ($pSheet->getParent()->unparsedLoadedData['sheets'][$pSheet->getCodeName()]['AlternateContents'] as $alternateContent) { + $objWriter->writeRaw($alternateContent); + } + } } diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataTest.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataTest.php new file mode 100644 index 0000000000..be6a91d625 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/UnparsedDataTest.php @@ -0,0 +1,104 @@ +load($sampleFilename); + + $excel->getSheet(1)->setCellValue('B1', '222'); + + $writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($excel); + $writer->save($resultFilename); + if (!file_exists($resultFilename)) { + $this->fail("Result file not found! ($resultFilename)"); + + return; + } + + $resultZip = new \ZipArchive(); + $resultZip->open($resultFilename); + $resultContentTypesRaw = $resultZip->getFromName('[Content_Types].xml'); + $resultControlPropRaw = $resultZip->getFromName('xl/ctrlProps/ctrlProp1.xml'); + $resultDrawingRaw = $resultZip->getFromName('xl/drawings/drawing1.xml'); + $resultVmlDrawingRaw = $resultZip->getFromName('xl/drawings/vmlDrawing1.vml'); + $resultPrinterSettingsRaw = $resultZip->getFromName('xl/printerSettings/printerSettings1.bin'); + $resultVbaProjectRaw = $resultZip->getFromName('xl/vbaProject.bin'); + $resultWorkbookRaw = $resultZip->getFromName('xl/workbook.xml'); + $resultSheet1RelsRaw = $resultZip->getFromName('xl/worksheets/_rels/sheet1.xml.rels'); + $resultSheet1Raw = $resultZip->getFromName('xl/worksheets/sheet1.xml'); + $resultSheet2Raw = $resultZip->getFromName('xl/worksheets/sheet2.xml'); + if (false === $resultZip->close()) { + throw new \Exception("Could not close zip file \"{$resultFilename}\"."); + } + unlink($resultFilename); + + // [Content_Types].xml + $this->assertTrue(strpos($resultContentTypesRaw, 'application/vnd.openxmlformats-officedocument.spreadsheetml.printerSettings') > 0, 'Content type for printerSettings not found!'); + $this->assertTrue(strpos($resultContentTypesRaw, 'application/vnd.ms-office.vbaProject') > 0, 'Content type for VbaProject not found!'); + $this->assertTrue(strpos($resultContentTypesRaw, 'application/vnd.ms-excel.controlproperties+xml') > 0, 'Content type for ctrlProp not found!'); + + // xl/ctrlProps/ctrlProp1.xml + $this->assertTrue(!empty($resultControlPropRaw), 'ctrlProp not found!'); + + // xl/drawings/drawing1.xml + $this->assertTrue(strpos($resultDrawingRaw, ' 0, 'AlternateContent at drawing.xml not found!'); + + // xl/drawings/vmlDrawing1.vml + $this->assertTrue(!empty($resultVmlDrawingRaw), 'vmlDrawing not found!'); + + // xl/printerSettings/printerSettings1.bin + $this->assertTrue(!empty($resultPrinterSettingsRaw), 'printerSettings.bin not found!'); + + // xl/vbaProject.bin + $this->assertTrue(!empty($resultVbaProjectRaw), 'vbaProject.bin not found!'); + + // xl/workbook.xml + $xmlWorkbook = simplexml_load_string($resultWorkbookRaw, 'SimpleXMLElement', Settings::getLibXmlLoaderOptions()); + if (!$xmlWorkbook->workbookProtection) { + $this->fail('workbook.xml/workbookProtection not found!'); + } else { + $this->assertEquals($xmlWorkbook->workbookProtection['workbookPassword'], 'CBEB', 'workbook.xml/workbookProtection[workbookPassword] is wrong!'); + $this->assertEquals($xmlWorkbook->workbookProtection['lockStructure'], 'true', 'workbook.xml/workbookProtection[lockStructure] is wrong!'); + + $this->assertEquals($xmlWorkbook->sheets->sheet[0]['state'], '', 'workbook.xml/sheets/sheet[0][state] is wrong!'); + $this->assertEquals($xmlWorkbook->sheets->sheet[1]['state'], 'hidden', 'workbook.xml/sheets/sheet[1][state] is wrong!'); + } + unset($xmlWorkbook); + + // xl/worksheets/_rels/sheet1.xml.rels + $this->assertTrue(strpos($resultSheet1RelsRaw, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/printerSettings') > 0, 'Sheet relation with printerSettings not found!'); + $this->assertTrue(strpos($resultSheet1RelsRaw, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing') > 0, 'Sheet relation with vmlDrawing not found!'); + $this->assertTrue(strpos($resultSheet1RelsRaw, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/ctrlProp') > 0, 'Sheet relation with ctrlProp not found!'); + + // xl/worksheets/sheet1.xml + $this->assertTrue(strpos($resultSheet1Raw, ' 0, 'AlternateContent at sheet1.xml not found!'); + $xmlWorksheet = simplexml_load_string($resultSheet1Raw, 'SimpleXMLElement', Settings::getLibXmlLoaderOptions()); + $pageSetupAttributes = $xmlWorksheet->pageSetup->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + $this->assertTrue(!empty($pageSetupAttributes['id']), 'sheet1.xml/pageSetup[r:id] not found!'); + if (!$xmlWorksheet->sheetProtection) { + $this->fail('sheet1.xml/sheetProtection not found!'); + } else { + $this->assertEquals($xmlWorksheet->sheetProtection['password'], 'CBEB', 'sheet1.xml/sheetProtection[password] is wrong!'); + $this->assertEquals($xmlWorksheet->sheetProtection['sheet'], 'true', 'sheet1.xml/sheetProtection[sheet] is wrong!'); + $this->assertEquals($xmlWorksheet->sheetProtection['objects'], 'true', 'sheet1.xml/sheetProtection[objects] is wrong!'); + $this->assertEquals($xmlWorksheet->sheetProtection['scenarios'], 'true', 'sheet1.xml/sheetProtection[scenarios] is wrong!'); + } + unset($xmlWorksheet); + + // xl/worksheets/sheet2.xml + $this->assertTrue(!empty($resultSheet2Raw), 'sheet2.xml not found!'); + } +} diff --git a/tests/data/Writer/XLSX/form_pass_print.xlsm b/tests/data/Writer/XLSX/form_pass_print.xlsm new file mode 100644 index 0000000000..9a593a9cb2 Binary files /dev/null and b/tests/data/Writer/XLSX/form_pass_print.xlsm differ