Completed
Pull Request — develop_3.0 (#458)
by Adrien
01:54
created

ZipHelper::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
crap 1
1
<?php
2
3
namespace Box\Spout\Writer\Common\Helper;
4
5
use Box\Spout\Writer\Common\Creator\EntityFactory;
6
7
/**
8
 * Class ZipHelper
9
 * This class provides helper functions to create zip files
10
 *
11
 * @package Box\Spout\Writer\Common\Helper
12
 */
13
class ZipHelper
14
{
15
    const ZIP_EXTENSION = '.zip';
16
17
    /** Controls what to do when trying to add an existing file */
18
    const EXISTING_FILES_SKIP = 'skip';
19
    const EXISTING_FILES_OVERWRITE = 'overwrite';
20
21
    /** @var EntityFactory Factory to create entities */
22
    private $entityFactory;
23
24
    /**
25
     * @param EntityFactory $entityFactory Factory to create entities
26
     */
27 89
    public function __construct($entityFactory)
28
    {
29 89
        $this->entityFactory = $entityFactory;
30 89
    }
31
32
    /**
33
     * Returns a new ZipArchive instance pointing at the given path.
34
     *
35
     * @param string $tmpFolderPath Path of the temp folder where the zip file will be created
36
     * @return \ZipArchive
37
     */
38 70
    public function createZip($tmpFolderPath)
39
    {
40 70
        $zip = $this->entityFactory->createZipArchive();
41 70
        $zipFilePath = $tmpFolderPath . self::ZIP_EXTENSION;
42
43 70
        $zip->open($zipFilePath, \ZipArchive::CREATE|\ZipArchive::OVERWRITE);
44
45 70
        return $zip;
46
    }
47
48
    /**
49
     * @param \ZipArchive $zip An opened zip archive object
50
     * @return string Path where the zip file of the given folder will be created
51
     */
52 70
    public function getZipFilePath(\ZipArchive $zip)
53
    {
54 70
        return $zip->filename;
55
    }
56
57
    /**
58
     * Adds the given file, located under the given root folder to the archive.
59
     * The file will be compressed.
60
     *
61
     * Example of use:
62
     *   addFileToArchive($zip, '/tmp/xlsx/foo', 'bar/baz.xml');
63
     *   => will add the file located at '/tmp/xlsx/foo/bar/baz.xml' in the archive, but only as 'bar/baz.xml'
64
     *
65
     * @param \ZipArchive $zip An opened zip archive object
66
     * @param string $rootFolderPath Path of the root folder that will be ignored in the archive tree.
67
     * @param string $localFilePath Path of the file to be added, under the root folder
68
     * @param string|void $existingFileMode Controls what to do when trying to add an existing file
69
     * @return void
70
     */
71 36
    public function addFileToArchive($zip, $rootFolderPath, $localFilePath, $existingFileMode = self::EXISTING_FILES_OVERWRITE)
72
    {
73 36
        $this->addFileToArchiveWithCompressionMethod(
74 36
            $zip,
75 36
            $rootFolderPath,
76 36
            $localFilePath,
77 36
            $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($zip, '/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 \ZipArchive $zip An opened zip archive object
91
     * @param string $rootFolderPath Path of the root folder that will be ignored in the archive tree.
92
     * @param string $localFilePath Path of the file to be added, under the root folder
93
     * @param string|void $existingFileMode Controls what to do when trying to add an existing file
94
     * @return void
95
     */
96 34
    public function addUncompressedFileToArchive($zip, $rootFolderPath, $localFilePath, $existingFileMode = self::EXISTING_FILES_OVERWRITE)
97
    {
98 34
        $this->addFileToArchiveWithCompressionMethod(
99 34
            $zip,
100 34
            $rootFolderPath,
101 34
            $localFilePath,
102 34
            $existingFileMode,
103 34
            \ZipArchive::CM_STORE
104
        );
105 34
    }
106
107
    /**
108
     * Adds the given file, located under the given root folder to the archive.
109
     * The file will NOT be compressed.
110
     *
111
     * Example of use:
112
     *   addUncompressedFileToArchive($zip, '/tmp/xlsx/foo', 'bar/baz.xml');
113
     *   => will add the file located at '/tmp/xlsx/foo/bar/baz.xml' in the archive, but only as 'bar/baz.xml'
114
     *
115
     * @param \ZipArchive $zip An opened zip archive object
116
     * @param string $rootFolderPath Path of the root folder that will be ignored in the archive tree.
117
     * @param string $localFilePath Path of the file to be added, under the root folder
118
     * @param string $existingFileMode Controls what to do when trying to add an existing file
119
     * @param int $compressionMethod The compression method
120
     * @return void
121
     */
122 70
    protected function addFileToArchiveWithCompressionMethod($zip, $rootFolderPath, $localFilePath, $existingFileMode, $compressionMethod)
123
    {
124 70
        if (!$this->shouldSkipFile($zip, $localFilePath, $existingFileMode)) {
125 70
            $normalizedFullFilePath = $this->getNormalizedRealPath($rootFolderPath . '/' . $localFilePath);
126 70
            $zip->addFile($normalizedFullFilePath, $localFilePath);
127
128 70
            if (self::canChooseCompressionMethod()) {
129 70
                $zip->setCompressionName($localFilePath, $compressionMethod);
130
            }
131
        }
132 70
    }
133
134
    /**
135
     * @return bool Whether it is possible to choose the desired compression method to be used
136
     */
137 70
    public static function canChooseCompressionMethod()
138
    {
139
        // setCompressionName() is a PHP7+ method...
140 70
        return (method_exists(new \ZipArchive(), 'setCompressionName'));
141
    }
142
143
    /**
144
     * @param \ZipArchive $zip An opened zip archive object
145
     * @param string $folderPath Path to the folder to be zipped
146
     * @param string|void $existingFileMode Controls what to do when trying to add an existing file
147
     * @return void
148
     */
149 70
    public function addFolderToArchive($zip, $folderPath, $existingFileMode = self::EXISTING_FILES_OVERWRITE)
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 \ZipArchive $zip An opened zip archive object
194
     * @param resource $streamPointer Pointer to the stream to copy the zip
195
     * @return void
196
     */
197 70
    public function closeArchiveAndCopyToStream($zip, $streamPointer)
198
    {
199 70
        $zipFilePath = $zip->filename;
200 70
        $zip->close();
201
202 70
        $this->copyZipToStream($zipFilePath, $streamPointer);
203 70
    }
204
205
    /**
206
     * Streams the contents of the zip file into the given stream
207
     *
208
     * @param string $zipFilePath Path of the zip file
209
     * @param resource $pointer Pointer to the stream to copy the zip
210
     * @return void
211
     */
212 70
    protected function copyZipToStream($zipFilePath, $pointer)
213
    {
214 70
        $zipFilePointer = fopen($zipFilePath, 'r');
215 70
        stream_copy_to_stream($zipFilePointer, $pointer);
216 70
        fclose($zipFilePointer);
217 70
    }
218
}
219