ZipHelper::getZipFilePath()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
namespace Box\Spout\Writer\Common\Helper;
4
5
use Box\Spout\Writer\Common\Creator\InternalEntityFactory;
6
7
/**
8
 * Class ZipHelper
9
 * This class provides helper functions to create zip files
10
 */
11
class ZipHelper
12
{
13
    const ZIP_EXTENSION = '.zip';
14
15
    /** Controls what to do when trying to add an existing file */
16
    const EXISTING_FILES_SKIP = 'skip';
17
    const EXISTING_FILES_OVERWRITE = 'overwrite';
18
19
    /** @var InternalEntityFactory Factory to create entities */
20
    private $entityFactory;
21
22
    /**
23
     * @param InternalEntityFactory $entityFactory Factory to create entities
24
     */
25 82
    public function __construct($entityFactory)
26
    {
27 82
        $this->entityFactory = $entityFactory;
28 82
    }
29
30
    /**
31
     * Returns a new ZipArchive instance pointing at the given path.
32
     *
33
     * @param string $tmpFolderPath Path of the temp folder where the zip file will be created
34
     * @return \ZipArchive
35
     */
36 75
    public function createZip($tmpFolderPath)
37
    {
38 75
        $zip = $this->entityFactory->createZipArchive();
39 75
        $zipFilePath = $tmpFolderPath . self::ZIP_EXTENSION;
40
41 75
        $zip->open($zipFilePath, \ZipArchive::CREATE | \ZipArchive::OVERWRITE);
42
43 75
        return $zip;
44
    }
45
46
    /**
47
     * @param \ZipArchive $zip An opened zip archive object
48
     * @return string Path where the zip file of the given folder will be created
49
     */
50 75
    public function getZipFilePath(\ZipArchive $zip)
51
    {
52 75
        return $zip->filename;
53
    }
54
55
    /**
56
     * Adds the given file, located under the given root folder to the archive.
57
     * The file will be compressed.
58
     *
59
     * Example of use:
60
     *   addFileToArchive($zip, '/tmp/xlsx/foo', 'bar/baz.xml');
61
     *   => will add the file located at '/tmp/xlsx/foo/bar/baz.xml' in the archive, but only as 'bar/baz.xml'
62
     *
63
     * @param \ZipArchive $zip An opened zip archive object
64
     * @param string $rootFolderPath Path of the root folder that will be ignored in the archive tree.
65
     * @param string $localFilePath Path of the file to be added, under the root folder
66
     * @param string $existingFileMode Controls what to do when trying to add an existing file
67
     * @return void
68
     */
69 39
    public function addFileToArchive($zip, $rootFolderPath, $localFilePath, $existingFileMode = self::EXISTING_FILES_OVERWRITE)
70
    {
71 39
        $this->addFileToArchiveWithCompressionMethod(
72 39
            $zip,
73
            $rootFolderPath,
74
            $localFilePath,
75
            $existingFileMode,
76 39
            \ZipArchive::CM_DEFAULT
77
        );
78 39
    }
79
80
    /**
81
     * Adds the given file, located under the given root folder to the archive.
82
     * The file will NOT be compressed.
83
     *
84
     * Example of use:
85
     *   addUncompressedFileToArchive($zip, '/tmp/xlsx/foo', 'bar/baz.xml');
86
     *   => will add the file located at '/tmp/xlsx/foo/bar/baz.xml' in the archive, but only as 'bar/baz.xml'
87
     *
88
     * @param \ZipArchive $zip An opened zip archive object
89
     * @param string $rootFolderPath Path of the root folder that will be ignored in the archive tree.
90
     * @param string $localFilePath Path of the file to be added, under the root folder
91
     * @param string $existingFileMode Controls what to do when trying to add an existing file
92
     * @return void
93
     */
94 36
    public function addUncompressedFileToArchive($zip, $rootFolderPath, $localFilePath, $existingFileMode = self::EXISTING_FILES_OVERWRITE)
95
    {
96 36
        $this->addFileToArchiveWithCompressionMethod(
97 36
            $zip,
98
            $rootFolderPath,
99
            $localFilePath,
100
            $existingFileMode,
101 36
            \ZipArchive::CM_STORE
102
        );
103 36
    }
104
105
    /**
106
     * Adds the given file, located under the given root folder to the archive.
107
     * The file will NOT be compressed.
108
     *
109
     * Example of use:
110
     *   addUncompressedFileToArchive($zip, '/tmp/xlsx/foo', 'bar/baz.xml');
111
     *   => will add the file located at '/tmp/xlsx/foo/bar/baz.xml' in the archive, but only as 'bar/baz.xml'
112
     *
113
     * @param \ZipArchive $zip An opened zip archive object
114
     * @param string $rootFolderPath Path of the root folder that will be ignored in the archive tree.
115
     * @param string $localFilePath Path of the file to be added, under the root folder
116
     * @param string $existingFileMode Controls what to do when trying to add an existing file
117
     * @param int $compressionMethod The compression method
118
     * @return void
119
     */
120 75
    protected function addFileToArchiveWithCompressionMethod($zip, $rootFolderPath, $localFilePath, $existingFileMode, $compressionMethod)
121
    {
122 75
        if (!$this->shouldSkipFile($zip, $localFilePath, $existingFileMode)) {
123 75
            $normalizedFullFilePath = $this->getNormalizedRealPath($rootFolderPath . '/' . $localFilePath);
124 75
            $zip->addFile($normalizedFullFilePath, $localFilePath);
125
126 75
            if (self::canChooseCompressionMethod()) {
127 75
                $zip->setCompressionName($localFilePath, $compressionMethod);
128
            }
129
        }
130 75
    }
131
132
    /**
133
     * @return bool Whether it is possible to choose the desired compression method to be used
134
     */
135 75
    public static function canChooseCompressionMethod()
136
    {
137
        // setCompressionName() is a PHP7+ method...
138 75
        return (\method_exists(new \ZipArchive(), 'setCompressionName'));
139
    }
140
141
    /**
142
     * @param \ZipArchive $zip An opened zip archive object
143
     * @param string $folderPath Path to the folder to be zipped
144
     * @param string $existingFileMode Controls what to do when trying to add an existing file
145
     * @return void
146
     */
147 75
    public function addFolderToArchive($zip, $folderPath, $existingFileMode = self::EXISTING_FILES_OVERWRITE)
148
    {
149 75
        $folderRealPath = $this->getNormalizedRealPath($folderPath) . '/';
150 75
        $itemIterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($folderPath, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST);
151
152 75
        foreach ($itemIterator as $itemInfo) {
153 75
            $itemRealPath = $this->getNormalizedRealPath($itemInfo->getPathname());
154 75
            $itemLocalPath = \str_replace($folderRealPath, '', $itemRealPath);
155
156 75
            if ($itemInfo->isFile() && !$this->shouldSkipFile($zip, $itemLocalPath, $existingFileMode)) {
157 75
                $zip->addFile($itemRealPath, $itemLocalPath);
158
            }
159
        }
160 75
    }
161
162
    /**
163
     * @param \ZipArchive $zip
164
     * @param string $itemLocalPath
165
     * @param string $existingFileMode
166
     * @return bool Whether the file should be added to the archive or skipped
167
     */
168 75
    protected function shouldSkipFile($zip, $itemLocalPath, $existingFileMode)
169
    {
170
        // Skip files if:
171
        //   - EXISTING_FILES_SKIP mode chosen
172
        //   - File already exists in the archive
173 75
        return ($existingFileMode === self::EXISTING_FILES_SKIP && $zip->locateName($itemLocalPath) !== false);
174
    }
175
176
    /**
177
     * Returns canonicalized absolute pathname, containing only forward slashes.
178
     *
179
     * @param string $path Path to normalize
180
     * @return string Normalized and canonicalized path
181
     */
182 75
    protected function getNormalizedRealPath($path)
183
    {
184 75
        $realPath = \realpath($path);
185
186 75
        return \str_replace(DIRECTORY_SEPARATOR, '/', $realPath);
187
    }
188
189
    /**
190
     * Closes the archive and copies it into the given stream
191
     *
192
     * @param \ZipArchive $zip An opened zip archive object
193
     * @param resource $streamPointer Pointer to the stream to copy the zip
194
     * @return void
195
     */
196 75
    public function closeArchiveAndCopyToStream($zip, $streamPointer)
197
    {
198 75
        $zipFilePath = $zip->filename;
199 75
        $zip->close();
200
201 75
        $this->copyZipToStream($zipFilePath, $streamPointer);
202 75
    }
203
204
    /**
205
     * Streams the contents of the zip file into the given stream
206
     *
207
     * @param string $zipFilePath Path of the zip file
208
     * @param resource $pointer Pointer to the stream to copy the zip
209
     * @return void
210
     */
211 75
    protected function copyZipToStream($zipFilePath, $pointer)
212
    {
213 75
        $zipFilePointer = \fopen($zipFilePath, 'r');
214 75
        \stream_copy_to_stream($zipFilePointer, $pointer);
215 75
        \fclose($zipFilePointer);
216 75
    }
217
}
218