FileSystemHelper::createAppXmlFile()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 15
ccs 5
cts 5
cp 1
rs 9.7666
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 1
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 43
    public function __construct($baseFolderPath, $zipHelper, $escaper)
62
    {
63 43
        parent::__construct($baseFolderPath);
64 43
        $this->zipHelper = $zipHelper;
65 43
        $this->escaper = $escaper;
66 43
    }
67
68
    /**
69
     * @return string
70
     */
71 39
    public function getRootFolder()
72
    {
73 39
        return $this->rootFolder;
74
    }
75
76
    /**
77
     * @return string
78
     */
79 43
    public function getXlFolder()
80
    {
81 43
        return $this->xlFolder;
82
    }
83
84
    /**
85
     * @return string
86
     */
87 43
    public function getXlWorksheetsFolder()
88
    {
89 43
        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 43
    public function createBaseFilesAndFolders()
99
    {
100
        $this
101 43
            ->createRootFolder()
102 43
            ->createRelsFolderAndFile()
103 43
            ->createDocPropsFolderAndFiles()
104 43
            ->createXlFolderAndSubFolders();
105 43
    }
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 43
    private function createRootFolder()
114
    {
115 43
        $this->rootFolder = $this->createFolder($this->baseFolderRealPath, \uniqid('xlsx', true));
116
117 43
        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 43
    private function createRelsFolderAndFile()
127
    {
128 43
        $this->relsFolder = $this->createFolder($this->rootFolder, self::RELS_FOLDER_NAME);
129
130 43
        $this->createRelsFile();
131
132 43
        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 43
    private function createRelsFile()
142
    {
143
        $relsFileContents = <<<'EOD'
144 43
<?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 43
        $this->createFileWithContents($this->relsFolder, self::RELS_FILE_NAME, $relsFileContents);
153
154 43
        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 43
    private function createDocPropsFolderAndFiles()
164
    {
165 43
        $this->docPropsFolder = $this->createFolder($this->rootFolder, self::DOC_PROPS_FOLDER_NAME);
166
167 43
        $this->createAppXmlFile();
168 43
        $this->createCoreXmlFile();
169
170 43
        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 43
    private function createAppXmlFile()
180
    {
181 43
        $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 43
    <Application>$appName</Application>
186
    <TotalTime>0</TotalTime>
187
</Properties>
188
EOD;
189
190 43
        $this->createFileWithContents($this->docPropsFolder, self::APP_XML_FILE_NAME, $appXmlFileContents);
191
192 43
        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 43
    private function createCoreXmlFile()
202
    {
203 43
        $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 43
    <dcterms:created xsi:type="dcterms:W3CDTF">$createdDate</dcterms:created>
208 43
    <dcterms:modified xsi:type="dcterms:W3CDTF">$createdDate</dcterms:modified>
209
    <cp:revision>0</cp:revision>
210
</cp:coreProperties>
211
EOD;
212
213 43
        $this->createFileWithContents($this->docPropsFolder, self::CORE_XML_FILE_NAME, $coreXmlFileContents);
214
215 43
        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 43
    private function createXlFolderAndSubFolders()
225
    {
226 43
        $this->xlFolder = $this->createFolder($this->rootFolder, self::XL_FOLDER_NAME);
227 43
        $this->createXlRelsFolder();
228 43
        $this->createXlWorksheetsFolder();
229
230 43
        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 43
    private function createXlRelsFolder()
240
    {
241 43
        $this->xlRelsFolder = $this->createFolder($this->xlFolder, self::RELS_FOLDER_NAME);
242
243 43
        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 43
    private function createXlWorksheetsFolder()
253
    {
254 43
        $this->xlWorksheetsFolder = $this->createFolder($this->xlFolder, self::WORKSHEETS_FOLDER_NAME);
255
256 43
        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 39
    public function createContentTypesFile($worksheets)
266
    {
267
        $contentTypesXmlFileContents = <<<'EOD'
268 39
<?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 39
        foreach ($worksheets as $worksheet) {
277 39
            $contentTypesXmlFileContents .= '<Override ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml" PartName="/xl/worksheets/sheet' . $worksheet->getId() . '.xml"/>';
278
        }
279
280
        $contentTypesXmlFileContents .= <<<'EOD'
281 39
    <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 39
        $this->createFileWithContents($this->rootFolder, self::CONTENT_TYPES_XML_FILE_NAME, $contentTypesXmlFileContents);
289
290 39
        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 39
    public function createWorkbookFile($worksheets)
300
    {
301
        $workbookXmlFileContents = <<<'EOD'
302 39
<?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 39
        foreach ($worksheets as $worksheet) {
309 39
            $worksheetName = $worksheet->getExternalSheet()->getName();
310 39
            $worksheetVisibility = $worksheet->getExternalSheet()->isVisible() ? 'visible' : 'hidden';
311 39
            $worksheetId = $worksheet->getId();
312 39
            $workbookXmlFileContents .= '<sheet name="' . $this->escaper->escape($worksheetName) . '" sheetId="' . $worksheetId . '" r:id="rIdSheet' . $worksheetId . '" state="' . $worksheetVisibility . '"/>';
313
        }
314
315
        $workbookXmlFileContents .= <<<'EOD'
316 39
    </sheets>
317
</workbook>
318
EOD;
319
320 39
        $this->createFileWithContents($this->xlFolder, self::WORKBOOK_XML_FILE_NAME, $workbookXmlFileContents);
321
322 39
        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 39
    public function createWorkbookRelsFile($worksheets)
332
    {
333
        $workbookRelsXmlFileContents = <<<'EOD'
334 39
<?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 39
        foreach ($worksheets as $worksheet) {
342 39
            $worksheetId = $worksheet->getId();
343 39
            $workbookRelsXmlFileContents .= '<Relationship Id="rIdSheet' . $worksheetId . '" Target="worksheets/sheet' . $worksheetId . '.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"/>';
344
        }
345
346 39
        $workbookRelsXmlFileContents .= '</Relationships>';
347
348 39
        $this->createFileWithContents($this->xlRelsFolder, self::WORKBOOK_RELS_XML_FILE_NAME, $workbookRelsXmlFileContents);
349
350 39
        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 39
    public function createStylesFile($styleManager)
360
    {
361 39
        $stylesXmlFileContents = $styleManager->getStylesXMLFileContent();
362 39
        $this->createFileWithContents($this->xlFolder, self::STYLES_XML_FILE_NAME, $stylesXmlFileContents);
363
364 39
        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 39
    public function zipRootFolderAndCopyToStream($streamPointer)
374
    {
375 39
        $zip = $this->zipHelper->createZip($this->rootFolder);
376
377 39
        $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 39
        $this->zipHelper->addFileToArchive($zip, $this->rootFolder, self::CONTENT_TYPES_XML_FILE_NAME);
383 39
        $this->zipHelper->addFileToArchive($zip, $this->rootFolder, self::XL_FOLDER_NAME . '/' . self::WORKBOOK_XML_FILE_NAME);
384 39
        $this->zipHelper->addFileToArchive($zip, $this->rootFolder, self::XL_FOLDER_NAME . '/' . self::STYLES_XML_FILE_NAME);
385
386 39
        $this->zipHelper->addFolderToArchive($zip, $this->rootFolder, ZipHelper::EXISTING_FILES_SKIP);
387 39
        $this->zipHelper->closeArchiveAndCopyToStream($zip, $streamPointer);
388
389
        // once the zip is copied, remove it
390 39
        $this->deleteFile($zipFilePath);
391 39
    }
392
}
393