1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace wapmorgan\UnifiedArchive; |
4
|
|
|
|
5
|
|
|
use RecursiveIteratorIterator; |
6
|
|
|
|
7
|
|
|
if (!defined('PCLZIP_ERR_NO_ERROR')) { |
8
|
|
|
// ----- Constants |
9
|
|
|
if (!defined('PCLZIP_READ_BLOCK_SIZE')) { |
10
|
|
|
define('PCLZIP_READ_BLOCK_SIZE', 2048); |
11
|
|
|
} |
12
|
|
|
if (!defined('PCLZIP_SEPARATOR')) { |
13
|
|
|
define('PCLZIP_SEPARATOR', ','); |
14
|
|
|
} |
15
|
|
|
if (!defined('PCLZIP_ERROR_EXTERNAL')) { |
16
|
|
|
define('PCLZIP_ERROR_EXTERNAL', 0); |
17
|
|
|
} |
18
|
|
|
if (!defined('PCLZIP_TEMPORARY_DIR')) { |
19
|
|
|
define('PCLZIP_TEMPORARY_DIR', sys_get_temp_dir()); |
20
|
|
|
} |
21
|
|
|
|
22
|
|
|
define('PCLZIP_ERR_USER_ABORTED', 2); |
23
|
|
|
define('PCLZIP_ERR_NO_ERROR', 0); |
24
|
|
|
define('PCLZIP_ERR_WRITE_OPEN_FAIL', -1); |
25
|
|
|
define('PCLZIP_ERR_READ_OPEN_FAIL', -2); |
26
|
|
|
define('PCLZIP_ERR_INVALID_PARAMETER', -3); |
27
|
|
|
define('PCLZIP_ERR_MISSING_FILE', -4); |
28
|
|
|
define('PCLZIP_ERR_FILENAME_TOO_LONG', -5); |
29
|
|
|
define('PCLZIP_ERR_INVALID_ZIP', -6); |
30
|
|
|
define('PCLZIP_ERR_BAD_EXTRACTED_FILE', -7); |
31
|
|
|
define('PCLZIP_ERR_DIR_CREATE_FAIL', -8); |
32
|
|
|
define('PCLZIP_ERR_BAD_EXTENSION', -9); |
33
|
|
|
define('PCLZIP_ERR_BAD_FORMAT', -10); |
34
|
|
|
define('PCLZIP_ERR_DELETE_FILE_FAIL', -11); |
35
|
|
|
define('PCLZIP_ERR_RENAME_FILE_FAIL', -12); |
36
|
|
|
define('PCLZIP_ERR_BAD_CHECKSUM', -13); |
37
|
|
|
define('PCLZIP_ERR_INVALID_ARCHIVE_ZIP', -14); |
38
|
|
|
define('PCLZIP_ERR_MISSING_OPTION_VALUE', -15); |
39
|
|
|
define('PCLZIP_ERR_INVALID_OPTION_VALUE', -16); |
40
|
|
|
define('PCLZIP_ERR_ALREADY_A_DIRECTORY', -17); |
41
|
|
|
define('PCLZIP_ERR_UNSUPPORTED_COMPRESSION', -18); |
42
|
|
|
define('PCLZIP_ERR_UNSUPPORTED_ENCRYPTION', -19); |
43
|
|
|
define('PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE', -20); |
44
|
|
|
define('PCLZIP_ERR_DIRECTORY_RESTRICTION', -21); |
45
|
|
|
|
46
|
|
|
// ----- Options values |
47
|
|
|
define('PCLZIP_OPT_PATH', 77001); |
48
|
|
|
define('PCLZIP_OPT_ADD_PATH', 77002); |
49
|
|
|
define('PCLZIP_OPT_REMOVE_PATH', 77003); |
50
|
|
|
define('PCLZIP_OPT_REMOVE_ALL_PATH', 77004); |
51
|
|
|
define('PCLZIP_OPT_SET_CHMOD', 77005); |
52
|
|
|
define('PCLZIP_OPT_EXTRACT_AS_STRING', 77006); |
53
|
|
|
define('PCLZIP_OPT_NO_COMPRESSION', 77007); |
54
|
|
|
define('PCLZIP_OPT_BY_NAME', 77008); |
55
|
|
|
define('PCLZIP_OPT_BY_INDEX', 77009); |
56
|
|
|
define('PCLZIP_OPT_BY_EREG', 77010); |
57
|
|
|
define('PCLZIP_OPT_BY_PREG', 77011); |
58
|
|
|
define('PCLZIP_OPT_COMMENT', 77012); |
59
|
|
|
define('PCLZIP_OPT_ADD_COMMENT', 77013); |
60
|
|
|
define('PCLZIP_OPT_PREPEND_COMMENT', 77014); |
61
|
|
|
define('PCLZIP_OPT_EXTRACT_IN_OUTPUT', 77015); |
62
|
|
|
define('PCLZIP_OPT_REPLACE_NEWER', 77016); |
63
|
|
|
define('PCLZIP_OPT_STOP_ON_ERROR', 77017); |
64
|
|
|
// Having big trouble with crypt. Need to multiply 2 long int |
65
|
|
|
// which is not correctly supported by PHP ... |
66
|
|
|
//define( 'PCLZIP_OPT_CRYPT', 77018 ); |
67
|
|
|
define('PCLZIP_OPT_EXTRACT_DIR_RESTRICTION', 77019); |
68
|
|
|
define('PCLZIP_OPT_TEMP_FILE_THRESHOLD', 77020); |
69
|
|
|
define('PCLZIP_OPT_ADD_TEMP_FILE_THRESHOLD', 77020); // alias |
70
|
|
|
define('PCLZIP_OPT_TEMP_FILE_ON', 77021); |
71
|
|
|
define('PCLZIP_OPT_ADD_TEMP_FILE_ON', 77021); // alias |
72
|
|
|
define('PCLZIP_OPT_TEMP_FILE_OFF', 77022); |
73
|
|
|
define('PCLZIP_OPT_ADD_TEMP_FILE_OFF', 77022); // alias |
74
|
|
|
|
75
|
|
|
// ----- File description attributes |
76
|
|
|
define('PCLZIP_ATT_FILE_NAME', 79001); |
77
|
|
|
define('PCLZIP_ATT_FILE_NEW_SHORT_NAME', 79002); |
78
|
|
|
define('PCLZIP_ATT_FILE_NEW_FULL_NAME', 79003); |
79
|
|
|
define('PCLZIP_ATT_FILE_MTIME', 79004); |
80
|
|
|
define('PCLZIP_ATT_FILE_CONTENT', 79005); |
81
|
|
|
define('PCLZIP_ATT_FILE_COMMENT', 79006); |
82
|
|
|
|
83
|
|
|
// ----- Call backs values |
84
|
|
|
define('PCLZIP_CB_PRE_EXTRACT', 78001); |
85
|
|
|
define('PCLZIP_CB_POST_EXTRACT', 78002); |
86
|
|
|
define('PCLZIP_CB_PRE_ADD', 78003); |
87
|
|
|
define('PCLZIP_CB_POST_ADD', 78004); |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
/** |
91
|
|
|
* @link https://web.archive.org/web/20190228165954/http://www.phpconcept.net/pclzip/user-guide/18 |
92
|
|
|
* @link https://web.archive.org/web/20190216075605/http://www.phpconcept.net/pclzip/user-guide/5 |
93
|
|
|
*/ |
94
|
|
|
class PclZipInterface |
95
|
|
|
{ |
96
|
|
|
const SELECT_FILTER_PASS = 1; |
97
|
|
|
const SELECT_FILTER_REFUSE = 0; |
98
|
|
|
|
99
|
|
|
const AVERAGE_ZIP_COMPRESSION_RATIO = 2; |
100
|
|
|
|
101
|
|
|
/** |
102
|
|
|
* @var UnifiedArchive |
103
|
|
|
*/ |
104
|
|
|
private $archive; |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* PclzipZipInterface constructor. |
108
|
|
|
* |
109
|
|
|
* @param UnifiedArchive $archive |
110
|
|
|
*/ |
111
|
|
|
public function __construct(UnifiedArchive $archive) |
112
|
|
|
{ |
113
|
|
|
$this->archive = $archive; |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
/** |
117
|
|
|
* @param $localname |
118
|
|
|
* @param $filename |
119
|
|
|
* |
120
|
|
|
* @return object |
121
|
|
|
*/ |
122
|
|
|
public function createFileHeader($localname, $filename) |
123
|
|
|
{ |
124
|
|
|
return (object) array( |
125
|
|
|
'filename' => $filename, |
126
|
|
|
'stored_filename' => $localname, |
127
|
|
|
'size' => filesize($filename), |
128
|
|
|
'compressed_size' => ceil(filesize($filename) |
129
|
|
|
/ self::AVERAGE_ZIP_COMPRESSION_RATIO), |
130
|
|
|
'mtime' => filemtime($filename), |
131
|
|
|
'comment' => null, |
132
|
|
|
'folder' => is_dir($filename), |
133
|
|
|
'status' => 'ok', |
134
|
|
|
); |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
/** |
138
|
|
|
* Creates a new archive |
139
|
|
|
* Two ways of usage: |
140
|
|
|
* <code>create($content, [$addDir, [$removeDir]])</code> |
141
|
|
|
* <code>create($content, [... options ...]])</code> |
142
|
|
|
*/ |
143
|
|
|
public function create($content) |
144
|
|
|
{ |
145
|
|
|
if (is_array($content)) $paths_list = $content; |
146
|
|
|
else $paths_list = explode(',', $content); |
147
|
|
|
|
148
|
|
|
$options = $this->makeOptionsFromArguments(func_get_args()); |
149
|
|
|
|
150
|
|
|
// filters initiation |
151
|
|
|
$filters = $this->createFilters($options); |
152
|
|
|
list($preAddCallback, $postAddCallback) = $this->extractCallbacks($options, PCLZIP_CB_PRE_ADD, PCLZIP_CB_POST_ADD); |
153
|
|
|
|
154
|
|
|
if (!empty($comment = $this->buildComment($options, null))) |
155
|
|
|
$this->archive->setComment($comment); |
156
|
|
|
|
157
|
|
|
// scan filesystem for files list |
158
|
|
|
return $this->addSnippets($paths_list, $filters, $preAddCallback, $postAddCallback); |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
/** |
162
|
|
|
* @param string $fileToAdd |
163
|
|
|
* @param array $filters |
164
|
|
|
* @param callable|null $preAddCallback |
165
|
|
|
* @param callable|null $postAddCallback |
166
|
|
|
* @return object |
167
|
|
|
*/ |
168
|
|
|
private function addSnippet($fileToAdd, array $filters, callable $preAddCallback, callable $postAddCallback) |
169
|
|
|
{ |
170
|
|
|
if (is_file($fileToAdd) || is_dir($fileToAdd)) { |
171
|
|
|
// apply filters to a file |
172
|
|
|
$localname = $fileToAdd; |
173
|
|
|
$filename = $fileToAdd; |
174
|
|
|
|
175
|
|
|
foreach ($filters as $filter) |
176
|
|
|
call_user_func($filter, $localname, $filename); |
177
|
|
|
|
178
|
|
|
$file_header = $this->createFileHeader($localname, $filename); |
179
|
|
|
if (call_user_func($preAddCallback, $file_header) == 1) { |
180
|
|
|
// |
181
|
|
|
// Check for max length > 255 |
182
|
|
|
// |
183
|
|
|
if (strlen(basename($file_header->stored_filename)) > 255) |
184
|
|
|
$file_header->status = 'filename_too_long'; |
185
|
|
|
if (is_file($filename)) { |
186
|
|
|
$this->archive->add([ |
187
|
|
|
$file_header->stored_filename => $file_header->filename, |
188
|
|
|
]); |
189
|
|
|
} else if (is_dir($filename)) { |
190
|
|
|
// $this->archive->addEmptyDir($file_header->stored_filename); |
191
|
|
|
} |
192
|
|
|
|
193
|
|
|
call_user_func($postAddCallback, $file_header); |
194
|
|
|
|
195
|
|
|
} else { |
196
|
|
|
// |
197
|
|
|
// File was skipped |
198
|
|
|
// |
199
|
|
|
$file_header->status = 'skipped'; |
200
|
|
|
} |
201
|
|
|
|
202
|
|
|
return $file_header; |
203
|
|
|
} |
204
|
|
|
} |
205
|
|
|
|
206
|
|
|
/** |
207
|
|
|
* Lists archive content |
208
|
|
|
* @throws Exceptions\NonExistentArchiveFileException |
209
|
|
|
*/ |
210
|
|
|
public function listContent() |
211
|
|
|
{ |
212
|
|
|
$filesList = []; |
213
|
|
|
|
214
|
|
|
foreach ($this->archive->getFiles() as $i => $fileName) { |
215
|
|
|
$fileData = $this->archive->getFileData($fileName); |
216
|
|
|
|
217
|
|
|
$filesList[] = [ |
218
|
|
|
'filename' => $fileData->path, |
219
|
|
|
'stored_filename' => $fileData->path, |
220
|
|
|
'size' => $fileData->uncompressedSize, |
221
|
|
|
'compressed_size' => $fileData->compressedSize, |
222
|
|
|
'mtime' => $fileData->modificationTime, |
223
|
|
|
'comment' => $fileData->comment, |
224
|
|
|
'folder' => false/*in_array(substr($statIndex['name'], -1), |
225
|
|
|
array('/', '\\'))*/, |
226
|
|
|
'index' => $i, |
227
|
|
|
'status' => 'ok', |
228
|
|
|
]; |
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
return $filesList; |
232
|
|
|
} |
233
|
|
|
|
234
|
|
|
/** |
235
|
|
|
* Extracts files |
236
|
|
|
* Two ways of usage: |
237
|
|
|
* <code>extract([$extractPath, [$removePath]])</code> |
238
|
|
|
* <code>extract([... options ...]])</code> |
239
|
|
|
*/ |
240
|
|
|
public function extract() |
241
|
|
|
{ |
242
|
|
|
$options = func_get_args(); |
243
|
|
|
//array_shift($options); |
244
|
|
|
|
245
|
|
|
// parse options |
246
|
|
|
if (isset($options[0]) && is_string($options[0])) { |
247
|
|
|
$options[PCLZIP_OPT_PATH] = $options[0]; |
248
|
|
|
if (isset($options[1]) && is_string($options[1])) { |
249
|
|
|
$options[PCLZIP_OPT_REMOVE_PATH] = $options[1]; |
250
|
|
|
} |
251
|
|
|
} else { |
252
|
|
|
$options = $this->makeKeyValueArrayFromList($options); |
253
|
|
|
} |
254
|
|
|
|
255
|
|
|
// filters initiation |
256
|
|
|
if (isset($options[PCLZIP_OPT_PATH])) |
257
|
|
|
$extractPath = rtrim($options[PCLZIP_OPT_PATH], '/'); |
258
|
|
|
else $extractPath = rtrim(getcwd(), '/'); |
259
|
|
|
|
260
|
|
|
$filters = $this->createFilters($options); |
261
|
|
|
list($preExtractCallback, $postExtractCallback) = $this->extractCallbacks($options, PCLZIP_CB_PRE_EXTRACT, PCLZIP_CB_POST_EXTRACT); |
262
|
|
|
$selectFilter = $this->createSelector($options); |
263
|
|
|
|
264
|
|
|
if (isset($options[PCLZIP_OPT_EXTRACT_AS_STRING])) |
265
|
|
|
$anotherOutputFormat = PCLZIP_OPT_EXTRACT_AS_STRING; |
266
|
|
|
else if (isset($options[PCLZIP_OPT_EXTRACT_IN_OUTPUT])) |
267
|
|
|
$anotherOutputFormat = PCLZIP_OPT_EXTRACT_IN_OUTPUT; |
268
|
|
|
else $anotherOutputFormat = false; |
269
|
|
|
|
270
|
|
|
if (isset($options[PCLZIP_OPT_REPLACE_NEWER])) |
271
|
|
|
$doNotReplaceNewer = false; |
272
|
|
|
else $doNotReplaceNewer = true; |
273
|
|
|
|
274
|
|
|
if (isset($options[PCLZIP_OPT_EXTRACT_DIR_RESTRICTION])) |
275
|
|
|
$restrictExtractDir = $options[PCLZIP_OPT_EXTRACT_DIR_RESTRICTION]; |
276
|
|
|
else $restrictExtractDir = false; |
277
|
|
|
|
278
|
|
|
$report = array(); |
279
|
|
|
foreach ($this->listContent() as $file_header) { |
280
|
|
|
$file_header = (object)$file_header; |
281
|
|
|
// add file information to report |
282
|
|
|
$report[] = $file_header; |
283
|
|
|
// refuse by select rule |
284
|
|
|
if (call_user_func($selectFilter, $file_header->stored_filename, |
285
|
|
|
$file_header->filename, $file_header->index) |
286
|
|
|
=== self::SELECT_FILTER_REFUSE) { |
287
|
|
|
// |
288
|
|
|
// I don't know need to remain this file in report or not, |
289
|
|
|
// but for now I remove |
290
|
|
|
array_pop($report); |
291
|
|
|
// $file_header->status = 'filtered'; |
292
|
|
|
// |
293
|
|
|
continue; |
294
|
|
|
} |
295
|
|
|
|
296
|
|
|
// |
297
|
|
|
// add extract path in case of extraction |
298
|
|
|
// for some reason need to do it before call pre extract callback |
299
|
|
|
// (pclzip.lib.php v2.8.2, line 3670) |
300
|
|
|
// so I decided to do it here too |
301
|
|
|
// |
302
|
|
|
if ($anotherOutputFormat === false) { |
303
|
|
|
$file_header->filename = realpath($extractPath).'/'. |
304
|
|
|
$file_header->filename; |
305
|
|
|
// |
306
|
|
|
// check for path correlation with restricted path |
307
|
|
|
// |
308
|
|
|
if ($restrictExtractDir !== false) { |
309
|
|
|
$filename = $file_header->filename; |
310
|
|
|
$restrictedDir = realpath($restrictExtractDir); |
311
|
|
|
if (strncasecmp($restrictedDir, $filename, |
312
|
|
|
strlen($restrictedDir)) !== 0) { |
313
|
|
|
// refuse file extraction |
314
|
|
|
$file_header->status = 'filtered'; |
315
|
|
|
continue; |
316
|
|
|
} |
317
|
|
|
} |
318
|
|
|
} |
319
|
|
|
|
320
|
|
|
// apply pre extract callback |
321
|
|
|
$callback_result = call_user_func($preExtractCallback, |
322
|
|
|
$file_header); |
323
|
|
|
if ($callback_result == 1) { |
324
|
|
|
// go on ... |
325
|
|
|
} elseif ($callback_result == 0) { |
326
|
|
|
// skip current file |
327
|
|
|
$file_header->status = 'skipped'; |
328
|
|
|
continue; |
329
|
|
|
} elseif ($callback_result == 2) { |
330
|
|
|
// skip & stop extraction |
331
|
|
|
$file_header->status = 'aborted'; |
332
|
|
|
break; |
333
|
|
|
} |
334
|
|
|
|
335
|
|
|
// return content |
336
|
|
|
if ($anotherOutputFormat == PCLZIP_OPT_EXTRACT_AS_STRING) { |
337
|
|
|
$file_header->content |
338
|
|
|
= $this->archive->getFileContent($file_header->stored_filename); |
339
|
|
|
} |
340
|
|
|
// echo content |
341
|
|
|
else if ($anotherOutputFormat == PCLZIP_OPT_EXTRACT_IN_OUTPUT) { |
342
|
|
|
echo $this->archive->getFileContent($file_header->stored_filename); |
343
|
|
|
} |
344
|
|
|
// extract content |
345
|
|
|
else if ($anotherOutputFormat === false) { |
346
|
|
|
// apply path filters |
347
|
|
|
foreach ($filters as $filter) call_user_func($filter, |
348
|
|
|
$file_header->stored_filename, $file_header->filename); |
349
|
|
|
// dir extraction process |
350
|
|
|
if ($file_header->folder) { |
351
|
|
|
// if dir doesn't exist |
352
|
|
|
if (!is_dir($file_header->filename)) { |
353
|
|
|
// try to create folder |
354
|
|
|
if (!mkdir($file_header->filename)) { |
355
|
|
|
$file_header->status = 'path_creation_fail'; |
356
|
|
|
continue; |
357
|
|
|
} |
358
|
|
|
} |
359
|
|
|
} |
360
|
|
|
// file extraction process |
361
|
|
|
else { |
362
|
|
|
// check if path is already taken by a folder |
363
|
|
|
if (is_dir($file_header->filename)) { |
364
|
|
|
$file_header->status = 'already_a_directory'; |
365
|
|
|
continue; |
366
|
|
|
} |
367
|
|
|
|
368
|
|
|
// check if file exists and it's newer |
369
|
|
|
if (is_file($file_header->filename)) { |
370
|
|
|
if (filemtime($file_header->filename) |
371
|
|
|
> $file_header->mtime) { |
372
|
|
|
// skip extraction if option EXTRACT_NEWER isn't set |
373
|
|
|
if ($doNotReplaceNewer) { |
374
|
|
|
$file_header->status = 'newer_exist'; |
375
|
|
|
continue; |
376
|
|
|
} |
377
|
|
|
} |
378
|
|
|
} |
379
|
|
|
$directory = dirname($file_header->filename); |
380
|
|
|
// check if running process can not create extraction folder |
381
|
|
|
if (!is_dir($directory) && !mkdir($directory)) { |
382
|
|
|
$file_header->status = 'path_creation_fail'; |
383
|
|
|
continue; |
384
|
|
|
} else if (!is_writable($directory)) { |
385
|
|
|
// check if file path is not writable |
386
|
|
|
$file_header->status = 'write_protected'; |
387
|
|
|
continue; |
388
|
|
|
} |
389
|
|
|
// extraction |
390
|
|
|
$stream = $this->archive->getFileStream($file_header->stored_filename); |
391
|
|
|
if (file_put_contents($file_header->filename, $stream)) { |
392
|
|
|
// ok |
393
|
|
|
} |
394
|
|
|
// extraction fails |
395
|
|
|
else { |
396
|
|
|
$file_header->status = 'write_error'; |
397
|
|
|
continue; |
398
|
|
|
} |
399
|
|
|
} |
400
|
|
|
} |
401
|
|
|
|
402
|
|
|
// apply post extract callback |
403
|
|
|
$callback_result = call_user_func($postExtractCallback, |
404
|
|
|
$file_header); |
405
|
|
|
if ($callback_result == 1) { |
406
|
|
|
// go on |
407
|
|
|
} elseif ($callback_result == 2) { |
408
|
|
|
// skip & stop extraction |
409
|
|
|
break; |
410
|
|
|
} |
411
|
|
|
} |
412
|
|
|
|
413
|
|
|
foreach ($report as $i => $reportItem) { |
414
|
|
|
$report[$i] = (array)$reportItem; |
415
|
|
|
} |
416
|
|
|
|
417
|
|
|
return $report; |
418
|
|
|
} |
419
|
|
|
|
420
|
|
|
/** |
421
|
|
|
* Reads properties of archive |
422
|
|
|
*/ |
423
|
|
|
public function properties() |
424
|
|
|
{ |
425
|
|
|
return [ |
426
|
|
|
'nb' => $this->archive->countFiles(), |
427
|
|
|
'comment' => $this->archive->getComment(), |
|
|
|
|
428
|
|
|
'status' => 'OK', |
429
|
|
|
]; |
430
|
|
|
} |
431
|
|
|
|
432
|
|
|
/** |
433
|
|
|
* Adds files in archive |
434
|
|
|
* <code>add($content, [$addDir, [$removeDir]])</code> |
435
|
|
|
* <code>add($content, [ ... options ... ])</code> |
436
|
|
|
*/ |
437
|
|
|
public function add($content) |
438
|
|
|
{ |
439
|
|
|
if (is_array($content)) $paths_list = $content; |
440
|
|
|
else $paths_list = explode(',', $content); |
441
|
|
|
|
442
|
|
|
$options = $this->makeOptionsFromArguments(func_get_args()); |
443
|
|
|
$filters = $this->createFilters($options); |
444
|
|
|
list($preAddCallback, $postAddCallback) = $this->extractCallbacks($options, PCLZIP_CB_PRE_ADD, PCLZIP_CB_POST_ADD); |
445
|
|
|
|
446
|
|
|
if (!empty($comment = $this->buildComment($options, $this->archive->getComment()))) |
|
|
|
|
447
|
|
|
$this->archive->setComment($comment); |
448
|
|
|
|
449
|
|
|
// scan filesystem for files list |
450
|
|
|
return $this->addSnippets($paths_list, $filters, $preAddCallback, $postAddCallback); |
451
|
|
|
} |
452
|
|
|
|
453
|
|
|
/** |
454
|
|
|
* Removes files from archive |
455
|
|
|
* Usage: |
456
|
|
|
* <code>delete([... options ...])</code> |
457
|
|
|
*/ |
458
|
|
|
public function delete() |
459
|
|
|
{ |
460
|
|
|
$options = $this->makeKeyValueArrayFromList(func_get_args()); |
461
|
|
|
$selectFilter = $this->createSelector($options); |
462
|
|
|
|
463
|
|
|
$report = []; |
464
|
|
|
foreach ($this->listContent() as $file_header) { |
465
|
|
|
$file_header = (object)$file_header; |
466
|
|
|
// select by select rule |
467
|
|
|
if (call_user_func($selectFilter, $file_header->stored_filename, |
468
|
|
|
$file_header->filename, $file_header->index) |
469
|
|
|
=== self::SELECT_FILTER_REFUSE) { |
470
|
|
|
// delete file from archive |
471
|
|
|
if ($this->archive->delete($file_header->stored_filename)) { |
472
|
|
|
// ok |
473
|
|
|
continue; |
474
|
|
|
} |
475
|
|
|
// deletion fails |
476
|
|
|
else { |
477
|
|
|
return 0; |
478
|
|
|
} |
479
|
|
|
} |
480
|
|
|
// unselected file add in report |
481
|
|
|
$report[] = $file_header; |
482
|
|
|
} |
483
|
|
|
|
484
|
|
|
foreach ($report as $i => $reportItem) { |
485
|
|
|
$report[$i] = (array)$reportItem; |
486
|
|
|
} |
487
|
|
|
|
488
|
|
|
return $report; |
489
|
|
|
} |
490
|
|
|
|
491
|
|
|
/** |
492
|
|
|
* Merges given archive into current archive |
493
|
|
|
* Two ways of usage: |
494
|
|
|
* <code>merge($filename)</code> |
495
|
|
|
* <code>merge(UnifiedArchive $unifiedArchiveInstance)</code> |
496
|
|
|
* This implementation is more intelligent than original' one. |
497
|
|
|
*/ |
498
|
|
|
public function merge($a) |
499
|
|
|
{ |
500
|
|
|
// filename |
501
|
|
|
if (is_string($a)) { |
502
|
|
|
if (($a = UnifiedArchive::open($a)) !== null) { |
503
|
|
|
// ok |
504
|
|
|
} else { |
505
|
|
|
// // unsupported type of archive |
506
|
|
|
return 0; |
507
|
|
|
} |
508
|
|
|
} |
509
|
|
|
// UnifiedArchive instance |
510
|
|
|
else if ($a instanceof UnifiedArchive) { |
511
|
|
|
// go on |
512
|
|
|
} |
513
|
|
|
// invalid argument |
514
|
|
|
else { |
515
|
|
|
return 0; |
516
|
|
|
} |
517
|
|
|
|
518
|
|
|
$tempDir = tempnam(PCLZIP_TEMPORARY_DIR, 'merging'); |
519
|
|
|
if (file_exists($tempDir)) unlink($tempDir); |
520
|
|
|
if (!mkdir($tempDir)) return 0; |
521
|
|
|
|
522
|
|
|
// go through archive content list and copy all files |
523
|
|
|
foreach ($a->getFiles() as $filename) { |
524
|
|
|
// dir merging process |
525
|
|
|
if (in_array(substr($filename, -1), array('/', '\\'))) { |
526
|
|
|
// $this->archive->addEmptyDir(rtrim($filename, '/\\')); |
527
|
|
|
} |
528
|
|
|
// file merging process |
529
|
|
|
else { |
530
|
|
|
// extract file in temporary dir |
531
|
|
|
if ($a->extractNode($tempDir, '/'.$filename)) { |
|
|
|
|
532
|
|
|
// go on |
533
|
|
|
} else { |
534
|
|
|
// extraction fails |
535
|
|
|
return 0; |
536
|
|
|
} |
537
|
|
|
// add file in archive |
538
|
|
|
if ($this->archive->add([$tempDir.'/'.$filename => $filename])) { |
539
|
|
|
// ok |
540
|
|
|
} else { |
541
|
|
|
return 0; |
542
|
|
|
} |
543
|
|
|
} |
544
|
|
|
} |
545
|
|
|
|
546
|
|
|
call_user_func(function ($directory) { |
547
|
|
|
foreach (glob($directory.'/*') as $f) { |
548
|
|
|
if (is_dir($f)) call_user_func(__FUNCTION__, $f); |
549
|
|
|
else unlink($f); |
550
|
|
|
} |
551
|
|
|
}, $tempDir); |
552
|
|
|
|
553
|
|
|
return 1; |
554
|
|
|
} |
555
|
|
|
|
556
|
|
|
/** |
557
|
|
|
* Duplicates archive |
558
|
|
|
* @param $clone_filename |
559
|
|
|
* @return int |
560
|
|
|
*/ |
561
|
|
|
public function duplicate($clone_filename) |
562
|
|
|
{ |
563
|
|
|
return copy($this->archive->filename, $clone_filename) ? 1 : 0; |
|
|
|
|
564
|
|
|
} |
565
|
|
|
|
566
|
|
|
/** |
567
|
|
|
* @param array $options |
568
|
|
|
* @return array |
569
|
|
|
*/ |
570
|
|
|
public function createFilters(array $options) |
571
|
|
|
{ |
572
|
|
|
$filters = array(); |
573
|
|
|
if (isset($options[PCLZIP_OPT_REMOVE_PATH]) |
574
|
|
|
&& !isset($options[PCLZIP_OPT_REMOVE_ALL_PATH])) |
575
|
|
|
$filters[] = function (&$key, &$value) use ($options) { |
|
|
|
|
576
|
|
|
$key = str_replace($key, null, $key); |
577
|
|
|
}; |
578
|
|
|
if (isset($options[PCLZIP_OPT_REMOVE_ALL_PATH])) |
579
|
|
|
$filters[] = function (&$key, &$value) { |
|
|
|
|
580
|
|
|
$key = basename($key); |
581
|
|
|
}; |
582
|
|
|
if (isset($options[PCLZIP_OPT_ADD_PATH])) |
583
|
|
|
$filters[] = function (&$key, &$value) use ($options) { |
|
|
|
|
584
|
|
|
$key = rtrim($options[PCLZIP_OPT_ADD_PATH], '/') . '/' . |
585
|
|
|
ltrim($key, '/'); |
586
|
|
|
}; |
587
|
|
|
return $filters; |
588
|
|
|
} |
589
|
|
|
|
590
|
|
|
/** |
591
|
|
|
* @param array $options |
592
|
|
|
* @param string $preCallbackConst |
593
|
|
|
* @param string $postCallbackConst |
594
|
|
|
* @return callable[]|\Closure[] |
595
|
|
|
*/ |
596
|
|
|
private function extractCallbacks(array $options, $preCallbackConst, $postCallbackConst) |
597
|
|
|
{ |
598
|
|
|
$preCallback = $postCallback = function () { return true; }; |
599
|
|
|
|
600
|
|
|
if (isset($options[$preCallbackConst]) && is_callable($options[$preCallbackConst])) |
601
|
|
|
$preCallback = $options[$preCallbackConst]; |
602
|
|
|
|
603
|
|
|
if (isset($options[$postCallbackConst]) && is_callable($options[$postCallbackConst])) |
604
|
|
|
$postCallback = $options[$postCallbackConst]; |
605
|
|
|
|
606
|
|
|
return [$preCallback, $postCallback]; |
607
|
|
|
} |
608
|
|
|
|
609
|
|
|
/** |
610
|
|
|
* @param array $options |
611
|
|
|
* @return array |
612
|
|
|
*/ |
613
|
|
|
private function makeKeyValueArrayFromList(array $options) |
614
|
|
|
{ |
615
|
|
|
// @todo create version for 5.5 of ARRAY_FILTER_USE_KEY function or drop 5.5 support |
616
|
|
|
$keys = array_filter($options, function ($v) {return ($v%2) == 0;}, ARRAY_FILTER_USE_KEY); |
617
|
|
|
$values = array_filter($options, function ($v) {return ($v%2) == 1;}, ARRAY_FILTER_USE_KEY); |
618
|
|
|
if (count($values) < count($keys)) $values[] = true; |
619
|
|
|
return array_combine($keys, $values); |
620
|
|
|
} |
621
|
|
|
|
622
|
|
|
/** |
623
|
|
|
* @param array $pathsList |
624
|
|
|
* @param array $filters |
625
|
|
|
* @param callable $preAddCallback |
626
|
|
|
* @param callable $postAddCallback |
627
|
|
|
* @return array |
628
|
|
|
*/ |
629
|
|
|
public function addSnippets(array $pathsList, array $filters, callable $preAddCallback, callable $postAddCallback) |
630
|
|
|
{ |
631
|
|
|
$report = []; |
632
|
|
|
|
633
|
|
|
foreach ($pathsList as $file_to_add) { |
634
|
|
|
$report[] = $this->addSnippet($file_to_add, $filters, |
635
|
|
|
$preAddCallback, $postAddCallback); |
636
|
|
|
|
637
|
|
|
// additional dir contents |
638
|
|
|
if (is_dir($file_to_add)) { |
639
|
|
|
$directory_contents = new \RecursiveIteratorIterator( |
640
|
|
|
new \RecursiveDirectoryIterator( |
641
|
|
|
$file_to_add, \RecursiveDirectoryIterator::SKIP_DOTS), |
642
|
|
|
RecursiveIteratorIterator::SELF_FIRST); |
643
|
|
|
foreach ($directory_contents as $indir_file_to_add) { |
644
|
|
|
$report[] = $this->addSnippet($indir_file_to_add, $filters, |
645
|
|
|
$preAddCallback, $postAddCallback); |
646
|
|
|
} |
647
|
|
|
} |
648
|
|
|
} |
649
|
|
|
return $report; |
650
|
|
|
} |
651
|
|
|
|
652
|
|
|
/** |
653
|
|
|
* @param array $indexes |
654
|
|
|
* @return \Closure |
655
|
|
|
*/ |
656
|
|
|
protected function createByIndexSelector(array $indexes) |
657
|
|
|
{ |
658
|
|
|
$allowedIndexes = array(); |
659
|
|
|
foreach ($indexes as $rule) { |
660
|
|
|
$parts = explode('-', $rule); |
661
|
|
|
if (count($parts) == 1) $allowedIndexes[] = $rule; |
662
|
|
|
else $allowedIndexes = array_merge( |
663
|
|
|
range($parts[0], $parts[1]), $allowedIndexes); |
664
|
|
|
} |
665
|
|
|
|
666
|
|
|
return function ($key, $value, $index) use ($allowedIndexes) { |
667
|
|
|
return in_array($index, $allowedIndexes) |
668
|
|
|
? self::SELECT_FILTER_PASS |
669
|
|
|
: self::SELECT_FILTER_REFUSE; |
670
|
|
|
}; |
671
|
|
|
} |
672
|
|
|
|
673
|
|
|
/** |
674
|
|
|
* @param string|array $names |
675
|
|
|
* @return \Closure |
676
|
|
|
*/ |
677
|
|
|
protected function createByNameSelector($names) |
678
|
|
|
{ |
679
|
|
|
$allowedNames = is_array($names) |
680
|
|
|
? $names |
681
|
|
|
: explode(',', $names); |
682
|
|
|
|
683
|
|
|
return function ($key, $value) use ($allowedNames) { |
|
|
|
|
684
|
|
|
foreach ($allowedNames as $name) { |
685
|
|
|
// select directory with nested files |
686
|
|
|
if (in_array(substr($name, -1), ['/', '\\'])) { |
687
|
|
|
if (strncasecmp($name, $key, strlen($name)) === 0) { |
688
|
|
|
// that's a file inside a dir or that dir |
689
|
|
|
return self::SELECT_FILTER_PASS; |
690
|
|
|
} |
691
|
|
|
} else { |
692
|
|
|
// select exact name only |
693
|
|
|
if (strcasecmp($name, $key) === 0) { |
694
|
|
|
// that's a file with this name |
695
|
|
|
return self::SELECT_FILTER_PASS; |
696
|
|
|
} |
697
|
|
|
} |
698
|
|
|
} |
699
|
|
|
|
700
|
|
|
// that file is not in allowed list |
701
|
|
|
return self::SELECT_FILTER_REFUSE; |
702
|
|
|
}; |
703
|
|
|
} |
704
|
|
|
|
705
|
|
|
/** |
706
|
|
|
* @param string $regex |
707
|
|
|
* @return \Closure |
708
|
|
|
*/ |
709
|
|
|
protected function createByEregSelector($regex) |
710
|
|
|
{ |
711
|
|
|
return function ($key, $value) use ($regex) { |
|
|
|
|
712
|
|
|
return (ereg($regex, $key) !== false) |
713
|
|
|
? self::SELECT_FILTER_PASS |
714
|
|
|
: self::SELECT_FILTER_REFUSE; |
715
|
|
|
}; |
716
|
|
|
} |
717
|
|
|
|
718
|
|
|
/** |
719
|
|
|
* @param $regex |
720
|
|
|
* @return \Closure |
721
|
|
|
*/ |
722
|
|
|
protected function createByPregSelector($regex) |
723
|
|
|
{ |
724
|
|
|
return function ($key, $value) use ($regex) { |
|
|
|
|
725
|
|
|
return preg_match($regex, $key) |
726
|
|
|
? self::SELECT_FILTER_PASS |
727
|
|
|
: self::SELECT_FILTER_REFUSE; |
728
|
|
|
}; |
729
|
|
|
} |
730
|
|
|
|
731
|
|
|
/** |
732
|
|
|
* @param array $options |
733
|
|
|
* @return callable |
734
|
|
|
*/ |
735
|
|
|
protected function createSelector(array $options) |
736
|
|
|
{ |
737
|
|
|
// exact matching |
738
|
|
|
if (isset($options[PCLZIP_OPT_BY_NAME])) |
739
|
|
|
$selectFilter = $this->createByNameSelector($options[PCLZIP_OPT_BY_NAME]); |
740
|
|
|
// <ereg> rule |
741
|
|
|
else if (isset($options[PCLZIP_OPT_BY_EREG]) && function_exists('ereg')) |
742
|
|
|
$selectFilter = $this->createByEregSelector($options[PCLZIP_OPT_BY_EREG]); |
743
|
|
|
// <preg_match> rule |
744
|
|
|
else if (isset($options[PCLZIP_OPT_BY_PREG])) |
745
|
|
|
$selectFilter = $this->createByPregSelector($options[PCLZIP_OPT_BY_PREG]); |
746
|
|
|
// index rule |
747
|
|
|
else if (isset($options[PCLZIP_OPT_BY_INDEX])) |
748
|
|
|
$selectFilter = $this->createByIndexSelector($options[PCLZIP_OPT_BY_INDEX]); |
749
|
|
|
// no rule |
750
|
|
|
else |
751
|
|
|
$selectFilter = function () { |
752
|
|
|
return self::SELECT_FILTER_PASS; |
753
|
|
|
}; |
754
|
|
|
return $selectFilter; |
755
|
|
|
} |
756
|
|
|
|
757
|
|
|
/** |
758
|
|
|
* @param array $args |
759
|
|
|
* @return array |
760
|
|
|
*/ |
761
|
|
|
protected function makeOptionsFromArguments(array $args) |
762
|
|
|
{ |
763
|
|
|
array_shift($args); |
764
|
|
|
|
765
|
|
|
// parse options |
766
|
|
|
if (isset($args[0]) && is_string($args[0])) { |
767
|
|
|
$options = [ |
768
|
|
|
PCLZIP_OPT_ADD_PATH => $args[0] |
769
|
|
|
]; |
770
|
|
|
|
771
|
|
|
if (isset($args[1]) && is_string($args[1])) { |
772
|
|
|
$options[PCLZIP_OPT_REMOVE_PATH] = $args[1]; |
773
|
|
|
} |
774
|
|
|
} else { |
775
|
|
|
$options = $this->makeKeyValueArrayFromList($args); |
776
|
|
|
} |
777
|
|
|
return $options; |
778
|
|
|
} |
779
|
|
|
|
780
|
|
|
/** |
781
|
|
|
* @param array $options |
782
|
|
|
* @param string|null $currentComment |
783
|
|
|
* @return mixed|string|null |
784
|
|
|
*/ |
785
|
|
|
protected function buildComment(array $options, $currentComment) |
786
|
|
|
{ |
787
|
|
|
$comment = null; |
788
|
|
|
if (isset($options[PCLZIP_OPT_COMMENT])) |
789
|
|
|
$comment = $options[PCLZIP_OPT_COMMENT]; |
790
|
|
|
else if (isset($options[PCLZIP_OPT_ADD_COMMENT])) {; |
791
|
|
|
$comment = $currentComment . $options[PCLZIP_OPT_ADD_COMMENT]; |
792
|
|
|
} else if (isset($options[PCLZIP_OPT_PREPEND_COMMENT])) { |
793
|
|
|
$comment = $options[PCLZIP_OPT_PREPEND_COMMENT] . $currentComment; |
794
|
|
|
} |
795
|
|
|
return $comment; |
796
|
|
|
} |
797
|
|
|
} |
798
|
|
|
|
This check looks for function or method calls that always return null and whose return value is used.
The method
getObject()
can return nothing but null, so it makes no sense to use the return value.The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.