1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace PhpZip; |
4
|
|
|
|
5
|
|
|
use PhpZip\Constants\UnixStat; |
6
|
|
|
use PhpZip\Constants\ZipCompressionLevel; |
7
|
|
|
use PhpZip\Constants\ZipCompressionMethod; |
8
|
|
|
use PhpZip\Constants\ZipEncryptionMethod; |
9
|
|
|
use PhpZip\Constants\ZipOptions; |
10
|
|
|
use PhpZip\Constants\ZipPlatform; |
11
|
|
|
use PhpZip\Exception\InvalidArgumentException; |
12
|
|
|
use PhpZip\Exception\ZipEntryNotFoundException; |
13
|
|
|
use PhpZip\Exception\ZipException; |
14
|
|
|
use PhpZip\IO\Stream\ResponseStream; |
15
|
|
|
use PhpZip\IO\Stream\ZipEntryStreamWrapper; |
16
|
|
|
use PhpZip\IO\ZipReader; |
17
|
|
|
use PhpZip\IO\ZipWriter; |
18
|
|
|
use PhpZip\Model\Data\ZipFileData; |
19
|
|
|
use PhpZip\Model\Data\ZipNewData; |
20
|
|
|
use PhpZip\Model\ImmutableZipContainer; |
21
|
|
|
use PhpZip\Model\ZipContainer; |
22
|
|
|
use PhpZip\Model\ZipEntry; |
23
|
|
|
use PhpZip\Model\ZipEntryMatcher; |
24
|
|
|
use PhpZip\Model\ZipInfo; |
25
|
|
|
use PhpZip\Util\FilesUtil; |
26
|
|
|
use PhpZip\Util\StringUtil; |
27
|
|
|
use Psr\Http\Message\ResponseInterface; |
28
|
|
|
use Symfony\Component\Finder\Finder; |
29
|
|
|
use Symfony\Component\Finder\SplFileInfo as SymfonySplFileInfo; |
30
|
|
|
|
31
|
|
|
/** |
32
|
|
|
* Create, open .ZIP files, modify, get info and extract files. |
33
|
|
|
* |
34
|
|
|
* Implemented support traditional PKWARE encryption and WinZip AES encryption. |
35
|
|
|
* Implemented support ZIP64. |
36
|
|
|
* Support ZipAlign functional. |
37
|
|
|
* |
38
|
|
|
* @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification |
39
|
|
|
* |
40
|
|
|
* @author Ne-Lexa [email protected] |
41
|
|
|
* @license MIT |
42
|
|
|
*/ |
43
|
|
|
class ZipFile implements ZipFileInterface |
|
|
|
|
44
|
|
|
{ |
45
|
|
|
/** @var array default mime types */ |
46
|
|
|
private static $defaultMimeTypes = [ |
47
|
|
|
'zip' => 'application/zip', |
48
|
|
|
'apk' => 'application/vnd.android.package-archive', |
49
|
|
|
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', |
50
|
|
|
'epub' => 'application/epub+zip', |
51
|
|
|
'jar' => 'application/java-archive', |
52
|
|
|
'odt' => 'application/vnd.oasis.opendocument.text', |
53
|
|
|
'pptx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', |
54
|
|
|
'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', |
55
|
|
|
'xpi' => 'application/x-xpinstall', |
56
|
|
|
]; |
57
|
|
|
|
58
|
|
|
/** @var ZipContainer */ |
59
|
|
|
protected $zipContainer; |
60
|
|
|
|
61
|
|
|
/** @var ZipReader|null */ |
62
|
|
|
private $reader; |
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* ZipFile constructor. |
66
|
|
|
*/ |
67
|
|
|
public function __construct() |
68
|
|
|
{ |
69
|
|
|
$this->zipContainer = $this->createZipContainer(null); |
70
|
|
|
} |
71
|
313 |
|
|
72
|
|
|
/** |
73
|
313 |
|
* @param resource $inputStream |
74
|
313 |
|
* @param array $options |
75
|
|
|
* |
76
|
|
|
* @return ZipReader |
77
|
|
|
*/ |
78
|
|
|
protected function createZipReader($inputStream, array $options = []) |
79
|
|
|
{ |
80
|
|
|
return new ZipReader($inputStream, $options); |
81
|
|
|
} |
82
|
158 |
|
|
83
|
|
|
/** |
84
|
158 |
|
* @return ZipWriter |
85
|
|
|
*/ |
86
|
|
|
protected function createZipWriter() |
87
|
|
|
{ |
88
|
|
|
return new ZipWriter($this->zipContainer); |
89
|
|
|
} |
90
|
134 |
|
|
91
|
|
|
/** |
92
|
134 |
|
* @param ImmutableZipContainer|null $sourceContainer |
93
|
|
|
* |
94
|
|
|
* @return ZipContainer |
95
|
|
|
*/ |
96
|
|
|
protected function createZipContainer(ImmutableZipContainer $sourceContainer = null) |
97
|
|
|
{ |
98
|
|
|
return new ZipContainer($sourceContainer); |
99
|
|
|
} |
100
|
320 |
|
|
101
|
|
|
/** |
102
|
320 |
|
* Open zip archive from file. |
103
|
|
|
* |
104
|
|
|
* @param string $filename |
105
|
|
|
* @param array $options |
106
|
|
|
* |
107
|
|
|
* @throws ZipException if can't open file |
108
|
|
|
* |
109
|
|
|
* @return ZipFile |
110
|
|
|
*/ |
111
|
|
|
public function openFile($filename, array $options = []) |
112
|
|
|
{ |
113
|
|
|
if (!file_exists($filename)) { |
114
|
|
|
throw new ZipException("File {$filename} does not exist."); |
115
|
129 |
|
} |
116
|
|
|
|
117
|
129 |
|
if (!($handle = @fopen($filename, 'rb'))) { |
118
|
2 |
|
throw new ZipException("File {$filename} can't open."); |
119
|
|
|
} |
120
|
|
|
|
121
|
127 |
|
return $this->openFromStream($handle, $options); |
122
|
2 |
|
} |
123
|
|
|
|
124
|
|
|
/** |
125
|
125 |
|
* Open zip archive from raw string data. |
126
|
|
|
* |
127
|
|
|
* @param string $data |
128
|
|
|
* @param array $options |
129
|
|
|
* |
130
|
|
|
* @throws ZipException if can't open temp stream |
131
|
|
|
* |
132
|
|
|
* @return ZipFile |
133
|
|
|
*/ |
134
|
|
|
public function openFromString($data, array $options = []) |
135
|
|
|
{ |
136
|
|
|
if ($data === null || $data === '') { |
137
|
|
|
throw new InvalidArgumentException('Empty string passed'); |
138
|
16 |
|
} |
139
|
|
|
|
140
|
16 |
|
if (!($handle = fopen('php://temp', 'r+b'))) { |
141
|
4 |
|
// @codeCoverageIgnoreStart |
142
|
|
|
throw new ZipException('A temporary resource cannot be opened for writing.'); |
143
|
|
|
// @codeCoverageIgnoreEnd |
144
|
12 |
|
} |
145
|
|
|
fwrite($handle, $data); |
146
|
|
|
rewind($handle); |
147
|
|
|
|
148
|
|
|
return $this->openFromStream($handle, $options); |
149
|
12 |
|
} |
150
|
12 |
|
|
151
|
|
|
/** |
152
|
12 |
|
* Open zip archive from stream resource. |
153
|
|
|
* |
154
|
|
|
* @param resource $handle |
155
|
|
|
* @param array $options |
156
|
|
|
* |
157
|
|
|
* @throws ZipException |
158
|
|
|
* |
159
|
|
|
* @return ZipFile |
160
|
|
|
*/ |
161
|
|
|
public function openFromStream($handle, array $options = []) |
162
|
|
|
{ |
163
|
|
|
$this->reader = $this->createZipReader($handle, $options); |
164
|
|
|
$this->zipContainer = $this->createZipContainer($this->reader->read()); |
165
|
159 |
|
|
166
|
|
|
return $this; |
167
|
159 |
|
} |
168
|
147 |
|
|
169
|
|
|
/** |
170
|
136 |
|
* @return string[] returns the list files |
171
|
|
|
*/ |
172
|
|
|
public function getListFiles() |
173
|
|
|
{ |
174
|
|
|
// strval is needed to cast entry names to string type |
175
|
|
|
return array_map('strval', array_keys($this->zipContainer->getEntries())); |
176
|
13 |
|
} |
177
|
|
|
|
178
|
|
|
/** |
179
|
13 |
|
* @return int returns the number of entries in this ZIP file |
180
|
|
|
*/ |
181
|
|
|
public function count() |
182
|
|
|
{ |
183
|
|
|
return $this->zipContainer->count(); |
184
|
|
|
} |
185
|
53 |
|
|
186
|
|
|
/** |
187
|
53 |
|
* Returns the file comment. |
188
|
|
|
* |
189
|
|
|
* @return string|null the file comment |
190
|
|
|
*/ |
191
|
|
|
public function getArchiveComment() |
192
|
|
|
{ |
193
|
|
|
return $this->zipContainer->getArchiveComment(); |
194
|
|
|
} |
195
|
6 |
|
|
196
|
|
|
/** |
197
|
6 |
|
* Set archive comment. |
198
|
|
|
* |
199
|
|
|
* @param string|null $comment |
200
|
|
|
* |
201
|
|
|
* @return ZipFile |
202
|
|
|
*/ |
203
|
|
|
public function setArchiveComment($comment = null) |
204
|
|
|
{ |
205
|
|
|
$this->zipContainer->setArchiveComment($comment); |
206
|
|
|
|
207
|
8 |
|
return $this; |
208
|
|
|
} |
209
|
8 |
|
|
210
|
|
|
/** |
211
|
6 |
|
* Checks if there is an entry in the archive. |
212
|
|
|
* |
213
|
|
|
* @param string $entryName |
214
|
|
|
* |
215
|
|
|
* @return bool |
216
|
|
|
*/ |
217
|
|
|
public function hasEntry($entryName) |
218
|
|
|
{ |
219
|
|
|
return $this->zipContainer->hasEntry($entryName); |
220
|
|
|
} |
221
|
57 |
|
|
222
|
|
|
/** |
223
|
57 |
|
* Returns ZipEntry object. |
224
|
|
|
* |
225
|
|
|
* @param string $entryName |
226
|
|
|
* |
227
|
|
|
* @throws ZipEntryNotFoundException |
228
|
|
|
* |
229
|
|
|
* @return ZipEntry |
230
|
|
|
*/ |
231
|
|
|
public function getEntry($entryName) |
232
|
|
|
{ |
233
|
|
|
return $this->zipContainer->getEntry($entryName); |
234
|
|
|
} |
235
|
36 |
|
|
236
|
|
|
/** |
237
|
36 |
|
* Checks that the entry in the archive is a directory. |
238
|
|
|
* Returns true if and only if this ZIP entry represents a directory entry |
239
|
|
|
* (i.e. end with '/'). |
240
|
|
|
* |
241
|
|
|
* @param string $entryName |
242
|
|
|
* |
243
|
|
|
* @throws ZipEntryNotFoundException |
244
|
|
|
* |
245
|
|
|
* @return bool |
246
|
|
|
*/ |
247
|
|
|
public function isDirectory($entryName) |
248
|
|
|
{ |
249
|
|
|
return $this->getEntry($entryName)->isDirectory(); |
250
|
|
|
} |
251
|
2 |
|
|
252
|
|
|
/** |
253
|
2 |
|
* Returns entry comment. |
254
|
|
|
* |
255
|
|
|
* @param string $entryName |
256
|
|
|
* |
257
|
|
|
* @throws ZipEntryNotFoundException |
258
|
|
|
* @throws ZipException |
259
|
|
|
* |
260
|
|
|
* @return string |
261
|
|
|
*/ |
262
|
|
|
public function getEntryComment($entryName) |
263
|
|
|
{ |
264
|
|
|
return $this->getEntry($entryName)->getComment(); |
265
|
|
|
} |
266
|
2 |
|
|
267
|
|
|
/** |
268
|
2 |
|
* Set entry comment. |
269
|
|
|
* |
270
|
|
|
* @param string $entryName |
271
|
|
|
* @param string|null $comment |
272
|
|
|
* |
273
|
|
|
* @throws ZipException |
274
|
|
|
* @throws ZipEntryNotFoundException |
275
|
|
|
* |
276
|
|
|
* @return ZipFile |
277
|
|
|
*/ |
278
|
|
|
public function setEntryComment($entryName, $comment = null) |
279
|
|
|
{ |
280
|
|
|
$this->getEntry($entryName)->setComment($comment); |
281
|
|
|
|
282
|
7 |
|
return $this; |
283
|
|
|
} |
284
|
7 |
|
|
285
|
|
|
/** |
286
|
3 |
|
* Returns the entry contents. |
287
|
|
|
* |
288
|
|
|
* @param string $entryName |
289
|
|
|
* |
290
|
|
|
* @throws ZipException |
291
|
|
|
* @throws ZipEntryNotFoundException |
292
|
|
|
* |
293
|
|
|
* @return string |
294
|
|
|
*/ |
295
|
|
|
public function getEntryContents($entryName) |
296
|
|
|
{ |
297
|
|
|
$zipData = $this->zipContainer->getEntry($entryName)->getData(); |
298
|
|
|
|
299
|
72 |
|
if ($zipData === null) { |
300
|
|
|
throw new ZipException(sprintf('No data for zip entry %s', $entryName)); |
301
|
72 |
|
} |
302
|
|
|
|
303
|
70 |
|
return $zipData->getDataAsString(); |
304
|
2 |
|
} |
305
|
|
|
|
306
|
|
|
/** |
307
|
68 |
|
* @param string $entryName |
308
|
|
|
* |
309
|
|
|
* @throws ZipException |
310
|
|
|
* @throws ZipEntryNotFoundException |
311
|
|
|
* |
312
|
|
|
* @return resource |
313
|
|
|
*/ |
314
|
|
|
public function getEntryStream($entryName) |
315
|
|
|
{ |
316
|
|
|
$resource = ZipEntryStreamWrapper::wrap($this->zipContainer->getEntry($entryName)); |
317
|
|
|
rewind($resource); |
318
|
8 |
|
|
319
|
|
|
return $resource; |
320
|
8 |
|
} |
321
|
8 |
|
|
322
|
|
|
/** |
323
|
8 |
|
* Get info by entry. |
324
|
|
|
* |
325
|
|
|
* @param string|ZipEntry $entryName |
326
|
|
|
* |
327
|
|
|
* @throws ZipEntryNotFoundException |
328
|
|
|
* @throws ZipException |
329
|
|
|
* |
330
|
|
|
* @return ZipInfo |
331
|
|
|
*/ |
332
|
|
|
public function getEntryInfo($entryName) |
333
|
|
|
{ |
334
|
|
|
return new ZipInfo($this->zipContainer->getEntry($entryName)); |
|
|
|
|
335
|
|
|
} |
336
|
30 |
|
|
337
|
|
|
/** |
338
|
30 |
|
* Get info by all entries. |
339
|
|
|
* |
340
|
|
|
* @return ZipInfo[] |
341
|
|
|
*/ |
342
|
|
|
public function getAllInfo() |
343
|
|
|
{ |
344
|
|
|
$infoMap = []; |
345
|
|
|
|
346
|
13 |
|
foreach ($this->zipContainer->getEntries() as $name => $entry) { |
347
|
|
|
$infoMap[$name] = new ZipInfo($entry); |
|
|
|
|
348
|
13 |
|
} |
349
|
|
|
|
350
|
13 |
|
return $infoMap; |
351
|
13 |
|
} |
352
|
|
|
|
353
|
|
|
/** |
354
|
13 |
|
* @return ZipEntryMatcher |
355
|
|
|
*/ |
356
|
|
|
public function matcher() |
357
|
|
|
{ |
358
|
|
|
return $this->zipContainer->matcher(); |
359
|
|
|
} |
360
|
7 |
|
|
361
|
|
|
/** |
362
|
7 |
|
* Returns an array of zip records (ex. for modify time). |
363
|
|
|
* |
364
|
|
|
* @return ZipEntry[] array of raw zip entries |
365
|
|
|
*/ |
366
|
|
|
public function getEntries() |
367
|
|
|
{ |
368
|
|
|
return $this->zipContainer->getEntries(); |
369
|
|
|
} |
370
|
4 |
|
|
371
|
|
|
/** |
372
|
4 |
|
* Extract the archive contents (unzip). |
373
|
|
|
* |
374
|
|
|
* Extract the complete archive or the given files to the specified destination. |
375
|
|
|
* |
376
|
|
|
* @param string $destDir location where to extract the files |
377
|
|
|
* @param array|string|null $entries entries to extract |
378
|
|
|
* @param array $options extract options |
379
|
|
|
* @param array $extractedEntries if the extractedEntries argument |
380
|
|
|
* is present, then the specified |
381
|
|
|
* array will be filled with |
382
|
|
|
* information about the |
383
|
|
|
* extracted entries |
384
|
|
|
* |
385
|
|
|
* @throws ZipException |
386
|
|
|
* |
387
|
|
|
* @return ZipFile |
388
|
|
|
*/ |
389
|
|
|
public function extractTo($destDir, $entries = null, array $options = [], &$extractedEntries = []) |
390
|
|
|
{ |
391
|
|
|
if (!file_exists($destDir)) { |
392
|
|
|
throw new ZipException(sprintf('Destination %s not found', $destDir)); |
393
|
18 |
|
} |
394
|
|
|
|
395
|
18 |
|
if (!is_dir($destDir)) { |
396
|
2 |
|
throw new ZipException('Destination is not directory'); |
397
|
|
|
} |
398
|
|
|
|
399
|
16 |
|
if (!is_writable($destDir)) { |
400
|
2 |
|
throw new ZipException('Destination is not writable directory'); |
401
|
|
|
} |
402
|
|
|
|
403
|
14 |
|
if ($extractedEntries === null) { |
|
|
|
|
404
|
2 |
|
$extractedEntries = []; |
405
|
|
|
} |
406
|
|
|
|
407
|
12 |
|
$defaultOptions = [ |
408
|
2 |
|
ZipOptions::EXTRACT_SYMLINKS => false, |
409
|
|
|
]; |
410
|
|
|
/** @noinspection AdditionOperationOnArraysInspection */ |
411
|
|
|
$options += $defaultOptions; |
412
|
12 |
|
|
413
|
|
|
$zipEntries = $this->zipContainer->getEntries(); |
414
|
12 |
|
|
415
|
|
|
if (!empty($entries)) { |
416
|
12 |
|
if (\is_string($entries)) { |
417
|
|
|
$entries = (array) $entries; |
418
|
12 |
|
} |
419
|
3 |
|
|
420
|
2 |
|
if (\is_array($entries)) { |
|
|
|
|
421
|
|
|
$entries = array_unique($entries); |
422
|
|
|
$zipEntries = array_intersect_key($zipEntries, array_flip($entries)); |
423
|
3 |
|
} |
424
|
3 |
|
} |
425
|
3 |
|
|
426
|
|
|
if (empty($zipEntries)) { |
427
|
|
|
return $this; |
428
|
|
|
} |
429
|
12 |
|
|
430
|
2 |
|
/** @var int[] $lastModDirs */ |
431
|
|
|
$lastModDirs = []; |
432
|
|
|
|
433
|
|
|
krsort($zipEntries, \SORT_NATURAL); |
434
|
10 |
|
|
435
|
|
|
$symlinks = []; |
436
|
10 |
|
$destDir = rtrim($destDir, '/\\'); |
437
|
|
|
|
438
|
10 |
|
foreach ($zipEntries as $entryName => $entry) { |
439
|
10 |
|
$unixMode = $entry->getUnixMode(); |
440
|
|
|
$entryName = FilesUtil::normalizeZipPath($entryName); |
441
|
10 |
|
$file = $destDir . \DIRECTORY_SEPARATOR . $entryName; |
442
|
10 |
|
|
443
|
10 |
|
$extractedEntries[$file] = $entry; |
444
|
10 |
|
$modifyTimestamp = $entry->getMTime()->getTimestamp(); |
445
|
|
|
$atime = $entry->getATime(); |
446
|
10 |
|
$accessTimestamp = $atime === null ? null : $atime->getTimestamp(); |
447
|
|
|
|
448
|
|
|
$dir = $entry->isDirectory() ? $file : \dirname($file); |
449
|
10 |
|
|
450
|
10 |
|
if (!is_dir($dir)) { |
451
|
10 |
|
$dirMode = $entry->isDirectory() ? $unixMode : 0755; |
452
|
10 |
|
|
453
|
|
|
if ($dirMode === 0) { |
454
|
10 |
|
$dirMode = 0755; |
455
|
|
|
} |
456
|
10 |
|
|
457
|
6 |
|
if (!mkdir($dir, $dirMode, true) && !is_dir($dir)) { |
458
|
|
|
// @codeCoverageIgnoreStart |
459
|
6 |
|
throw new \RuntimeException(sprintf('Directory "%s" was not created', $dir)); |
460
|
|
|
// @codeCoverageIgnoreEnd |
461
|
|
|
} |
462
|
|
|
chmod($dir, $dirMode); |
463
|
6 |
|
} |
464
|
|
|
|
465
|
|
|
$parts = explode('/', rtrim($entryName, '/')); |
466
|
|
|
$path = $destDir . \DIRECTORY_SEPARATOR; |
467
|
|
|
|
468
|
6 |
|
foreach ($parts as $part) { |
469
|
|
|
if (!isset($lastModDirs[$path]) || $lastModDirs[$path] > $modifyTimestamp) { |
470
|
|
|
$lastModDirs[$path] = $modifyTimestamp; |
471
|
10 |
|
} |
472
|
10 |
|
|
473
|
|
|
$path .= $part . \DIRECTORY_SEPARATOR; |
474
|
10 |
|
} |
475
|
10 |
|
|
476
|
10 |
|
if ($entry->isDirectory()) { |
477
|
|
|
$lastModDirs[$dir] = $modifyTimestamp; |
478
|
|
|
|
479
|
10 |
|
continue; |
480
|
|
|
} |
481
|
|
|
|
482
|
10 |
|
$zipData = $entry->getData(); |
483
|
5 |
|
|
484
|
|
|
if ($zipData === null) { |
485
|
5 |
|
continue; |
486
|
|
|
} |
487
|
|
|
|
488
|
9 |
|
if ($entry->isUnixSymlink()) { |
489
|
|
|
$symlinks[$file] = $zipData->getDataAsString(); |
490
|
9 |
|
|
491
|
|
|
continue; |
492
|
|
|
} |
493
|
|
|
|
494
|
9 |
|
/** @noinspection PhpUsageOfSilenceOperatorInspection */ |
495
|
2 |
|
if (!($handle = @fopen($file, 'w+b'))) { |
496
|
|
|
// @codeCoverageIgnoreStart |
497
|
2 |
|
throw new ZipException( |
498
|
|
|
sprintf( |
499
|
|
|
'Cannot extract zip entry %s. File %s cannot open for write.', |
500
|
|
|
$entry->getName(), |
501
|
9 |
|
$file |
502
|
|
|
) |
503
|
|
|
); |
504
|
|
|
// @codeCoverageIgnoreEnd |
505
|
|
|
} |
506
|
|
|
|
507
|
|
|
try { |
508
|
|
|
$zipData->copyDataToStream($handle); |
509
|
|
|
} catch (ZipException $e) { |
510
|
|
|
unlink($file); |
511
|
|
|
|
512
|
|
|
throw $e; |
513
|
|
|
} |
514
|
9 |
|
fclose($handle); |
515
|
1 |
|
|
516
|
1 |
|
if ($unixMode === 0) { |
517
|
|
|
$unixMode = 0644; |
518
|
1 |
|
} |
519
|
|
|
chmod($file, $unixMode); |
520
|
8 |
|
|
521
|
|
|
if ($accessTimestamp !== null) { |
522
|
8 |
|
/** @noinspection PotentialMalwareInspection */ |
523
|
|
|
touch($file, $modifyTimestamp, $accessTimestamp); |
524
|
|
|
} else { |
525
|
8 |
|
touch($file, $modifyTimestamp); |
526
|
|
|
} |
527
|
8 |
|
} |
528
|
|
|
|
529
|
|
|
$allowSymlink = (bool) $options[ZipOptions::EXTRACT_SYMLINKS]; |
530
|
|
|
|
531
|
8 |
|
foreach ($symlinks as $linkPath => $target) { |
532
|
|
|
if (!FilesUtil::symlink($target, $linkPath, $allowSymlink)) { |
533
|
|
|
unset($extractedEntries[$linkPath]); |
534
|
|
|
} |
535
|
9 |
|
} |
536
|
|
|
|
537
|
9 |
|
krsort($lastModDirs, \SORT_NATURAL); |
538
|
2 |
|
|
539
|
|
|
foreach ($lastModDirs as $dir => $lastMod) { |
540
|
|
|
touch($dir, $lastMod); |
541
|
|
|
} |
542
|
|
|
|
543
|
9 |
|
ksort($extractedEntries); |
544
|
|
|
|
545
|
9 |
|
return $this; |
546
|
9 |
|
} |
547
|
|
|
|
548
|
|
|
/** |
549
|
9 |
|
* Add entry from the string. |
550
|
|
|
* |
551
|
9 |
|
* @param string $entryName zip entry name |
552
|
|
|
* @param string $contents string contents |
553
|
|
|
* @param int|null $compressionMethod Compression method. |
554
|
|
|
* Use {@see ZipCompressionMethod::STORED}, |
555
|
|
|
* {@see ZipCompressionMethod::DEFLATED} or |
556
|
|
|
* {@see ZipCompressionMethod::BZIP2}. |
557
|
|
|
* If null, then auto choosing method. |
558
|
|
|
* |
559
|
|
|
* @throws ZipException |
560
|
|
|
* |
561
|
|
|
* @return ZipFile |
562
|
|
|
*/ |
563
|
|
|
public function addFromString($entryName, $contents, $compressionMethod = null) |
564
|
|
|
{ |
565
|
|
|
$entryName = $this->normalizeEntryName($entryName); |
566
|
|
|
|
567
|
|
|
if ($contents === null) { |
|
|
|
|
568
|
|
|
throw new InvalidArgumentException('Contents is null'); |
569
|
124 |
|
} |
570
|
|
|
|
571
|
124 |
|
$contents = (string) $contents; |
572
|
2 |
|
$length = \strlen($contents); |
573
|
|
|
|
574
|
|
|
if ($compressionMethod === null || $compressionMethod === ZipEntry::UNKNOWN) { |
575
|
122 |
|
if ($length < 512) { |
576
|
2 |
|
$compressionMethod = ZipCompressionMethod::STORED; |
577
|
|
|
} else { |
578
|
|
|
$mimeType = FilesUtil::getMimeTypeFromString($contents); |
579
|
120 |
|
$compressionMethod = FilesUtil::isBadCompressionMimeType($mimeType) ? |
580
|
|
|
ZipCompressionMethod::STORED : |
581
|
120 |
|
ZipCompressionMethod::DEFLATED; |
582
|
2 |
|
} |
583
|
|
|
} |
584
|
118 |
|
|
585
|
118 |
|
$zipEntry = new ZipEntry($entryName); |
586
|
|
|
$zipEntry->setData(new ZipNewData($zipEntry, $contents)); |
587
|
118 |
|
$zipEntry->setUncompressedSize($length); |
588
|
91 |
|
$zipEntry->setCompressionMethod($compressionMethod); |
589
|
89 |
|
$zipEntry->setCreatedOS(ZipPlatform::OS_UNIX); |
590
|
|
|
$zipEntry->setExtractedOS(ZipPlatform::OS_UNIX); |
591
|
6 |
|
$zipEntry->setUnixMode(0100644); |
592
|
6 |
|
$zipEntry->setTime(time()); |
593
|
|
|
|
594
|
6 |
|
$this->addZipEntry($zipEntry); |
595
|
|
|
|
596
|
|
|
return $this; |
597
|
|
|
} |
598
|
118 |
|
|
599
|
118 |
|
/** |
600
|
118 |
|
* @param string $entryName |
601
|
118 |
|
* |
602
|
116 |
|
* @return string |
603
|
116 |
|
*/ |
604
|
116 |
|
protected function normalizeEntryName($entryName) |
605
|
116 |
|
{ |
606
|
|
|
if ($entryName === null) { |
|
|
|
|
607
|
116 |
|
throw new InvalidArgumentException('Entry name is null'); |
608
|
|
|
} |
609
|
116 |
|
|
610
|
|
|
$entryName = ltrim((string) $entryName, '\\/'); |
611
|
|
|
|
612
|
|
|
if (\DIRECTORY_SEPARATOR === '\\') { |
613
|
|
|
$entryName = str_replace('\\', '/', $entryName); |
614
|
|
|
} |
615
|
|
|
|
616
|
|
|
if ($entryName === '') { |
617
|
|
|
throw new InvalidArgumentException('Empty entry name'); |
618
|
|
|
} |
619
|
|
|
|
620
|
3 |
|
return $entryName; |
621
|
|
|
} |
622
|
|
|
|
623
|
3 |
|
/** |
624
|
3 |
|
* @param Finder $finder |
625
|
3 |
|
* @param array $options |
626
|
|
|
* |
627
|
3 |
|
* @throws ZipException |
628
|
|
|
* |
629
|
3 |
|
* @return ZipEntry[] |
630
|
|
|
*/ |
631
|
|
|
public function addFromFinder(Finder $finder, array $options = []) |
632
|
|
|
{ |
633
|
3 |
|
$defaultOptions = [ |
634
|
|
|
ZipOptions::STORE_ONLY_FILES => false, |
635
|
3 |
|
ZipOptions::COMPRESSION_METHOD => null, |
636
|
3 |
|
ZipOptions::MODIFIED_TIME => null, |
637
|
3 |
|
]; |
638
|
3 |
|
/** @noinspection AdditionOperationOnArraysInspection */ |
639
|
|
|
$options += $defaultOptions; |
640
|
|
|
|
641
|
|
|
if ($options[ZipOptions::STORE_ONLY_FILES]) { |
642
|
3 |
|
$finder->files(); |
643
|
|
|
} |
644
|
|
|
|
645
|
|
|
$entries = []; |
646
|
|
|
|
647
|
|
|
foreach ($finder as $fileInfo) { |
648
|
|
|
if ($fileInfo->isReadable()) { |
649
|
|
|
$entry = $this->addSplFile($fileInfo, null, $options); |
650
|
|
|
$entries[$entry->getName()] = $entry; |
651
|
|
|
} |
652
|
|
|
} |
653
|
|
|
|
654
|
52 |
|
return $entries; |
655
|
|
|
} |
656
|
52 |
|
|
657
|
|
|
/** |
658
|
|
|
* @param \SplFileInfo $file |
659
|
|
|
* @param string|null $entryName |
660
|
52 |
|
* @param array $options |
661
|
52 |
|
* |
662
|
|
|
* @throws ZipException |
663
|
52 |
|
* |
664
|
|
|
* @return ZipEntry |
665
|
52 |
|
*/ |
666
|
4 |
|
public function addSplFile(\SplFileInfo $file, $entryName = null, array $options = []) |
667
|
|
|
{ |
668
|
|
|
if ($file instanceof \DirectoryIterator) { |
669
|
48 |
|
throw new InvalidArgumentException('File should not be \DirectoryIterator.'); |
670
|
7 |
|
} |
671
|
3 |
|
$defaultOptions = [ |
672
|
|
|
ZipOptions::COMPRESSION_METHOD => null, |
673
|
4 |
|
ZipOptions::MODIFIED_TIME => null, |
674
|
|
|
]; |
675
|
|
|
/** @noinspection AdditionOperationOnArraysInspection */ |
676
|
|
|
$options += $defaultOptions; |
677
|
48 |
|
|
678
|
|
|
if (!$file->isReadable()) { |
679
|
48 |
|
throw new InvalidArgumentException(sprintf('File %s is not readable', $file->getPathname())); |
680
|
|
|
} |
681
|
|
|
|
682
|
|
|
if ($entryName === null) { |
683
|
48 |
|
if ($file instanceof SymfonySplFileInfo) { |
684
|
|
|
$entryName = $file->getRelativePathname(); |
685
|
48 |
|
} else { |
686
|
48 |
|
$entryName = $file->getBasename(); |
687
|
48 |
|
} |
688
|
|
|
} |
689
|
48 |
|
|
690
|
48 |
|
$entryName = $this->normalizeEntryName($entryName); |
691
|
|
|
$entryName = $file->isDir() ? rtrim($entryName, '/\\') . '/' : $entryName; |
692
|
48 |
|
|
693
|
2 |
|
$zipEntry = new ZipEntry($entryName); |
694
|
2 |
|
$zipEntry->setCreatedOS(ZipPlatform::OS_UNIX); |
695
|
|
|
$zipEntry->setExtractedOS(ZipPlatform::OS_UNIX); |
696
|
2 |
|
|
697
|
2 |
|
$zipData = null; |
698
|
2 |
|
$filePerms = $file->getPerms(); |
699
|
2 |
|
|
700
|
2 |
|
if ($file->isLink()) { |
701
|
|
|
$linkTarget = $file->getLinkTarget(); |
702
|
2 |
|
$lengthLinkTarget = \strlen($linkTarget); |
703
|
48 |
|
|
704
|
48 |
|
$zipEntry->setCompressionMethod(ZipCompressionMethod::STORED); |
705
|
3 |
|
$zipEntry->setUncompressedSize($lengthLinkTarget); |
706
|
45 |
|
$zipEntry->setCompressedSize($lengthLinkTarget); |
707
|
34 |
|
$zipEntry->setCrc(crc32($linkTarget)); |
708
|
|
|
$filePerms |= UnixStat::UNX_IFLNK; |
709
|
21 |
|
|
710
|
|
|
$zipData = new ZipNewData($zipEntry, $linkTarget); |
711
|
21 |
|
} elseif ($file->isFile()) { |
712
|
|
|
if (isset($options[ZipOptions::COMPRESSION_METHOD])) { |
713
|
|
|
$compressionMethod = $options[ZipOptions::COMPRESSION_METHOD]; |
714
|
48 |
|
} elseif ($file->getSize() < 512) { |
715
|
|
|
$compressionMethod = ZipCompressionMethod::STORED; |
716
|
46 |
|
} else { |
717
|
|
|
$compressionMethod = FilesUtil::isBadCompressionFile($file->getPathname()) ? |
718
|
|
|
ZipCompressionMethod::STORED : |
719
|
|
|
ZipCompressionMethod::DEFLATED; |
720
|
|
|
} |
721
|
|
|
|
722
|
|
|
$zipEntry->setCompressionMethod($compressionMethod); |
723
|
|
|
|
724
|
46 |
|
$zipData = new ZipFileData($zipEntry, $file); |
725
|
|
|
} elseif ($file->isDir()) { |
726
|
46 |
|
$zipEntry->setCompressionMethod(ZipCompressionMethod::STORED); |
727
|
|
|
$zipEntry->setUncompressedSize(0); |
728
|
46 |
|
$zipEntry->setCompressedSize(0); |
729
|
|
|
$zipEntry->setCrc(0); |
730
|
|
|
} |
731
|
|
|
|
732
|
|
|
$zipEntry->setUnixMode($filePerms); |
733
|
|
|
|
734
|
|
|
$timestamp = null; |
735
|
|
|
|
736
|
|
|
if (isset($options[ZipOptions::MODIFIED_TIME])) { |
737
|
|
|
$mtime = $options[ZipOptions::MODIFIED_TIME]; |
738
|
|
|
|
739
|
|
|
if ($mtime instanceof \DateTimeInterface) { |
740
|
|
|
$timestamp = $mtime->getTimestamp(); |
741
|
|
|
} elseif (is_numeric($mtime)) { |
742
|
|
|
$timestamp = (int) $mtime; |
743
|
|
|
} elseif (\is_string($mtime)) { |
744
|
46 |
|
$timestamp = strtotime($mtime); |
745
|
46 |
|
|
746
|
|
|
if ($timestamp === false) { |
747
|
|
|
$timestamp = null; |
748
|
46 |
|
} |
749
|
46 |
|
} |
750
|
|
|
} |
751
|
46 |
|
|
752
|
|
|
if ($timestamp === null) { |
753
|
46 |
|
$timestamp = $file->getMTime(); |
754
|
|
|
} |
755
|
|
|
|
756
|
|
|
$zipEntry->setTime($timestamp); |
757
|
|
|
$zipEntry->setData($zipData); |
758
|
|
|
|
759
|
158 |
|
$this->addZipEntry($zipEntry); |
760
|
|
|
|
761
|
158 |
|
return $zipEntry; |
762
|
158 |
|
} |
763
|
|
|
|
764
|
|
|
/** |
765
|
|
|
* @param ZipEntry $zipEntry |
766
|
|
|
*/ |
767
|
|
|
protected function addZipEntry(ZipEntry $zipEntry) |
768
|
|
|
{ |
769
|
|
|
$this->zipContainer->addEntry($zipEntry); |
770
|
|
|
} |
771
|
|
|
|
772
|
|
|
/** |
773
|
|
|
* Add entry from the file. |
774
|
|
|
* |
775
|
|
|
* @param string $filename destination file |
776
|
|
|
* @param string|null $entryName zip Entry name |
777
|
|
|
* @param int|null $compressionMethod Compression method. |
778
|
|
|
* Use {@see ZipCompressionMethod::STORED}, |
779
|
49 |
|
* {@see ZipCompressionMethod::DEFLATED} or |
780
|
|
|
* {@see ZipCompressionMethod::BZIP2}. |
781
|
49 |
|
* If null, then auto choosing method. |
782
|
2 |
|
* |
783
|
|
|
* @throws ZipException |
784
|
|
|
* |
785
|
47 |
|
* @return ZipFile |
786
|
47 |
|
*/ |
787
|
|
|
public function addFile($filename, $entryName = null, $compressionMethod = null) |
788
|
|
|
{ |
789
|
47 |
|
if ($filename === null) { |
|
|
|
|
790
|
|
|
throw new InvalidArgumentException('Filename is null'); |
791
|
|
|
} |
792
|
|
|
|
793
|
41 |
|
$this->addSplFile( |
794
|
|
|
new \SplFileInfo($filename), |
795
|
|
|
$entryName, |
796
|
|
|
[ |
797
|
|
|
ZipOptions::COMPRESSION_METHOD => $compressionMethod, |
798
|
|
|
] |
799
|
|
|
); |
800
|
|
|
|
801
|
|
|
return $this; |
802
|
|
|
} |
803
|
|
|
|
804
|
|
|
/** |
805
|
|
|
* Add entry from the stream. |
806
|
|
|
* |
807
|
|
|
* @param resource $stream stream resource |
808
|
|
|
* @param string $entryName zip Entry name |
809
|
|
|
* @param int|null $compressionMethod Compression method. |
810
|
|
|
* Use {@see ZipCompressionMethod::STORED}, |
811
|
13 |
|
* {@see ZipCompressionMethod::DEFLATED} or |
812
|
|
|
* {@see ZipCompressionMethod::BZIP2}. |
813
|
13 |
|
* If null, then auto choosing method. |
814
|
2 |
|
* |
815
|
|
|
* @throws ZipException |
816
|
|
|
* |
817
|
11 |
|
* @return ZipFile |
818
|
|
|
*/ |
819
|
|
|
public function addFromStream($stream, $entryName, $compressionMethod = null) |
820
|
11 |
|
{ |
821
|
|
|
if (!\is_resource($stream)) { |
822
|
11 |
|
throw new InvalidArgumentException('Stream is not resource'); |
823
|
2 |
|
} |
824
|
|
|
|
825
|
9 |
|
$entryName = $this->normalizeEntryName($entryName); |
826
|
|
|
$zipEntry = new ZipEntry($entryName); |
827
|
9 |
|
$fstat = fstat($stream); |
828
|
|
|
|
829
|
9 |
|
if ($fstat !== false) { |
830
|
8 |
|
$unixMode = $fstat['mode']; |
831
|
8 |
|
$length = $fstat['size']; |
832
|
|
|
|
833
|
8 |
|
if ($compressionMethod === null || $compressionMethod === ZipEntry::UNKNOWN) { |
834
|
6 |
|
if ($length < 512) { |
835
|
2 |
|
$compressionMethod = ZipCompressionMethod::STORED; |
836
|
|
|
} else { |
837
|
6 |
|
rewind($stream); |
838
|
6 |
|
$bufferContents = stream_get_contents($stream, min(1024, $length)); |
839
|
6 |
|
rewind($stream); |
840
|
6 |
|
$mimeType = FilesUtil::getMimeTypeFromString($bufferContents); |
841
|
6 |
|
$compressionMethod = FilesUtil::isBadCompressionMimeType($mimeType) ? |
842
|
|
|
ZipCompressionMethod::STORED : |
843
|
6 |
|
ZipCompressionMethod::DEFLATED; |
844
|
|
|
} |
845
|
8 |
|
$zipEntry->setUncompressedSize($length); |
846
|
|
|
} |
847
|
|
|
} else { |
848
|
1 |
|
$unixMode = 0100644; |
849
|
|
|
|
850
|
1 |
|
if ($compressionMethod === null || $compressionMethod === ZipEntry::UNKNOWN) { |
851
|
1 |
|
$compressionMethod = ZipCompressionMethod::DEFLATED; |
852
|
|
|
} |
853
|
|
|
} |
854
|
|
|
|
855
|
9 |
|
$zipEntry->setCreatedOS(ZipPlatform::OS_UNIX); |
856
|
9 |
|
$zipEntry->setExtractedOS(ZipPlatform::OS_UNIX); |
857
|
9 |
|
$zipEntry->setUnixMode($unixMode); |
858
|
9 |
|
$zipEntry->setCompressionMethod($compressionMethod); |
859
|
7 |
|
$zipEntry->setTime(time()); |
860
|
7 |
|
$zipEntry->setData(new ZipNewData($zipEntry, $stream)); |
861
|
|
|
|
862
|
7 |
|
$this->addZipEntry($zipEntry); |
863
|
|
|
|
864
|
7 |
|
return $this; |
865
|
|
|
} |
866
|
|
|
|
867
|
|
|
/** |
868
|
|
|
* Add an empty directory in the zip archive. |
869
|
|
|
* |
870
|
|
|
* @param string $dirName |
871
|
|
|
* |
872
|
|
|
* @throws ZipException |
873
|
|
|
* |
874
|
|
|
* @return ZipFile |
875
|
|
|
*/ |
876
|
27 |
|
public function addEmptyDir($dirName) |
877
|
|
|
{ |
878
|
27 |
|
$dirName = $this->normalizeEntryName($dirName); |
879
|
2 |
|
$dirName = rtrim($dirName, '\\/') . '/'; |
880
|
|
|
|
881
|
25 |
|
$zipEntry = new ZipEntry($dirName); |
882
|
|
|
$zipEntry->setCompressionMethod(ZipCompressionMethod::STORED); |
883
|
25 |
|
$zipEntry->setUncompressedSize(0); |
884
|
2 |
|
$zipEntry->setCompressedSize(0); |
885
|
|
|
$zipEntry->setCrc(0); |
886
|
23 |
|
$zipEntry->setCreatedOS(ZipPlatform::OS_UNIX); |
887
|
|
|
$zipEntry->setExtractedOS(ZipPlatform::OS_UNIX); |
888
|
23 |
|
$zipEntry->setUnixMode(040755); |
889
|
23 |
|
$zipEntry->setTime(time()); |
890
|
23 |
|
|
891
|
23 |
|
$this->addZipEntry($zipEntry); |
892
|
23 |
|
|
893
|
23 |
|
return $this; |
894
|
23 |
|
} |
895
|
23 |
|
|
896
|
23 |
|
/** |
897
|
|
|
* Add directory not recursively to the zip archive. |
898
|
23 |
|
* |
899
|
|
|
* @param string $inputDir Input directory |
900
|
23 |
|
* @param string $localPath add files to this directory, or the root |
901
|
|
|
* @param int|null $compressionMethod Compression method. |
902
|
|
|
* |
903
|
|
|
* Use {@see ZipCompressionMethod::STORED}, {@see |
904
|
|
|
* ZipCompressionMethod::DEFLATED} or |
905
|
|
|
* {@see ZipCompressionMethod::BZIP2}. If null, then auto choosing method. |
906
|
|
|
* |
907
|
|
|
* @throws ZipException |
908
|
|
|
* |
909
|
|
|
* @return ZipFile |
910
|
|
|
*/ |
911
|
|
|
public function addDir($inputDir, $localPath = '/', $compressionMethod = null) |
912
|
|
|
{ |
913
|
|
|
if ($inputDir === null) { |
|
|
|
|
914
|
|
|
throw new InvalidArgumentException('Input dir is null'); |
915
|
|
|
} |
916
|
|
|
$inputDir = (string) $inputDir; |
917
|
|
|
|
918
|
15 |
|
if ($inputDir === '') { |
919
|
|
|
throw new InvalidArgumentException('The input directory is not specified'); |
920
|
15 |
|
} |
921
|
2 |
|
|
922
|
|
|
if (!is_dir($inputDir)) { |
923
|
13 |
|
throw new InvalidArgumentException(sprintf('The "%s" directory does not exist.', $inputDir)); |
924
|
|
|
} |
925
|
13 |
|
$inputDir = rtrim($inputDir, '/\\') . \DIRECTORY_SEPARATOR; |
926
|
2 |
|
|
927
|
|
|
$directoryIterator = new \DirectoryIterator($inputDir); |
928
|
|
|
|
929
|
11 |
|
return $this->addFilesFromIterator($directoryIterator, $localPath, $compressionMethod); |
930
|
2 |
|
} |
931
|
|
|
|
932
|
9 |
|
/** |
933
|
|
|
* Add recursive directory to the zip archive. |
934
|
9 |
|
* |
935
|
|
|
* @param string $inputDir Input directory |
936
|
9 |
|
* @param string $localPath add files to this directory, or the root |
937
|
|
|
* @param int|null $compressionMethod Compression method. |
938
|
|
|
* Use {@see ZipCompressionMethod::STORED}, {@see |
939
|
|
|
* ZipCompressionMethod::DEFLATED} or |
940
|
|
|
* {@see ZipCompressionMethod::BZIP2}. If null, then auto choosing method. |
941
|
|
|
* |
942
|
|
|
* @throws ZipException |
943
|
|
|
* |
944
|
|
|
* @return ZipFile |
945
|
|
|
* |
946
|
|
|
* @see ZipCompressionMethod::STORED |
947
|
|
|
* @see ZipCompressionMethod::DEFLATED |
948
|
|
|
* @see ZipCompressionMethod::BZIP2 |
949
|
|
|
*/ |
950
|
|
|
public function addDirRecursive($inputDir, $localPath = '/', $compressionMethod = null) |
951
|
|
|
{ |
952
|
|
|
if ($inputDir === null) { |
|
|
|
|
953
|
|
|
throw new InvalidArgumentException('Input dir is null'); |
954
|
|
|
} |
955
|
|
|
$inputDir = (string) $inputDir; |
956
|
|
|
|
957
|
16 |
|
if ($inputDir === '') { |
958
|
|
|
throw new InvalidArgumentException('The input directory is not specified'); |
959
|
16 |
|
} |
960
|
2 |
|
|
961
|
|
|
if (!is_dir($inputDir)) { |
962
|
14 |
|
throw new InvalidArgumentException(sprintf('The "%s" directory does not exist.', $inputDir)); |
963
|
|
|
} |
964
|
14 |
|
$inputDir = rtrim($inputDir, '/\\') . \DIRECTORY_SEPARATOR; |
965
|
2 |
|
|
966
|
|
|
$directoryIterator = new \RecursiveDirectoryIterator($inputDir); |
967
|
|
|
|
968
|
12 |
|
return $this->addFilesFromIterator($directoryIterator, $localPath, $compressionMethod); |
969
|
2 |
|
} |
970
|
|
|
|
971
|
10 |
|
/** |
972
|
|
|
* Add directories from directory iterator. |
973
|
10 |
|
* |
974
|
|
|
* @param \Iterator $iterator directory iterator |
975
|
10 |
|
* @param string $localPath add files to this directory, or the root |
976
|
|
|
* @param int|null $compressionMethod Compression method. |
977
|
|
|
* Use {@see ZipCompressionMethod::STORED}, {@see |
978
|
|
|
* ZipCompressionMethod::DEFLATED} or |
979
|
|
|
* {@see ZipCompressionMethod::BZIP2}. If null, then auto choosing method. |
980
|
|
|
* |
981
|
|
|
* @throws ZipException |
982
|
|
|
* |
983
|
|
|
* @return ZipFile |
984
|
|
|
* |
985
|
|
|
* @see ZipCompressionMethod::STORED |
986
|
|
|
* @see ZipCompressionMethod::DEFLATED |
987
|
|
|
* @see ZipCompressionMethod::BZIP2 |
988
|
|
|
*/ |
989
|
|
|
public function addFilesFromIterator( |
990
|
|
|
\Iterator $iterator, |
991
|
|
|
$localPath = '/', |
992
|
|
|
$compressionMethod = null |
993
|
|
|
) { |
994
|
|
|
$localPath = (string) $localPath; |
995
|
|
|
|
996
|
25 |
|
if ($localPath !== '') { |
997
|
|
|
$localPath = trim($localPath, '\\/'); |
998
|
|
|
} else { |
999
|
|
|
$localPath = ''; |
1000
|
|
|
} |
1001
|
25 |
|
|
1002
|
|
|
$iterator = $iterator instanceof \RecursiveIterator ? |
1003
|
25 |
|
new \RecursiveIteratorIterator($iterator) : |
1004
|
24 |
|
new \IteratorIterator($iterator); |
1005
|
|
|
/** |
1006
|
1 |
|
* @var string[] $files |
1007
|
|
|
* @var string $path |
1008
|
|
|
*/ |
1009
|
25 |
|
$files = []; |
1010
|
13 |
|
|
1011
|
25 |
|
foreach ($iterator as $file) { |
1012
|
|
|
if ($file instanceof \SplFileInfo) { |
1013
|
|
|
if ($file->getBasename() === '..') { |
1014
|
|
|
continue; |
1015
|
|
|
} |
1016
|
25 |
|
|
1017
|
|
|
if ($file->getBasename() === '.') { |
1018
|
25 |
|
$files[] = \dirname($file->getPathname()); |
1019
|
25 |
|
} else { |
1020
|
25 |
|
$files[] = $file->getPathname(); |
1021
|
23 |
|
} |
1022
|
|
|
} |
1023
|
|
|
} |
1024
|
25 |
|
|
1025
|
25 |
|
if (empty($files)) { |
1026
|
|
|
return $this; |
1027
|
25 |
|
} |
1028
|
|
|
|
1029
|
|
|
natcasesort($files); |
1030
|
|
|
$path = array_shift($files); |
1031
|
|
|
|
1032
|
25 |
|
$this->doAddFiles($path, $files, $localPath, $compressionMethod); |
1033
|
|
|
|
1034
|
|
|
return $this; |
1035
|
|
|
} |
1036
|
25 |
|
|
1037
|
25 |
|
/** |
1038
|
|
|
* Add files from glob pattern. |
1039
|
25 |
|
* |
1040
|
|
|
* @param string $inputDir Input directory |
1041
|
25 |
|
* @param string $globPattern glob pattern |
1042
|
|
|
* @param string $localPath add files to this directory, or the root |
1043
|
|
|
* @param int|null $compressionMethod Compression method. |
1044
|
|
|
* Use {@see ZipCompressionMethod::STORED}, |
1045
|
|
|
* {@see ZipCompressionMethod::DEFLATED} or |
1046
|
|
|
* {@see ZipCompressionMethod::BZIP2}. If null, then auto choosing method. |
1047
|
|
|
* |
1048
|
|
|
* @throws ZipException |
1049
|
|
|
* |
1050
|
|
|
* @return ZipFile |
1051
|
|
|
* @sse https://en.wikipedia.org/wiki/Glob_(programming) Glob pattern syntax |
1052
|
|
|
*/ |
1053
|
|
|
public function addFilesFromGlob($inputDir, $globPattern, $localPath = '/', $compressionMethod = null) |
1054
|
|
|
{ |
1055
|
|
|
return $this->addGlob($inputDir, $globPattern, $localPath, false, $compressionMethod); |
1056
|
|
|
} |
1057
|
|
|
|
1058
|
|
|
/** |
1059
|
|
|
* Add files from glob pattern. |
1060
|
11 |
|
* |
1061
|
|
|
* @param string $inputDir Input directory |
1062
|
11 |
|
* @param string $globPattern glob pattern |
1063
|
|
|
* @param string $localPath add files to this directory, or the root |
1064
|
|
|
* @param bool $recursive recursive search |
1065
|
|
|
* @param int|null $compressionMethod Compression method. |
1066
|
|
|
* Use {@see ZipCompressionMethod::STORED}, |
1067
|
|
|
* {@see ZipCompressionMethod::DEFLATED} or |
1068
|
|
|
* {@see ZipCompressionMethod::BZIP2}. If null, then auto choosing method. |
1069
|
|
|
* |
1070
|
|
|
* @throws ZipException |
1071
|
|
|
* |
1072
|
|
|
* @return ZipFile |
1073
|
|
|
* |
1074
|
|
|
* @sse https://en.wikipedia.org/wiki/Glob_(programming) Glob pattern syntax |
1075
|
|
|
*/ |
1076
|
|
|
private function addGlob( |
1077
|
|
|
$inputDir, |
1078
|
|
|
$globPattern, |
1079
|
|
|
$localPath = '/', |
1080
|
|
|
$recursive = true, |
1081
|
|
|
$compressionMethod = null |
1082
|
26 |
|
) { |
1083
|
|
|
if ($inputDir === null) { |
|
|
|
|
1084
|
|
|
throw new InvalidArgumentException('Input dir is null'); |
1085
|
|
|
} |
1086
|
|
|
$inputDir = (string) $inputDir; |
1087
|
|
|
|
1088
|
|
|
if ($inputDir === '') { |
1089
|
26 |
|
throw new InvalidArgumentException('The input directory is not specified'); |
1090
|
4 |
|
} |
1091
|
|
|
|
1092
|
22 |
|
if (!is_dir($inputDir)) { |
1093
|
|
|
throw new InvalidArgumentException(sprintf('The "%s" directory does not exist.', $inputDir)); |
1094
|
22 |
|
} |
1095
|
4 |
|
$globPattern = (string) $globPattern; |
1096
|
|
|
|
1097
|
|
|
if (empty($globPattern)) { |
1098
|
18 |
|
throw new InvalidArgumentException('The glob pattern is not specified'); |
1099
|
6 |
|
} |
1100
|
|
|
|
1101
|
12 |
|
$inputDir = rtrim($inputDir, '/\\') . \DIRECTORY_SEPARATOR; |
1102
|
|
|
$globPattern = $inputDir . $globPattern; |
1103
|
12 |
|
|
1104
|
8 |
|
$filesFound = FilesUtil::globFileSearch($globPattern, \GLOB_BRACE, $recursive); |
1105
|
|
|
|
1106
|
|
|
if ($filesFound === false || empty($filesFound)) { |
1107
|
4 |
|
return $this; |
1108
|
4 |
|
} |
1109
|
|
|
|
1110
|
4 |
|
$this->doAddFiles($inputDir, $filesFound, $localPath, $compressionMethod); |
1111
|
|
|
|
1112
|
4 |
|
return $this; |
1113
|
|
|
} |
1114
|
|
|
|
1115
|
|
|
/** |
1116
|
4 |
|
* Add files recursively from glob pattern. |
1117
|
|
|
* |
1118
|
4 |
|
* @param string $inputDir Input directory |
1119
|
|
|
* @param string $globPattern glob pattern |
1120
|
|
|
* @param string $localPath add files to this directory, or the root |
1121
|
|
|
* @param int|null $compressionMethod Compression method. |
1122
|
|
|
* Use {@see ZipCompressionMethod::STORED}, |
1123
|
|
|
* {@see ZipCompressionMethod::DEFLATED} or |
1124
|
|
|
* {@see ZipCompressionMethod::BZIP2}. If null, then auto choosing method. |
1125
|
|
|
* |
1126
|
|
|
* @throws ZipException |
1127
|
|
|
* |
1128
|
|
|
* @return ZipFile |
1129
|
|
|
* @sse https://en.wikipedia.org/wiki/Glob_(programming) Glob pattern syntax |
1130
|
|
|
*/ |
1131
|
|
|
public function addFilesFromGlobRecursive($inputDir, $globPattern, $localPath = '/', $compressionMethod = null) |
1132
|
|
|
{ |
1133
|
|
|
return $this->addGlob($inputDir, $globPattern, $localPath, true, $compressionMethod); |
1134
|
|
|
} |
1135
|
|
|
|
1136
|
|
|
/** |
1137
|
15 |
|
* Add files from regex pattern. |
1138
|
|
|
* |
1139
|
15 |
|
* @param string $inputDir search files in this directory |
1140
|
|
|
* @param string $regexPattern regex pattern |
1141
|
|
|
* @param string $localPath add files to this directory, or the root |
1142
|
|
|
* @param int|null $compressionMethod Compression method. |
1143
|
|
|
* Use {@see ZipCompressionMethod::STORED}, |
1144
|
|
|
* {@see ZipCompressionMethod::DEFLATED} or |
1145
|
|
|
* {@see ZipCompressionMethod::BZIP2}. If null, then auto choosing method. |
1146
|
|
|
* |
1147
|
|
|
* @throws ZipException |
1148
|
|
|
* |
1149
|
|
|
* @return ZipFile |
1150
|
|
|
* |
1151
|
|
|
* @internal param bool $recursive Recursive search |
1152
|
|
|
*/ |
1153
|
|
|
public function addFilesFromRegex($inputDir, $regexPattern, $localPath = '/', $compressionMethod = null) |
1154
|
|
|
{ |
1155
|
|
|
return $this->addRegex($inputDir, $regexPattern, $localPath, false, $compressionMethod); |
1156
|
|
|
} |
1157
|
|
|
|
1158
|
|
|
/** |
1159
|
11 |
|
* Add files from regex pattern. |
1160
|
|
|
* |
1161
|
11 |
|
* @param string $inputDir search files in this directory |
1162
|
|
|
* @param string $regexPattern regex pattern |
1163
|
|
|
* @param string $localPath add files to this directory, or the root |
1164
|
|
|
* @param bool $recursive recursive search |
1165
|
|
|
* @param int|null $compressionMethod Compression method. |
1166
|
|
|
* Use {@see ZipCompressionMethod::STORED}, |
1167
|
|
|
* {@see ZipCompressionMethod::DEFLATED} or |
1168
|
|
|
* {@see ZipCompressionMethod::BZIP2}. |
1169
|
|
|
* If null, then auto choosing method. |
1170
|
|
|
* |
1171
|
|
|
* @throws ZipException |
1172
|
|
|
* |
1173
|
|
|
* @return ZipFile |
1174
|
|
|
*/ |
1175
|
|
|
private function addRegex( |
1176
|
|
|
$inputDir, |
1177
|
|
|
$regexPattern, |
1178
|
|
|
$localPath = '/', |
1179
|
|
|
$recursive = true, |
1180
|
|
|
$compressionMethod = null |
1181
|
22 |
|
) { |
1182
|
|
|
$regexPattern = (string) $regexPattern; |
1183
|
|
|
|
1184
|
|
|
if (empty($regexPattern)) { |
1185
|
|
|
throw new InvalidArgumentException('The regex pattern is not specified'); |
1186
|
|
|
} |
1187
|
|
|
$inputDir = (string) $inputDir; |
1188
|
22 |
|
|
1189
|
|
|
if ($inputDir === '') { |
1190
|
22 |
|
throw new InvalidArgumentException('The input directory is not specified'); |
1191
|
8 |
|
} |
1192
|
|
|
|
1193
|
14 |
|
if (!is_dir($inputDir)) { |
1194
|
|
|
throw new InvalidArgumentException(sprintf('The "%s" directory does not exist.', $inputDir)); |
1195
|
14 |
|
} |
1196
|
8 |
|
$inputDir = rtrim($inputDir, '/\\') . \DIRECTORY_SEPARATOR; |
1197
|
|
|
|
1198
|
|
|
$files = FilesUtil::regexFileSearch($inputDir, $regexPattern, $recursive); |
1199
|
6 |
|
|
1200
|
2 |
|
if (empty($files)) { |
1201
|
|
|
return $this; |
1202
|
4 |
|
} |
1203
|
|
|
|
1204
|
4 |
|
$this->doAddFiles($inputDir, $files, $localPath, $compressionMethod); |
1205
|
|
|
|
1206
|
4 |
|
return $this; |
1207
|
|
|
} |
1208
|
|
|
|
1209
|
|
|
/** |
1210
|
4 |
|
* @param string $fileSystemDir |
1211
|
|
|
* @param array $files |
1212
|
4 |
|
* @param string $zipPath |
1213
|
|
|
* @param int|null $compressionMethod |
1214
|
|
|
* |
1215
|
|
|
* @throws ZipException |
1216
|
|
|
*/ |
1217
|
|
|
private function doAddFiles($fileSystemDir, array $files, $zipPath, $compressionMethod = null) |
1218
|
|
|
{ |
1219
|
|
|
$fileSystemDir = rtrim($fileSystemDir, '/\\') . \DIRECTORY_SEPARATOR; |
1220
|
|
|
|
1221
|
|
|
if (!empty($zipPath) && \is_string($zipPath)) { |
1222
|
|
|
$zipPath = trim($zipPath, '\\/') . '/'; |
1223
|
33 |
|
} else { |
1224
|
|
|
$zipPath = '/'; |
1225
|
33 |
|
} |
1226
|
|
|
|
1227
|
33 |
|
/** |
1228
|
15 |
|
* @var string $file |
1229
|
|
|
*/ |
1230
|
18 |
|
foreach ($files as $file) { |
1231
|
|
|
$filename = str_replace($fileSystemDir, $zipPath, $file); |
1232
|
|
|
$filename = ltrim($filename, '\\/'); |
1233
|
|
|
|
1234
|
|
|
if (is_dir($file) && FilesUtil::isEmptyDir($file)) { |
1235
|
|
|
$this->addEmptyDir($filename); |
1236
|
33 |
|
} elseif (is_file($file)) { |
1237
|
33 |
|
$this->addFile($file, $filename, $compressionMethod); |
1238
|
33 |
|
} |
1239
|
|
|
} |
1240
|
33 |
|
} |
1241
|
15 |
|
|
1242
|
33 |
|
/** |
1243
|
33 |
|
* Add files recursively from regex pattern. |
1244
|
|
|
* |
1245
|
|
|
* @param string $inputDir search files in this directory |
1246
|
33 |
|
* @param string $regexPattern regex pattern |
1247
|
|
|
* @param string $localPath add files to this directory, or the root |
1248
|
|
|
* @param int|null $compressionMethod Compression method. |
1249
|
|
|
* Use {@see ZipCompressionMethod::STORED}, |
1250
|
|
|
* {@see ZipCompressionMethod::DEFLATED} or |
1251
|
|
|
* {@see ZipCompressionMethod::BZIP2}. If null, then auto choosing method. |
1252
|
|
|
* |
1253
|
|
|
* @throws ZipException |
1254
|
|
|
* |
1255
|
|
|
* @return ZipFile |
1256
|
|
|
* |
1257
|
|
|
* @internal param bool $recursive Recursive search |
1258
|
|
|
*/ |
1259
|
|
|
public function addFilesFromRegexRecursive($inputDir, $regexPattern, $localPath = '/', $compressionMethod = null) |
1260
|
|
|
{ |
1261
|
|
|
return $this->addRegex($inputDir, $regexPattern, $localPath, true, $compressionMethod); |
1262
|
|
|
} |
1263
|
|
|
|
1264
|
|
|
/** |
1265
|
11 |
|
* Add array data to archive. |
1266
|
|
|
* Keys is local names. |
1267
|
11 |
|
* Values is contents. |
1268
|
|
|
* |
1269
|
|
|
* @param array $mapData associative array for added to zip |
1270
|
|
|
*/ |
1271
|
|
|
public function addAll(array $mapData) |
1272
|
|
|
{ |
1273
|
|
|
foreach ($mapData as $localName => $content) { |
1274
|
|
|
$this[$localName] = $content; |
1275
|
|
|
} |
1276
|
|
|
} |
1277
|
2 |
|
|
1278
|
|
|
/** |
1279
|
2 |
|
* Rename the entry. |
1280
|
2 |
|
* |
1281
|
|
|
* @param string $oldName old entry name |
1282
|
2 |
|
* @param string $newName new entry name |
1283
|
|
|
* |
1284
|
|
|
* @throws ZipException |
1285
|
|
|
* |
1286
|
|
|
* @return ZipFile |
1287
|
|
|
*/ |
1288
|
|
|
public function rename($oldName, $newName) |
1289
|
|
|
{ |
1290
|
|
|
if ($oldName === null || $newName === null) { |
|
|
|
|
1291
|
|
|
throw new InvalidArgumentException('name is null'); |
1292
|
|
|
} |
1293
|
|
|
$oldName = ltrim((string) $oldName, '\\/'); |
1294
|
13 |
|
$newName = ltrim((string) $newName, '\\/'); |
1295
|
|
|
|
1296
|
13 |
|
if ($oldName !== $newName) { |
1297
|
4 |
|
$this->zipContainer->renameEntry($oldName, $newName); |
1298
|
|
|
} |
1299
|
9 |
|
|
1300
|
9 |
|
return $this; |
1301
|
|
|
} |
1302
|
9 |
|
|
1303
|
9 |
|
/** |
1304
|
|
|
* Delete entry by name. |
1305
|
|
|
* |
1306
|
5 |
|
* @param string $entryName zip Entry name |
1307
|
|
|
* |
1308
|
|
|
* @throws ZipEntryNotFoundException if entry not found |
1309
|
|
|
* |
1310
|
|
|
* @return ZipFile |
1311
|
|
|
*/ |
1312
|
|
|
public function deleteFromName($entryName) |
1313
|
|
|
{ |
1314
|
|
|
$entryName = ltrim((string) $entryName, '\\/'); |
1315
|
|
|
|
1316
|
|
|
if (!$this->zipContainer->deleteEntry($entryName)) { |
1317
|
|
|
throw new ZipEntryNotFoundException($entryName); |
1318
|
10 |
|
} |
1319
|
|
|
|
1320
|
10 |
|
return $this; |
1321
|
|
|
} |
1322
|
10 |
|
|
1323
|
2 |
|
/** |
1324
|
|
|
* Delete entries by glob pattern. |
1325
|
|
|
* |
1326
|
8 |
|
* @param string $globPattern Glob pattern |
1327
|
|
|
* |
1328
|
|
|
* @return ZipFile |
1329
|
|
|
* @sse https://en.wikipedia.org/wiki/Glob_(programming) Glob pattern syntax |
1330
|
|
|
*/ |
1331
|
|
|
public function deleteFromGlob($globPattern) |
1332
|
|
|
{ |
1333
|
|
|
if ($globPattern === null || !\is_string($globPattern) || empty($globPattern)) { |
|
|
|
|
1334
|
|
|
throw new InvalidArgumentException('The glob pattern is not specified'); |
1335
|
|
|
} |
1336
|
|
|
$globPattern = '~' . FilesUtil::convertGlobToRegEx($globPattern) . '~si'; |
1337
|
6 |
|
$this->deleteFromRegex($globPattern); |
1338
|
|
|
|
1339
|
6 |
|
return $this; |
1340
|
4 |
|
} |
1341
|
|
|
|
1342
|
2 |
|
/** |
1343
|
2 |
|
* Delete entries by regex pattern. |
1344
|
|
|
* |
1345
|
2 |
|
* @param string $regexPattern Regex pattern |
1346
|
|
|
* |
1347
|
|
|
* @return ZipFile |
1348
|
|
|
*/ |
1349
|
|
|
public function deleteFromRegex($regexPattern) |
1350
|
|
|
{ |
1351
|
|
|
if ($regexPattern === null || !\is_string($regexPattern) || empty($regexPattern)) { |
|
|
|
|
1352
|
|
|
throw new InvalidArgumentException('The regex pattern is not specified'); |
1353
|
|
|
} |
1354
|
|
|
$this->matcher()->match($regexPattern)->delete(); |
1355
|
9 |
|
|
1356
|
|
|
return $this; |
1357
|
9 |
|
} |
1358
|
4 |
|
|
1359
|
|
|
/** |
1360
|
5 |
|
* Delete all entries. |
1361
|
|
|
* |
1362
|
5 |
|
* @return ZipFile |
1363
|
|
|
*/ |
1364
|
|
|
public function deleteAll() |
1365
|
|
|
{ |
1366
|
|
|
$this->zipContainer->deleteAll(); |
1367
|
|
|
|
1368
|
|
|
return $this; |
1369
|
|
|
} |
1370
|
2 |
|
|
1371
|
|
|
/** |
1372
|
2 |
|
* Set compression level for new entries. |
1373
|
|
|
* |
1374
|
2 |
|
* @param int $compressionLevel |
1375
|
|
|
* |
1376
|
|
|
* @return ZipFile |
1377
|
|
|
* |
1378
|
|
|
* @see ZipCompressionLevel::NORMAL |
1379
|
|
|
* @see ZipCompressionLevel::SUPER_FAST |
1380
|
|
|
* @see ZipCompressionLevel::FAST |
1381
|
|
|
* @see ZipCompressionLevel::MAXIMUM |
1382
|
|
|
*/ |
1383
|
|
|
public function setCompressionLevel($compressionLevel = ZipCompressionLevel::NORMAL) |
1384
|
|
|
{ |
1385
|
|
|
$compressionLevel = (int) $compressionLevel; |
1386
|
|
|
|
1387
|
|
|
foreach ($this->zipContainer->getEntries() as $entry) { |
1388
|
|
|
$entry->setCompressionLevel($compressionLevel); |
1389
|
16 |
|
} |
1390
|
|
|
|
1391
|
16 |
|
return $this; |
1392
|
|
|
} |
1393
|
16 |
|
|
1394
|
14 |
|
/** |
1395
|
|
|
* @param string $entryName |
1396
|
|
|
* @param int $compressionLevel |
1397
|
6 |
|
* |
1398
|
|
|
* @throws ZipException |
1399
|
|
|
* |
1400
|
|
|
* @return ZipFile |
1401
|
|
|
* |
1402
|
|
|
* @see ZipCompressionLevel::NORMAL |
1403
|
|
|
* @see ZipCompressionLevel::SUPER_FAST |
1404
|
|
|
* @see ZipCompressionLevel::FAST |
1405
|
|
|
* @see ZipCompressionLevel::MAXIMUM |
1406
|
|
|
*/ |
1407
|
|
|
public function setCompressionLevelEntry($entryName, $compressionLevel) |
1408
|
|
|
{ |
1409
|
|
|
$compressionLevel = (int) $compressionLevel; |
1410
|
|
|
$this->getEntry($entryName)->setCompressionLevel($compressionLevel); |
1411
|
|
|
|
1412
|
|
|
return $this; |
1413
|
10 |
|
} |
1414
|
|
|
|
1415
|
10 |
|
/** |
1416
|
10 |
|
* @param string $entryName |
1417
|
|
|
* @param int $compressionMethod Compression method. |
1418
|
8 |
|
* Use {@see ZipCompressionMethod::STORED}, {@see ZipCompressionMethod::DEFLATED} |
1419
|
|
|
* or |
1420
|
|
|
* {@see ZipCompressionMethod::BZIP2}. If null, then auto choosing method. |
1421
|
|
|
* |
1422
|
|
|
* @throws ZipException |
1423
|
|
|
* |
1424
|
|
|
* @return ZipFile |
1425
|
|
|
* |
1426
|
|
|
* @see ZipCompressionMethod::STORED |
1427
|
|
|
* @see ZipCompressionMethod::DEFLATED |
1428
|
|
|
* @see ZipCompressionMethod::BZIP2 |
1429
|
|
|
*/ |
1430
|
|
|
public function setCompressionMethodEntry($entryName, $compressionMethod) |
1431
|
|
|
{ |
1432
|
|
|
$this->zipContainer |
1433
|
|
|
->getEntry($entryName) |
1434
|
|
|
->setCompressionMethod($compressionMethod) |
1435
|
|
|
; |
1436
|
6 |
|
|
1437
|
|
|
return $this; |
1438
|
6 |
|
} |
1439
|
6 |
|
|
1440
|
6 |
|
/** |
1441
|
|
|
* zipalign is optimization to Android application (APK) files. |
1442
|
|
|
* |
1443
|
4 |
|
* @param int|null $align |
1444
|
|
|
* |
1445
|
|
|
* @return ZipFile |
1446
|
|
|
* |
1447
|
|
|
* @see https://developer.android.com/studio/command-line/zipalign.html |
1448
|
|
|
*/ |
1449
|
|
|
public function setZipAlign($align = null) |
1450
|
|
|
{ |
1451
|
|
|
$this->zipContainer->setZipAlign($align); |
1452
|
|
|
|
1453
|
|
|
return $this; |
1454
|
|
|
} |
1455
|
4 |
|
|
1456
|
|
|
/** |
1457
|
4 |
|
* Set password to all input encrypted entries. |
1458
|
|
|
* |
1459
|
4 |
|
* @param string $password Password |
1460
|
|
|
* |
1461
|
|
|
* @return ZipFile |
1462
|
|
|
*/ |
1463
|
|
|
public function setReadPassword($password) |
1464
|
|
|
{ |
1465
|
|
|
$this->zipContainer->setReadPassword($password); |
1466
|
|
|
|
1467
|
|
|
return $this; |
1468
|
|
|
} |
1469
|
9 |
|
|
1470
|
|
|
/** |
1471
|
9 |
|
* Set password to concrete input entry. |
1472
|
|
|
* |
1473
|
9 |
|
* @param string $entryName |
1474
|
|
|
* @param string $password Password |
1475
|
|
|
* |
1476
|
|
|
* @throws ZipException |
1477
|
|
|
* |
1478
|
|
|
* @return ZipFile |
1479
|
|
|
*/ |
1480
|
|
|
public function setReadPasswordEntry($entryName, $password) |
1481
|
|
|
{ |
1482
|
|
|
$this->zipContainer->setReadPasswordEntry($entryName, $password); |
1483
|
|
|
|
1484
|
|
|
return $this; |
1485
|
|
|
} |
1486
|
2 |
|
|
1487
|
|
|
/** |
1488
|
2 |
|
* Sets a new password for all files in the archive. |
1489
|
|
|
* |
1490
|
2 |
|
* @param string $password Password |
1491
|
|
|
* @param int|null $encryptionMethod Encryption method |
1492
|
|
|
* |
1493
|
|
|
* @return ZipFile |
1494
|
|
|
*/ |
1495
|
|
|
public function setPassword($password, $encryptionMethod = ZipEncryptionMethod::WINZIP_AES_256) |
1496
|
|
|
{ |
1497
|
|
|
$this->zipContainer->setWritePassword($password); |
1498
|
|
|
|
1499
|
|
|
if ($encryptionMethod !== null) { |
1500
|
|
|
$this->zipContainer->setEncryptionMethod($encryptionMethod); |
1501
|
11 |
|
} |
1502
|
|
|
|
1503
|
11 |
|
return $this; |
1504
|
|
|
} |
1505
|
11 |
|
|
1506
|
11 |
|
/** |
1507
|
|
|
* Sets a new password of an entry defined by its name. |
1508
|
|
|
* |
1509
|
10 |
|
* @param string $entryName |
1510
|
|
|
* @param string $password |
1511
|
|
|
* @param int|null $encryptionMethod |
1512
|
|
|
* |
1513
|
|
|
* @throws ZipException |
1514
|
|
|
* |
1515
|
|
|
* @return ZipFile |
1516
|
|
|
*/ |
1517
|
|
|
public function setPasswordEntry($entryName, $password, $encryptionMethod = null) |
1518
|
|
|
{ |
1519
|
|
|
$this->getEntry($entryName)->setPassword($password, $encryptionMethod); |
1520
|
|
|
|
1521
|
|
|
return $this; |
1522
|
|
|
} |
1523
|
6 |
|
|
1524
|
|
|
/** |
1525
|
6 |
|
* Disable encryption for all entries that are already in the archive. |
1526
|
|
|
* |
1527
|
5 |
|
* @return ZipFile |
1528
|
|
|
*/ |
1529
|
|
|
public function disableEncryption() |
1530
|
|
|
{ |
1531
|
|
|
$this->zipContainer->removePassword(); |
1532
|
|
|
|
1533
|
|
|
return $this; |
1534
|
|
|
} |
1535
|
2 |
|
|
1536
|
|
|
/** |
1537
|
2 |
|
* Disable encryption of an entry defined by its name. |
1538
|
|
|
* |
1539
|
2 |
|
* @param string $entryName |
1540
|
|
|
* |
1541
|
|
|
* @return ZipFile |
1542
|
|
|
*/ |
1543
|
|
|
public function disableEncryptionEntry($entryName) |
1544
|
|
|
{ |
1545
|
|
|
$this->zipContainer->removePasswordEntry($entryName); |
1546
|
|
|
|
1547
|
|
|
return $this; |
1548
|
|
|
} |
1549
|
1 |
|
|
1550
|
|
|
/** |
1551
|
1 |
|
* Undo all changes done in the archive. |
1552
|
|
|
* |
1553
|
1 |
|
* @return ZipFile |
1554
|
|
|
*/ |
1555
|
|
|
public function unchangeAll() |
1556
|
|
|
{ |
1557
|
|
|
$this->zipContainer->unchangeAll(); |
1558
|
|
|
|
1559
|
|
|
return $this; |
1560
|
|
|
} |
1561
|
2 |
|
|
1562
|
|
|
/** |
1563
|
2 |
|
* Undo change archive comment. |
1564
|
|
|
* |
1565
|
2 |
|
* @return ZipFile |
1566
|
|
|
*/ |
1567
|
|
|
public function unchangeArchiveComment() |
1568
|
|
|
{ |
1569
|
|
|
$this->zipContainer->unchangeArchiveComment(); |
1570
|
|
|
|
1571
|
|
|
return $this; |
1572
|
|
|
} |
1573
|
2 |
|
|
1574
|
|
|
/** |
1575
|
2 |
|
* Revert all changes done to an entry with the given name. |
1576
|
|
|
* |
1577
|
2 |
|
* @param string|ZipEntry $entry Entry name or ZipEntry |
1578
|
|
|
* |
1579
|
|
|
* @return ZipFile |
1580
|
|
|
*/ |
1581
|
|
|
public function unchangeEntry($entry) |
1582
|
|
|
{ |
1583
|
|
|
$this->zipContainer->unchangeEntry($entry); |
1584
|
|
|
|
1585
|
|
|
return $this; |
1586
|
|
|
} |
1587
|
2 |
|
|
1588
|
|
|
/** |
1589
|
2 |
|
* Save as file. |
1590
|
|
|
* |
1591
|
2 |
|
* @param string $filename Output filename |
1592
|
|
|
* |
1593
|
|
|
* @throws ZipException |
1594
|
|
|
* |
1595
|
|
|
* @return ZipFile |
1596
|
|
|
*/ |
1597
|
|
|
public function saveAsFile($filename) |
1598
|
|
|
{ |
1599
|
|
|
$filename = (string) $filename; |
1600
|
|
|
|
1601
|
|
|
$tempFilename = $filename . '.temp' . uniqid('', false); |
1602
|
|
|
|
1603
|
128 |
|
if (!($handle = @fopen($tempFilename, 'w+b'))) { |
1604
|
|
|
throw new InvalidArgumentException(sprintf('Cannot open "%s" for writing.', $tempFilename)); |
1605
|
128 |
|
} |
1606
|
|
|
$this->saveAsStream($handle); |
1607
|
128 |
|
|
1608
|
|
|
$reopen = false; |
1609
|
128 |
|
|
1610
|
2 |
|
if ($this->reader !== null) { |
1611
|
|
|
$meta = $this->reader->getStreamMetaData(); |
1612
|
126 |
|
|
1613
|
|
|
if ($meta['wrapper_type'] === 'plainfile' && isset($meta['uri'])) { |
1614
|
126 |
|
$readFilePath = realpath($meta['uri']); |
1615
|
|
|
$writeFilePath = realpath($filename); |
1616
|
|
|
|
1617
|
|
|
if ($readFilePath !== false && $writeFilePath !== false && $readFilePath === $writeFilePath) { |
1618
|
|
|
$this->reader->close(); |
1619
|
|
|
$reopen = true; |
1620
|
|
|
} |
1621
|
|
|
} |
1622
|
126 |
|
} |
1623
|
|
|
|
1624
|
|
|
if (!@rename($tempFilename, $filename)) { |
1625
|
|
|
if (is_file($tempFilename)) { |
1626
|
|
|
unlink($tempFilename); |
1627
|
|
|
} |
1628
|
|
|
|
1629
|
|
|
throw new ZipException(sprintf('Cannot move %s to %s', $tempFilename, $filename)); |
1630
|
|
|
} |
1631
|
|
|
|
1632
|
|
|
if ($reopen) { |
1633
|
|
|
return $this->openFile($filename); |
1634
|
128 |
|
} |
1635
|
|
|
|
1636
|
128 |
|
return $this; |
1637
|
2 |
|
} |
1638
|
|
|
|
1639
|
126 |
|
/** |
1640
|
126 |
|
* Save as stream. |
1641
|
126 |
|
* |
1642
|
|
|
* @param resource $handle Output stream resource |
1643
|
126 |
|
* |
1644
|
|
|
* @throws ZipException |
1645
|
|
|
* |
1646
|
|
|
* @return ZipFile |
1647
|
|
|
*/ |
1648
|
|
|
public function saveAsStream($handle) |
1649
|
|
|
{ |
1650
|
|
|
if (!\is_resource($handle)) { |
1651
|
|
|
throw new InvalidArgumentException('handle is not resource'); |
1652
|
|
|
} |
1653
|
|
|
ftruncate($handle, 0); |
1654
|
|
|
$this->writeZipToStream($handle); |
1655
|
|
|
fclose($handle); |
1656
|
6 |
|
|
1657
|
|
|
return $this; |
1658
|
6 |
|
} |
1659
|
|
|
|
1660
|
6 |
|
/** |
1661
|
4 |
|
* Output .ZIP archive as attachment. |
1662
|
|
|
* Die after output. |
1663
|
|
|
* |
1664
|
6 |
|
* @param string $outputFilename Output filename |
1665
|
|
|
* @param string|null $mimeType Mime-Type |
1666
|
|
|
* @param bool $attachment Http Header 'Content-Disposition' if true then attachment otherwise inline |
1667
|
6 |
|
* |
1668
|
6 |
|
* @throws ZipException |
1669
|
|
|
*/ |
1670
|
6 |
|
public function outputAsAttachment($outputFilename, $mimeType = null, $attachment = true) |
1671
|
|
|
{ |
1672
|
6 |
|
$outputFilename = (string) $outputFilename; |
1673
|
|
|
|
1674
|
6 |
|
if ($mimeType === null) { |
1675
|
6 |
|
$mimeType = $this->getMimeTypeByFilename($outputFilename); |
1676
|
|
|
} |
1677
|
|
|
|
1678
|
6 |
|
if (!($handle = fopen('php://temp', 'w+b'))) { |
1679
|
6 |
|
throw new InvalidArgumentException('php://temp cannot open for write.'); |
1680
|
6 |
|
} |
1681
|
|
|
$this->writeZipToStream($handle); |
1682
|
6 |
|
$this->close(); |
1683
|
|
|
|
1684
|
|
|
$size = fstat($handle)['size']; |
1685
|
6 |
|
|
1686
|
6 |
|
$headerContentDisposition = 'Content-Disposition: ' . ($attachment ? 'attachment' : 'inline'); |
1687
|
6 |
|
|
1688
|
|
|
if (!empty($outputFilename)) { |
1689
|
6 |
|
$headerContentDisposition .= '; filename="' . basename($outputFilename) . '"'; |
1690
|
|
|
} |
1691
|
|
|
|
1692
|
|
|
header($headerContentDisposition); |
1693
|
|
|
header('Content-Type: ' . $mimeType); |
1694
|
|
|
header('Content-Length: ' . $size); |
1695
|
|
|
|
1696
|
6 |
|
rewind($handle); |
1697
|
|
|
|
1698
|
6 |
|
try { |
1699
|
6 |
|
echo stream_get_contents($handle, -1, 0); |
1700
|
|
|
} finally { |
1701
|
6 |
|
fclose($handle); |
1702
|
6 |
|
} |
1703
|
|
|
} |
1704
|
|
|
|
1705
|
|
|
/** |
1706
|
|
|
* @param string $outputFilename |
1707
|
|
|
* |
1708
|
|
|
* @return string |
1709
|
|
|
*/ |
1710
|
|
|
protected function getMimeTypeByFilename($outputFilename) |
1711
|
|
|
{ |
1712
|
|
|
$outputFilename = (string) $outputFilename; |
1713
|
|
|
$ext = strtolower(pathinfo($outputFilename, \PATHINFO_EXTENSION)); |
1714
|
|
|
|
1715
|
|
|
if (!empty($ext) && isset(self::$defaultMimeTypes[$ext])) { |
1716
|
|
|
return self::$defaultMimeTypes[$ext]; |
1717
|
|
|
} |
1718
|
|
|
|
1719
|
|
|
return self::$defaultMimeTypes['zip']; |
1720
|
2 |
|
} |
1721
|
|
|
|
1722
|
2 |
|
/** |
1723
|
|
|
* Output .ZIP archive as PSR-7 Response. |
1724
|
2 |
|
* |
1725
|
2 |
|
* @param ResponseInterface $response Instance PSR-7 Response |
1726
|
|
|
* @param string $outputFilename Output filename |
1727
|
|
|
* @param string|null $mimeType Mime-Type |
1728
|
2 |
|
* @param bool $attachment Http Header 'Content-Disposition' if true then attachment otherwise inline |
1729
|
|
|
* |
1730
|
|
|
* @throws ZipException |
1731
|
2 |
|
* |
1732
|
2 |
|
* @return ResponseInterface |
1733
|
2 |
|
*/ |
1734
|
|
|
public function outputAsResponse(ResponseInterface $response, $outputFilename, $mimeType = null, $attachment = true) |
1735
|
2 |
|
{ |
1736
|
|
|
$outputFilename = (string) $outputFilename; |
1737
|
2 |
|
|
1738
|
2 |
|
if ($mimeType === null) { |
1739
|
|
|
$mimeType = $this->getMimeTypeByFilename($outputFilename); |
1740
|
|
|
} |
1741
|
2 |
|
|
1742
|
2 |
|
if (!($handle = fopen('php://temp', 'w+b'))) { |
1743
|
|
|
throw new InvalidArgumentException('php://temp cannot open for write.'); |
1744
|
2 |
|
} |
1745
|
|
|
$this->writeZipToStream($handle); |
1746
|
2 |
|
$this->close(); |
1747
|
|
|
rewind($handle); |
1748
|
|
|
|
1749
|
|
|
$contentDispositionValue = ($attachment ? 'attachment' : 'inline'); |
1750
|
2 |
|
|
1751
|
2 |
|
if (!empty($outputFilename)) { |
1752
|
2 |
|
$contentDispositionValue .= '; filename="' . basename($outputFilename) . '"'; |
1753
|
|
|
} |
1754
|
|
|
|
1755
|
|
|
$stream = new ResponseStream($handle); |
1756
|
|
|
$size = $stream->getSize(); |
1757
|
|
|
|
1758
|
|
|
if ($size !== null) { |
1759
|
|
|
/** @noinspection CallableParameterUseCaseInTypeContextInspection */ |
1760
|
|
|
$response = $response->withHeader('Content-Length', (string) $size); |
1761
|
136 |
|
} |
1762
|
|
|
|
1763
|
136 |
|
return $response |
1764
|
|
|
->withHeader('Content-Type', $mimeType) |
1765
|
136 |
|
->withHeader('Content-Disposition', $contentDispositionValue) |
1766
|
136 |
|
->withBody($stream) |
1767
|
|
|
; |
1768
|
|
|
} |
1769
|
|
|
|
1770
|
|
|
/** |
1771
|
|
|
* @param resource $handle |
1772
|
|
|
* |
1773
|
|
|
* @throws ZipException |
1774
|
|
|
*/ |
1775
|
2 |
|
protected function writeZipToStream($handle) |
1776
|
|
|
{ |
1777
|
2 |
|
$this->onBeforeSave(); |
1778
|
|
|
|
1779
|
|
|
$this->createZipWriter()->write($handle); |
1780
|
2 |
|
} |
1781
|
2 |
|
|
1782
|
|
|
/** |
1783
|
|
|
* Returns the zip archive as a string. |
1784
|
2 |
|
* |
1785
|
|
|
* @throws ZipException |
1786
|
2 |
|
* |
1787
|
|
|
* @return string |
1788
|
|
|
*/ |
1789
|
|
|
public function outputAsString() |
1790
|
|
|
{ |
1791
|
|
|
if (!($handle = fopen('php://temp', 'w+b'))) { |
1792
|
|
|
throw new InvalidArgumentException('php://temp cannot open for write.'); |
1793
|
135 |
|
} |
1794
|
|
|
$this->writeZipToStream($handle); |
1795
|
135 |
|
rewind($handle); |
1796
|
|
|
|
1797
|
|
|
try { |
1798
|
|
|
return stream_get_contents($handle); |
1799
|
|
|
} finally { |
1800
|
321 |
|
fclose($handle); |
1801
|
|
|
} |
1802
|
321 |
|
} |
1803
|
147 |
|
|
1804
|
147 |
|
/** |
1805
|
|
|
* Event before save or output. |
1806
|
321 |
|
*/ |
1807
|
321 |
|
protected function onBeforeSave() |
1808
|
321 |
|
{ |
1809
|
|
|
} |
1810
|
|
|
|
1811
|
|
|
/** |
1812
|
|
|
* Close zip archive and release input stream. |
1813
|
|
|
*/ |
1814
|
|
|
public function close() |
1815
|
|
|
{ |
1816
|
|
|
if ($this->reader !== null) { |
1817
|
11 |
|
$this->reader->close(); |
1818
|
|
|
$this->reader = null; |
1819
|
11 |
|
} |
1820
|
2 |
|
$this->zipContainer = $this->createZipContainer(null); |
1821
|
|
|
gc_collect_cycles(); |
1822
|
|
|
} |
1823
|
9 |
|
|
1824
|
|
|
/** |
1825
|
9 |
|
* Save and reopen zip archive. |
1826
|
7 |
|
* |
1827
|
7 |
|
* @throws ZipException |
1828
|
|
|
* |
1829
|
7 |
|
* @return ZipFile |
1830
|
7 |
|
*/ |
1831
|
|
|
public function rewrite() |
1832
|
|
|
{ |
1833
|
2 |
|
if ($this->reader === null) { |
1834
|
|
|
throw new ZipException('input stream is null'); |
1835
|
2 |
|
} |
1836
|
|
|
|
1837
|
|
|
$meta = $this->reader->getStreamMetaData(); |
1838
|
2 |
|
|
1839
|
2 |
|
if ($meta['wrapper_type'] !== 'plainfile' || !isset($meta['uri'])) { |
1840
|
|
|
throw new ZipException('Overwrite is only supported for open local files.'); |
1841
|
|
|
} |
1842
|
9 |
|
|
1843
|
|
|
return $this->saveAsFile($meta['uri']); |
1844
|
|
|
} |
1845
|
|
|
|
1846
|
|
|
/** |
1847
|
|
|
* Release all resources. |
1848
|
313 |
|
*/ |
1849
|
|
|
public function __destruct() |
1850
|
313 |
|
{ |
1851
|
313 |
|
$this->close(); |
1852
|
|
|
} |
1853
|
|
|
|
1854
|
|
|
/** |
1855
|
|
|
* Offset to set. |
1856
|
|
|
* |
1857
|
|
|
* @see http://php.net/manual/en/arrayaccess.offsetset.php |
1858
|
|
|
* |
1859
|
|
|
* @param string $entryName the offset to assign the value to |
1860
|
|
|
* @param string|\DirectoryIterator|\SplFileInfo|resource $contents the value to set |
1861
|
|
|
* |
1862
|
|
|
* @throws ZipException |
1863
|
|
|
* |
1864
|
|
|
* @see ZipFile::addFromString |
1865
|
|
|
* @see ZipFile::addEmptyDir |
1866
|
|
|
* @see ZipFile::addFile |
1867
|
|
|
* @see ZipFile::addFilesFromIterator |
1868
|
74 |
|
*/ |
1869
|
|
|
public function offsetSet($entryName, $contents) |
1870
|
74 |
|
{ |
1871
|
2 |
|
if ($entryName === null) { |
|
|
|
|
1872
|
|
|
throw new InvalidArgumentException('Key must not be null, but must contain the name of the zip entry.'); |
1873
|
72 |
|
} |
1874
|
|
|
$entryName = ltrim((string) $entryName, '\\/'); |
1875
|
72 |
|
|
1876
|
2 |
|
if ($entryName === '') { |
1877
|
|
|
throw new InvalidArgumentException('Key is empty, but must contain the name of the zip entry.'); |
1878
|
|
|
} |
1879
|
70 |
|
|
1880
|
1 |
|
if ($contents instanceof \DirectoryIterator) { |
1881
|
69 |
|
$this->addFilesFromIterator($contents, $entryName); |
1882
|
2 |
|
} elseif ($contents instanceof \SplFileInfo) { |
1883
|
69 |
|
$this->addSplFile($contents, $entryName); |
1884
|
6 |
|
} elseif (StringUtil::endsWith($entryName, '/')) { |
1885
|
69 |
|
$this->addEmptyDir($entryName); |
1886
|
2 |
|
} elseif (\is_resource($contents)) { |
1887
|
|
|
$this->addFromStream($contents, $entryName); |
1888
|
67 |
|
} else { |
1889
|
|
|
$this->addFromString($entryName, (string) $contents); |
1890
|
70 |
|
} |
1891
|
|
|
} |
1892
|
|
|
|
1893
|
|
|
/** |
1894
|
|
|
* Offset to unset. |
1895
|
|
|
* |
1896
|
|
|
* @see http://php.net/manual/en/arrayaccess.offsetunset.php |
1897
|
|
|
* |
1898
|
|
|
* @param string $entryName the offset to unset |
1899
|
|
|
* |
1900
|
|
|
* @throws ZipEntryNotFoundException |
1901
|
3 |
|
*/ |
1902
|
|
|
public function offsetUnset($entryName) |
1903
|
3 |
|
{ |
1904
|
3 |
|
$this->deleteFromName($entryName); |
1905
|
|
|
} |
1906
|
|
|
|
1907
|
|
|
/** |
1908
|
|
|
* Return the current element. |
1909
|
|
|
* |
1910
|
|
|
* @see http://php.net/manual/en/iterator.current.php |
1911
|
|
|
* |
1912
|
|
|
* @throws ZipException |
1913
|
|
|
* |
1914
|
|
|
* @return mixed can return any type |
1915
|
|
|
* |
1916
|
|
|
* @since 5.0.0 |
1917
|
7 |
|
*/ |
1918
|
|
|
public function current() |
1919
|
7 |
|
{ |
1920
|
|
|
return $this->offsetGet($this->key()); |
1921
|
|
|
} |
1922
|
|
|
|
1923
|
|
|
/** |
1924
|
|
|
* Offset to retrieve. |
1925
|
|
|
* |
1926
|
|
|
* @see http://php.net/manual/en/arrayaccess.offsetget.php |
1927
|
|
|
* |
1928
|
|
|
* @param string $entryName the offset to retrieve |
1929
|
|
|
* |
1930
|
|
|
* @throws ZipException |
1931
|
|
|
* |
1932
|
|
|
* @return string|null |
1933
|
58 |
|
*/ |
1934
|
|
|
public function offsetGet($entryName) |
1935
|
58 |
|
{ |
1936
|
|
|
return $this->getEntryContents($entryName); |
1937
|
|
|
} |
1938
|
|
|
|
1939
|
|
|
/** |
1940
|
|
|
* Return the key of the current element. |
1941
|
|
|
* |
1942
|
|
|
* @see http://php.net/manual/en/iterator.key.php |
1943
|
|
|
* |
1944
|
|
|
* @return mixed scalar on success, or null on failure |
1945
|
|
|
* |
1946
|
|
|
* @since 5.0.0 |
1947
|
7 |
|
*/ |
1948
|
|
|
public function key() |
1949
|
7 |
|
{ |
1950
|
|
|
return key($this->zipContainer->getEntries()); |
1951
|
|
|
} |
1952
|
|
|
|
1953
|
|
|
/** |
1954
|
|
|
* Move forward to next element. |
1955
|
|
|
* |
1956
|
|
|
* @see http://php.net/manual/en/iterator.next.php |
1957
|
|
|
* @since 5.0.0 |
1958
|
7 |
|
*/ |
1959
|
|
|
public function next() |
1960
|
7 |
|
{ |
1961
|
7 |
|
next($this->zipContainer->getEntries()); |
1962
|
|
|
} |
1963
|
|
|
|
1964
|
|
|
/** |
1965
|
|
|
* Checks if current position is valid. |
1966
|
|
|
* |
1967
|
|
|
* @see http://php.net/manual/en/iterator.valid.php |
1968
|
|
|
* |
1969
|
|
|
* @return bool The return value will be casted to boolean and then evaluated. |
1970
|
|
|
* Returns true on success or false on failure. |
1971
|
|
|
* |
1972
|
|
|
* @since 5.0.0 |
1973
|
7 |
|
*/ |
1974
|
|
|
public function valid() |
1975
|
7 |
|
{ |
1976
|
|
|
return $this->offsetExists($this->key()); |
1977
|
|
|
} |
1978
|
|
|
|
1979
|
|
|
/** |
1980
|
|
|
* Whether a offset exists. |
1981
|
|
|
* |
1982
|
|
|
* @see http://php.net/manual/en/arrayaccess.offsetexists.php |
1983
|
|
|
* |
1984
|
|
|
* @param string $entryName an offset to check for |
1985
|
|
|
* |
1986
|
|
|
* @return bool true on success or false on failure. |
1987
|
|
|
* The return value will be casted to boolean if non-boolean was returned. |
1988
|
56 |
|
*/ |
1989
|
|
|
public function offsetExists($entryName) |
1990
|
56 |
|
{ |
1991
|
|
|
return $this->hasEntry($entryName); |
1992
|
|
|
} |
1993
|
|
|
|
1994
|
|
|
/** |
1995
|
|
|
* Rewind the Iterator to the first element. |
1996
|
|
|
* |
1997
|
|
|
* @see http://php.net/manual/en/iterator.rewind.php |
1998
|
|
|
* @since 5.0.0 |
1999
|
7 |
|
*/ |
2000
|
|
|
public function rewind() |
2001
|
7 |
|
{ |
2002
|
7 |
|
reset($this->zipContainer->getEntries()); |
2003
|
|
|
} |
2004
|
|
|
} |
2005
|
|
|
|
This interface has been deprecated. The supplier of the interface has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the interface will be removed and what other interface to use instead.