Completed
Pull Request — develop_3.0 (#437)
by Adrien
04:41
created

FileSystemHelper::createAppXmlFile()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 8

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