FileSystemHelper   A
last analyzed

Complexity

Total Complexity 16

Size/Duplication

Total Lines 284
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 16
lcom 1
cbo 5
dl 0
loc 284
rs 10
c 0
b 0
f 0
ccs 77
cts 77
cp 1

15 Methods

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