Failed Conditions
Push — develop_3.0 ( 80553c...f9d8ad )
by Adrien
31:09
created

ZipHelper::addFileToArchiveWithCompressionMethod()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

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