See Release Notes
Long Term Support Release
Differences Between: [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401]
1 <?php 2 3 namespace Box\Spout\Writer\XLSX\Helper; 4 5 use Box\Spout\Writer\Common\Entity\Worksheet; 6 use Box\Spout\Writer\Common\Helper\FileSystemWithRootFolderHelperInterface; 7 use Box\Spout\Writer\Common\Helper\ZipHelper; 8 use Box\Spout\Writer\XLSX\Manager\Style\StyleManager; 9 10 /** 11 * Class FileSystemHelper 12 * This class provides helper functions to help with the file system operations 13 * like files/folders creation & deletion for XLSX files 14 */ 15 class FileSystemHelper extends \Box\Spout\Common\Helper\FileSystemHelper implements FileSystemWithRootFolderHelperInterface 16 { 17 const APP_NAME = 'Spout'; 18 19 const RELS_FOLDER_NAME = '_rels'; 20 const DOC_PROPS_FOLDER_NAME = 'docProps'; 21 const XL_FOLDER_NAME = 'xl'; 22 const WORKSHEETS_FOLDER_NAME = 'worksheets'; 23 24 const RELS_FILE_NAME = '.rels'; 25 const APP_XML_FILE_NAME = 'app.xml'; 26 const CORE_XML_FILE_NAME = 'core.xml'; 27 const CONTENT_TYPES_XML_FILE_NAME = '[Content_Types].xml'; 28 const WORKBOOK_XML_FILE_NAME = 'workbook.xml'; 29 const WORKBOOK_RELS_XML_FILE_NAME = 'workbook.xml.rels'; 30 const STYLES_XML_FILE_NAME = 'styles.xml'; 31 32 /** @var ZipHelper Helper to perform tasks with Zip archive */ 33 private $zipHelper; 34 35 /** @var \Box\Spout\Common\Helper\Escaper\XLSX Used to escape XML data */ 36 private $escaper; 37 38 /** @var string Path to the root folder inside the temp folder where the files to create the XLSX will be stored */ 39 private $rootFolder; 40 41 /** @var string Path to the "_rels" folder inside the root folder */ 42 private $relsFolder; 43 44 /** @var string Path to the "docProps" folder inside the root folder */ 45 private $docPropsFolder; 46 47 /** @var string Path to the "xl" folder inside the root folder */ 48 private $xlFolder; 49 50 /** @var string Path to the "_rels" folder inside the "xl" folder */ 51 private $xlRelsFolder; 52 53 /** @var string Path to the "worksheets" folder inside the "xl" folder */ 54 private $xlWorksheetsFolder; 55 56 /** 57 * @param string $baseFolderPath The path of the base folder where all the I/O can occur 58 * @param ZipHelper $zipHelper Helper to perform tasks with Zip archive 59 * @param \Box\Spout\Common\Helper\Escaper\XLSX $escaper Used to escape XML data 60 */ 61 public function __construct($baseFolderPath, $zipHelper, $escaper) 62 { 63 parent::__construct($baseFolderPath); 64 $this->zipHelper = $zipHelper; 65 $this->escaper = $escaper; 66 } 67 68 /** 69 * @return string 70 */ 71 public function getRootFolder() 72 { 73 return $this->rootFolder; 74 } 75 76 /** 77 * @return string 78 */ 79 public function getXlFolder() 80 { 81 return $this->xlFolder; 82 } 83 84 /** 85 * @return string 86 */ 87 public function getXlWorksheetsFolder() 88 { 89 return $this->xlWorksheetsFolder; 90 } 91 92 /** 93 * Creates all the folders needed to create a XLSX file, as well as the files that won't change. 94 * 95 * @throws \Box\Spout\Common\Exception\IOException If unable to create at least one of the base folders 96 * @return void 97 */ 98 public function createBaseFilesAndFolders() 99 { 100 $this 101 ->createRootFolder() 102 ->createRelsFolderAndFile() 103 ->createDocPropsFolderAndFiles() 104 ->createXlFolderAndSubFolders(); 105 } 106 107 /** 108 * Creates the folder that will be used as root 109 * 110 * @throws \Box\Spout\Common\Exception\IOException If unable to create the folder 111 * @return FileSystemHelper 112 */ 113 private function createRootFolder() 114 { 115 $this->rootFolder = $this->createFolder($this->baseFolderRealPath, uniqid('xlsx', true)); 116 117 return $this; 118 } 119 120 /** 121 * Creates the "_rels" folder under the root folder as well as the ".rels" file in it 122 * 123 * @throws \Box\Spout\Common\Exception\IOException If unable to create the folder or the ".rels" file 124 * @return FileSystemHelper 125 */ 126 private function createRelsFolderAndFile() 127 { 128 $this->relsFolder = $this->createFolder($this->rootFolder, self::RELS_FOLDER_NAME); 129 130 $this->createRelsFile(); 131 132 return $this; 133 } 134 135 /** 136 * Creates the ".rels" file under the "_rels" folder (under root) 137 * 138 * @throws \Box\Spout\Common\Exception\IOException If unable to create the file 139 * @return FileSystemHelper 140 */ 141 private function createRelsFile() 142 { 143 $relsFileContents = <<<'EOD' 144 <?xml version="1.0" encoding="UTF-8"?> 145 <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"> 146 <Relationship Id="rIdWorkbook" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/> 147 <Relationship Id="rIdCore" Type="http://schemas.openxmlformats.org/officedocument/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/> 148 <Relationship Id="rIdApp" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/> 149 </Relationships> 150 EOD; 151 152 $this->createFileWithContents($this->relsFolder, self::RELS_FILE_NAME, $relsFileContents); 153 154 return $this; 155 } 156 157 /** 158 * Creates the "docProps" folder under the root folder as well as the "app.xml" and "core.xml" files in it 159 * 160 * @throws \Box\Spout\Common\Exception\IOException If unable to create the folder or one of the files 161 * @return FileSystemHelper 162 */ 163 private function createDocPropsFolderAndFiles() 164 { 165 $this->docPropsFolder = $this->createFolder($this->rootFolder, self::DOC_PROPS_FOLDER_NAME); 166 167 $this->createAppXmlFile(); 168 $this->createCoreXmlFile(); 169 170 return $this; 171 } 172 173 /** 174 * Creates the "app.xml" file under the "docProps" folder 175 * 176 * @throws \Box\Spout\Common\Exception\IOException If unable to create the file 177 * @return FileSystemHelper 178 */ 179 private function createAppXmlFile() 180 { 181 $appName = self::APP_NAME; 182 $appXmlFileContents = <<<EOD 183 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> 184 <Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties"> 185 <Application>$appName</Application> 186 <TotalTime>0</TotalTime> 187 </Properties> 188 EOD; 189 190 $this->createFileWithContents($this->docPropsFolder, self::APP_XML_FILE_NAME, $appXmlFileContents); 191 192 return $this; 193 } 194 195 /** 196 * Creates the "core.xml" file under the "docProps" folder 197 * 198 * @throws \Box\Spout\Common\Exception\IOException If unable to create the file 199 * @return FileSystemHelper 200 */ 201 private function createCoreXmlFile() 202 { 203 $createdDate = (new \DateTime())->format(\DateTime::W3C); 204 $coreXmlFileContents = <<<EOD 205 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> 206 <cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcmitype="http://purl.org/dc/dcmitype/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 207 <dcterms:created xsi:type="dcterms:W3CDTF">$createdDate</dcterms:created> 208 <dcterms:modified xsi:type="dcterms:W3CDTF">$createdDate</dcterms:modified> 209 <cp:revision>0</cp:revision> 210 </cp:coreProperties> 211 EOD; 212 213 $this->createFileWithContents($this->docPropsFolder, self::CORE_XML_FILE_NAME, $coreXmlFileContents); 214 215 return $this; 216 } 217 218 /** 219 * Creates the "xl" folder under the root folder as well as its subfolders 220 * 221 * @throws \Box\Spout\Common\Exception\IOException If unable to create at least one of the folders 222 * @return FileSystemHelper 223 */ 224 private function createXlFolderAndSubFolders() 225 { 226 $this->xlFolder = $this->createFolder($this->rootFolder, self::XL_FOLDER_NAME); 227 $this->createXlRelsFolder(); 228 $this->createXlWorksheetsFolder(); 229 230 return $this; 231 } 232 233 /** 234 * Creates the "_rels" folder under the "xl" folder 235 * 236 * @throws \Box\Spout\Common\Exception\IOException If unable to create the folder 237 * @return FileSystemHelper 238 */ 239 private function createXlRelsFolder() 240 { 241 $this->xlRelsFolder = $this->createFolder($this->xlFolder, self::RELS_FOLDER_NAME); 242 243 return $this; 244 } 245 246 /** 247 * Creates the "worksheets" folder under the "xl" folder 248 * 249 * @throws \Box\Spout\Common\Exception\IOException If unable to create the folder 250 * @return FileSystemHelper 251 */ 252 private function createXlWorksheetsFolder() 253 { 254 $this->xlWorksheetsFolder = $this->createFolder($this->xlFolder, self::WORKSHEETS_FOLDER_NAME); 255 256 return $this; 257 } 258 259 /** 260 * Creates the "[Content_Types].xml" file under the root folder 261 * 262 * @param Worksheet[] $worksheets 263 * @return FileSystemHelper 264 */ 265 public function createContentTypesFile($worksheets) 266 { 267 $contentTypesXmlFileContents = <<<'EOD' 268 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> 269 <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"> 270 <Default ContentType="application/xml" Extension="xml"/> 271 <Default ContentType="application/vnd.openxmlformats-package.relationships+xml" Extension="rels"/> 272 <Override ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml" PartName="/xl/workbook.xml"/> 273 EOD; 274 275 /** @var Worksheet $worksheet */ 276 foreach ($worksheets as $worksheet) { 277 $contentTypesXmlFileContents .= '<Override ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml" PartName="/xl/worksheets/sheet' . $worksheet->getId() . '.xml"/>'; 278 } 279 280 $contentTypesXmlFileContents .= <<<'EOD' 281 <Override ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml" PartName="/xl/styles.xml"/> 282 <Override ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml" PartName="/xl/sharedStrings.xml"/> 283 <Override ContentType="application/vnd.openxmlformats-package.core-properties+xml" PartName="/docProps/core.xml"/> 284 <Override ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml" PartName="/docProps/app.xml"/> 285 </Types> 286 EOD; 287 288 $this->createFileWithContents($this->rootFolder, self::CONTENT_TYPES_XML_FILE_NAME, $contentTypesXmlFileContents); 289 290 return $this; 291 } 292 293 /** 294 * Creates the "workbook.xml" file under the "xl" folder 295 * 296 * @param Worksheet[] $worksheets 297 * @return FileSystemHelper 298 */ 299 public function createWorkbookFile($worksheets) 300 { 301 $workbookXmlFileContents = <<<'EOD' 302 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> 303 <workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"> 304 <sheets> 305 EOD; 306 307 /** @var Worksheet $worksheet */ 308 foreach ($worksheets as $worksheet) { 309 $worksheetName = $worksheet->getExternalSheet()->getName(); 310 $worksheetVisibility = $worksheet->getExternalSheet()->isVisible() ? 'visible' : 'hidden'; 311 $worksheetId = $worksheet->getId(); 312 $workbookXmlFileContents .= '<sheet name="' . $this->escaper->escape($worksheetName) . '" sheetId="' . $worksheetId . '" r:id="rIdSheet' . $worksheetId . '" state="' . $worksheetVisibility . '"/>'; 313 } 314 315 $workbookXmlFileContents .= <<<'EOD' 316 </sheets> 317 </workbook> 318 EOD; 319 320 $this->createFileWithContents($this->xlFolder, self::WORKBOOK_XML_FILE_NAME, $workbookXmlFileContents); 321 322 return $this; 323 } 324 325 /** 326 * Creates the "workbook.xml.res" file under the "xl/_res" folder 327 * 328 * @param Worksheet[] $worksheets 329 * @return FileSystemHelper 330 */ 331 public function createWorkbookRelsFile($worksheets) 332 { 333 $workbookRelsXmlFileContents = <<<'EOD' 334 <?xml version="1.0" encoding="UTF-8"?> 335 <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"> 336 <Relationship Id="rIdStyles" Target="styles.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles"/> 337 <Relationship Id="rIdSharedStrings" Target="sharedStrings.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings"/> 338 EOD; 339 340 /** @var Worksheet $worksheet */ 341 foreach ($worksheets as $worksheet) { 342 $worksheetId = $worksheet->getId(); 343 $workbookRelsXmlFileContents .= '<Relationship Id="rIdSheet' . $worksheetId . '" Target="worksheets/sheet' . $worksheetId . '.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"/>'; 344 } 345 346 $workbookRelsXmlFileContents .= '</Relationships>'; 347 348 $this->createFileWithContents($this->xlRelsFolder, self::WORKBOOK_RELS_XML_FILE_NAME, $workbookRelsXmlFileContents); 349 350 return $this; 351 } 352 353 /** 354 * Creates the "styles.xml" file under the "xl" folder 355 * 356 * @param StyleManager $styleManager 357 * @return FileSystemHelper 358 */ 359 public function createStylesFile($styleManager) 360 { 361 $stylesXmlFileContents = $styleManager->getStylesXMLFileContent(); 362 $this->createFileWithContents($this->xlFolder, self::STYLES_XML_FILE_NAME, $stylesXmlFileContents); 363 364 return $this; 365 } 366 367 /** 368 * Zips the root folder and streams the contents of the zip into the given stream 369 * 370 * @param resource $streamPointer Pointer to the stream to copy the zip 371 * @return void 372 */ 373 public function zipRootFolderAndCopyToStream($streamPointer) 374 { 375 $zip = $this->zipHelper->createZip($this->rootFolder); 376 377 $zipFilePath = $this->zipHelper->getZipFilePath($zip); 378 379 // In order to have the file's mime type detected properly, files need to be added 380 // to the zip file in a particular order. 381 // "[Content_Types].xml" then at least 2 files located in "xl" folder should be zipped first. 382 $this->zipHelper->addFileToArchive($zip, $this->rootFolder, self::CONTENT_TYPES_XML_FILE_NAME); 383 $this->zipHelper->addFileToArchive($zip, $this->rootFolder, self::XL_FOLDER_NAME . '/' . self::WORKBOOK_XML_FILE_NAME); 384 $this->zipHelper->addFileToArchive($zip, $this->rootFolder, self::XL_FOLDER_NAME . '/' . self::STYLES_XML_FILE_NAME); 385 386 $this->zipHelper->addFolderToArchive($zip, $this->rootFolder, ZipHelper::EXISTING_FILES_SKIP); 387 $this->zipHelper->closeArchiveAndCopyToStream($zip, $streamPointer); 388 389 // once the zip is copied, remove it 390 $this->deleteFile($zipFilePath); 391 } 392 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body