Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

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