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

ZipHelper::addUncompressedFileToArchive()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1.125

Importance

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