Completed
Push — develop_3.0 ( 762dd1...30366e )
by Adrien
04:15
created

FileSystemHelper::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 5
ccs 4
cts 4
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 2
crap 1
1
<?php
2
3
namespace Box\Spout\Writer\ODS\Helper;
4
5
use Box\Spout\Writer\Common\Helper\FileSystemWithRootFolderHelperInterface;
6
use Box\Spout\Writer\Common\Helper\ZipHelper;
7
use Box\Spout\Writer\Common\Entity\Worksheet;
8
use Box\Spout\Writer\ODS\Manager\Style\StyleManager;
9
use Box\Spout\Writer\ODS\Manager\WorksheetManager;
10
11
/**
12
 * Class FileSystemHelper
13
 * This class provides helper functions to help with the file system operations
14
 * like files/folders creation & deletion for ODS files
15
 *
16
 * @package Box\Spout\Writer\ODS\Helper
17
 */
18
class FileSystemHelper extends \Box\Spout\Common\Helper\FileSystemHelper implements FileSystemWithRootFolderHelperInterface
19
{
20
    const APP_NAME = 'Spout';
21
    const MIMETYPE = 'application/vnd.oasis.opendocument.spreadsheet';
22
23
    const META_INF_FOLDER_NAME = 'META-INF';
24
    const SHEETS_CONTENT_TEMP_FOLDER_NAME = 'worksheets-temp';
25
26
    const MANIFEST_XML_FILE_NAME = 'manifest.xml';
27
    const CONTENT_XML_FILE_NAME = 'content.xml';
28
    const META_XML_FILE_NAME = 'meta.xml';
29
    const MIMETYPE_FILE_NAME = 'mimetype';
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 string Path to the root folder inside the temp folder where the files to create the ODS will be stored */
36
    protected $rootFolder;
37
38
    /** @var string Path to the "META-INF" folder inside the root folder */
39
    protected $metaInfFolder;
40
41
    /** @var string Path to the temp folder, inside the root folder, where specific sheets content will be written to */
42
    protected $sheetsContentTempFolder;
43
44
    /**
45
     * @param string $baseFolderPath The path of the base folder where all the I/O can occur
46
     * @param ZipHelper $zipHelper Helper to perform tasks with Zip archive
47
     */
48 43
    public function __construct($baseFolderPath, $zipHelper)
49
    {
50 43
        parent::__construct($baseFolderPath);
51 43
        $this->zipHelper = $zipHelper;
52 43
    }
53
54
    /**
55
     * @return string
56
     */
57 34
    public function getRootFolder()
58
    {
59 34
        return $this->rootFolder;
60
    }
61
62
    /**
63
     * @return string
64
     */
65 43
    public function getSheetsContentTempFolder()
66
    {
67 43
        return $this->sheetsContentTempFolder;
68
    }
69
70
    /**
71
     * Creates all the folders needed to create a ODS file, as well as the files that won't change.
72
     *
73
     * @return void
74
     * @throws \Box\Spout\Common\Exception\IOException If unable to create at least one of the base folders
75
     */
76 43
    public function createBaseFilesAndFolders()
77
    {
78
        $this
79 43
            ->createRootFolder()
80 43
            ->createMetaInfoFolderAndFile()
81 43
            ->createSheetsContentTempFolder()
82 43
            ->createMetaFile()
83 43
            ->createMimetypeFile();
84 43
    }
85
86
    /**
87
     * Creates the folder that will be used as root
88
     *
89
     * @return FileSystemHelper
90
     * @throws \Box\Spout\Common\Exception\IOException If unable to create the folder
91
     */
92 43
    protected function createRootFolder()
93
    {
94 43
        $this->rootFolder = $this->createFolder($this->baseFolderRealPath, uniqid('ods'));
95 43
        return $this;
96
    }
97
98
    /**
99
     * Creates the "META-INF" folder under the root folder as well as the "manifest.xml" file in it
100
     *
101
     * @return FileSystemHelper
102
     * @throws \Box\Spout\Common\Exception\IOException If unable to create the folder or the "manifest.xml" file
103
     */
104 43
    protected function createMetaInfoFolderAndFile()
105
    {
106 43
        $this->metaInfFolder = $this->createFolder($this->rootFolder, self::META_INF_FOLDER_NAME);
107
108 43
        $this->createManifestFile();
109
110 43
        return $this;
111
    }
112
113
    /**
114
     * Creates the "manifest.xml" file under the "META-INF" folder (under root)
115
     *
116
     * @return FileSystemHelper
117
     * @throws \Box\Spout\Common\Exception\IOException If unable to create the file
118
     */
119 43
    protected function createManifestFile()
120
    {
121
        $manifestXmlFileContents = <<<EOD
122 43
<?xml version="1.0" encoding="UTF-8"?>
123
<manifest:manifest xmlns:manifest="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0" manifest:version="1.2">
124
    <manifest:file-entry manifest:full-path="/" manifest:media-type="application/vnd.oasis.opendocument.spreadsheet"/>
125
    <manifest:file-entry manifest:full-path="styles.xml" manifest:media-type="text/xml"/>
126
    <manifest:file-entry manifest:full-path="content.xml" manifest:media-type="text/xml"/>
127
    <manifest:file-entry manifest:full-path="meta.xml" manifest:media-type="text/xml"/>
128
</manifest:manifest>
129
EOD;
130
131 43
        $this->createFileWithContents($this->metaInfFolder, self::MANIFEST_XML_FILE_NAME, $manifestXmlFileContents);
132
133 43
        return $this;
134
    }
135
136
    /**
137
     * Creates the temp folder where specific sheets content will be written to.
138
     * This folder is not part of the final ODS file and is only used to be able to jump between sheets.
139
     *
140
     * @return FileSystemHelper
141
     * @throws \Box\Spout\Common\Exception\IOException If unable to create the folder
142
     */
143 43
    protected function createSheetsContentTempFolder()
144
    {
145 43
        $this->sheetsContentTempFolder = $this->createFolder($this->rootFolder, self::SHEETS_CONTENT_TEMP_FOLDER_NAME);
146 43
        return $this;
147
    }
148
149
    /**
150
     * Creates the "meta.xml" file under the root folder
151
     *
152
     * @return FileSystemHelper
153
     * @throws \Box\Spout\Common\Exception\IOException If unable to create the file
154
     */
155 43
    protected function createMetaFile()
156
    {
157 43
        $appName = self::APP_NAME;
158 43
        $createdDate = (new \DateTime())->format(\DateTime::W3C);
159
160
        $metaXmlFileContents = <<<EOD
161
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
162
<office:document-meta office:version="1.2" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:xlink="http://www.w3.org/1999/xlink">
163
    <office:meta>
164 43
        <dc:creator>$appName</dc:creator>
165 43
        <meta:creation-date>$createdDate</meta:creation-date>
166 43
        <dc:date>$createdDate</dc:date>
167
    </office:meta>
168
</office:document-meta>
169
EOD;
170
171 43
        $this->createFileWithContents($this->rootFolder, self::META_XML_FILE_NAME, $metaXmlFileContents);
172
173 43
        return $this;
174
    }
175
176
    /**
177
     * Creates the "mimetype" file under the root folder
178
     *
179
     * @return FileSystemHelper
180
     * @throws \Box\Spout\Common\Exception\IOException If unable to create the file
181
     */
182 43
    protected function createMimetypeFile()
183
    {
184 43
        $this->createFileWithContents($this->rootFolder, self::MIMETYPE_FILE_NAME, self::MIMETYPE);
185 43
        return $this;
186
    }
187
188
    /**
189
     * Creates the "content.xml" file under the root folder
190
     *
191
     * @param WorksheetManager $worksheetManager
192
     * @param StyleManager $styleManager
193
     * @param Worksheet[] $worksheets
194
     * @return FileSystemHelper
195
     */
196 34
    public function createContentFile($worksheetManager, $styleManager, $worksheets)
197
    {
198
        $contentXmlFileContents = <<<EOD
199 34
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
200
<office:document-content office:version="1.2" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:msoxl="http://schemas.microsoft.com/office/excel/formula" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:xlink="http://www.w3.org/1999/xlink">
201
EOD;
202
203 34
        $contentXmlFileContents .= $styleManager->getContentXmlFontFaceSectionContent();
204 34
        $contentXmlFileContents .= $styleManager->getContentXmlAutomaticStylesSectionContent(count($worksheets));
205
206 34
        $contentXmlFileContents .= '<office:body><office:spreadsheet>';
207
208 34
        $this->createFileWithContents($this->rootFolder, self::CONTENT_XML_FILE_NAME, $contentXmlFileContents);
209
210
        // Append sheets content to "content.xml"
211 34
        $contentXmlFilePath = $this->rootFolder . '/' . self::CONTENT_XML_FILE_NAME;
212 34
        $contentXmlHandle = fopen($contentXmlFilePath, 'a');
213
214 34
        foreach ($worksheets as $worksheet) {
215
            // write the "<table:table>" node, with the final sheet's name
216 34
            fwrite($contentXmlHandle, $worksheetManager->getTableElementStartAsString($worksheet));
217
218 34
            $worksheetFilePath = $worksheet->getFilePath();
219 34
            $this->copyFileContentsToTarget($worksheetFilePath, $contentXmlHandle);
220
221 34
            fwrite($contentXmlHandle, '</table:table>');
222
        }
223
224 34
        $contentXmlFileContents = '</office:spreadsheet></office:body></office:document-content>';
225
226 34
        fwrite($contentXmlHandle, $contentXmlFileContents);
227 34
        fclose($contentXmlHandle);
228
229 34
        return $this;
230
    }
231
232
    /**
233
     * Streams the content of the file at the given path into the target resource.
234
     * Depending on which mode the target resource was created with, it will truncate then copy
235
     * or append the content to the target file.
236
     *
237
     * @param string $sourceFilePath Path of the file whose content will be copied
238
     * @param resource $targetResource Target resource that will receive the content
239
     * @return void
240
     */
241 34
    protected function copyFileContentsToTarget($sourceFilePath, $targetResource)
242
    {
243 34
        $sourceHandle = fopen($sourceFilePath, 'r');
244 34
        stream_copy_to_stream($sourceHandle, $targetResource);
245 34
        fclose($sourceHandle);
246 34
    }
247
248
    /**
249
     * Deletes the temporary folder where sheets content was stored.
250
     *
251
     * @return FileSystemHelper
252
     */
253 34
    public function deleteWorksheetTempFolder()
254
    {
255 34
        $this->deleteFolderRecursively($this->sheetsContentTempFolder);
256 34
        return $this;
257
    }
258
259
260
    /**
261
     * Creates the "styles.xml" file under the root folder
262
     *
263
     * @param StyleManager $styleManager
264
     * @param int $numWorksheets Number of created worksheets
265
     * @return FileSystemHelper
266
     */
267 34
    public function createStylesFile($styleManager, $numWorksheets)
268
    {
269 34
        $stylesXmlFileContents = $styleManager->getStylesXMLFileContent($numWorksheets);
270 34
        $this->createFileWithContents($this->rootFolder, self::STYLES_XML_FILE_NAME, $stylesXmlFileContents);
271
272 34
        return $this;
273
    }
274
275
    /**
276
     * Zips the root folder and streams the contents of the zip into the given stream
277
     *
278
     * @param resource $streamPointer Pointer to the stream to copy the zip
279
     * @return void
280
     */
281 34
    public function zipRootFolderAndCopyToStream($streamPointer)
282
    {
283 34
        $zip = $this->zipHelper->createZip($this->rootFolder);
284
285 34
        $zipFilePath = $this->zipHelper->getZipFilePath($zip);
286
287
        // In order to have the file's mime type detected properly, files need to be added
288
        // to the zip file in a particular order.
289
        // @see http://www.jejik.com/articles/2010/03/how_to_correctly_create_odf_documents_using_zip/
290 34
        $this->zipHelper->addUncompressedFileToArchive($zip, $this->rootFolder, self::MIMETYPE_FILE_NAME);
291
292 34
        $this->zipHelper->addFolderToArchive($zip, $this->rootFolder, ZipHelper::EXISTING_FILES_SKIP);
293 34
        $this->zipHelper->closeArchiveAndCopyToStream($zip, $streamPointer);
294
295
        // once the zip is copied, remove it
296 34
        $this->deleteFile($zipFilePath);
297 34
    }
298
}
299