Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better support of xlsm with forms #435

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 154 additions & 0 deletions src/PhpSpreadsheet/Reader/Xlsx.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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')) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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':
Expand All @@ -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;
}
}
}
Expand Down
8 changes: 8 additions & 0 deletions src/PhpSpreadsheet/Spreadsheet.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 ?
*
Expand Down
25 changes: 24 additions & 1 deletion src/PhpSpreadsheet/Writer/Xlsx.php
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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));
}
Expand All @@ -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
Expand Down
20 changes: 18 additions & 2 deletions src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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');
}

Expand Down Expand Up @@ -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
Expand Down
Loading