Passed
Push — master ( ba3494...32b8d0 )
by f
11:53
created

PclZipInterface::duplicate()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 1
nc 2
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
namespace wapmorgan\UnifiedArchive;
3
4
use RecursiveIteratorIterator;
5
6
if (!defined('PCLZIP_ERR_NO_ERROR')) {
7
    // ----- Constants
8
    if (!defined('PCLZIP_READ_BLOCK_SIZE')) {
9
        define('PCLZIP_READ_BLOCK_SIZE', 2048);
10
    }
11
    if (!defined('PCLZIP_SEPARATOR')) {
12
        define('PCLZIP_SEPARATOR', ',');
13
    }
14
    if (!defined('PCLZIP_ERROR_EXTERNAL')) {
15
        define('PCLZIP_ERROR_EXTERNAL', 0);
16
    }
17
    if (!defined('PCLZIP_TEMPORARY_DIR')) {
18
        define('PCLZIP_TEMPORARY_DIR', sys_get_temp_dir());
19
    }
20
21
    define('PCLZIP_ERR_USER_ABORTED', 2);
22
    define('PCLZIP_ERR_NO_ERROR', 0);
23
    define('PCLZIP_ERR_WRITE_OPEN_FAIL', -1);
24
    define('PCLZIP_ERR_READ_OPEN_FAIL', -2);
25
    define('PCLZIP_ERR_INVALID_PARAMETER', -3);
26
    define('PCLZIP_ERR_MISSING_FILE', -4);
27
    define('PCLZIP_ERR_FILENAME_TOO_LONG', -5);
28
    define('PCLZIP_ERR_INVALID_ZIP', -6);
29
    define('PCLZIP_ERR_BAD_EXTRACTED_FILE', -7);
30
    define('PCLZIP_ERR_DIR_CREATE_FAIL', -8);
31
    define('PCLZIP_ERR_BAD_EXTENSION', -9);
32
    define('PCLZIP_ERR_BAD_FORMAT', -10);
33
    define('PCLZIP_ERR_DELETE_FILE_FAIL', -11);
34
    define('PCLZIP_ERR_RENAME_FILE_FAIL', -12);
35
    define('PCLZIP_ERR_BAD_CHECKSUM', -13);
36
    define('PCLZIP_ERR_INVALID_ARCHIVE_ZIP', -14);
37
    define('PCLZIP_ERR_MISSING_OPTION_VALUE', -15);
38
    define('PCLZIP_ERR_INVALID_OPTION_VALUE', -16);
39
    define('PCLZIP_ERR_ALREADY_A_DIRECTORY', -17);
40
    define('PCLZIP_ERR_UNSUPPORTED_COMPRESSION', -18);
41
    define('PCLZIP_ERR_UNSUPPORTED_ENCRYPTION', -19);
42
    define('PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE', -20);
43
    define('PCLZIP_ERR_DIRECTORY_RESTRICTION', -21);
44
45
    // ----- Options values
46
    define('PCLZIP_OPT_PATH', 77001);
47
    define('PCLZIP_OPT_ADD_PATH', 77002);
48
    define('PCLZIP_OPT_REMOVE_PATH', 77003);
49
    define('PCLZIP_OPT_REMOVE_ALL_PATH', 77004);
50
    define('PCLZIP_OPT_SET_CHMOD', 77005);
51
    define('PCLZIP_OPT_EXTRACT_AS_STRING', 77006);
52
    define('PCLZIP_OPT_NO_COMPRESSION', 77007);
53
    define('PCLZIP_OPT_BY_NAME', 77008);
54
    define('PCLZIP_OPT_BY_INDEX', 77009);
55
    define('PCLZIP_OPT_BY_EREG', 77010);
56
    define('PCLZIP_OPT_BY_PREG', 77011);
57
    define('PCLZIP_OPT_COMMENT', 77012);
58
    define('PCLZIP_OPT_ADD_COMMENT', 77013);
59
    define('PCLZIP_OPT_PREPEND_COMMENT', 77014);
60
    define('PCLZIP_OPT_EXTRACT_IN_OUTPUT', 77015);
61
    define('PCLZIP_OPT_REPLACE_NEWER', 77016);
62
    define('PCLZIP_OPT_STOP_ON_ERROR', 77017);
63
    // Having big trouble with crypt. Need to multiply 2 long int
64
    // which is not correctly supported by PHP ...
65
    //define( 'PCLZIP_OPT_CRYPT', 77018 );
66
    define('PCLZIP_OPT_EXTRACT_DIR_RESTRICTION', 77019);
67
    define('PCLZIP_OPT_TEMP_FILE_THRESHOLD', 77020);
68
    define('PCLZIP_OPT_ADD_TEMP_FILE_THRESHOLD', 77020); // alias
69
    define('PCLZIP_OPT_TEMP_FILE_ON', 77021);
70
    define('PCLZIP_OPT_ADD_TEMP_FILE_ON', 77021); // alias
71
    define('PCLZIP_OPT_TEMP_FILE_OFF', 77022);
72
    define('PCLZIP_OPT_ADD_TEMP_FILE_OFF', 77022); // alias
73
74
    // ----- File description attributes
75
    define('PCLZIP_ATT_FILE_NAME', 79001);
76
    define('PCLZIP_ATT_FILE_NEW_SHORT_NAME', 79002);
77
    define('PCLZIP_ATT_FILE_NEW_FULL_NAME', 79003);
78
    define('PCLZIP_ATT_FILE_MTIME', 79004);
79
    define('PCLZIP_ATT_FILE_CONTENT', 79005);
80
    define('PCLZIP_ATT_FILE_COMMENT', 79006);
81
82
    // ----- Call backs values
83
    define('PCLZIP_CB_PRE_EXTRACT', 78001);
84
    define('PCLZIP_CB_POST_EXTRACT', 78002);
85
    define('PCLZIP_CB_PRE_ADD', 78003);
86
    define('PCLZIP_CB_POST_ADD', 78004);
87
}
88
89
class PclZipInterface
90
{
91
    const SELECT_FILTER_PASS = 1;
92
    const SELECT_FILTER_REFUSE = 0;
93
94
    const AVERAGE_ZIP_COMPRESSION_RATIO = 2;
95
96
    /**
97
     * @var UnifiedArchive
98
     */
99
    private $archive;
100
101
    /**
102
     * PclzipZipInterface constructor.
103
     *
104
     * @param UnifiedArchive $archive
105
     */
106
    public function __construct(UnifiedArchive $archive)
107
    {
108
        $this->archive = $archive;
109
    }
110
111
    /**
112
     * @param $localname
113
     * @param $filename
114
     *
115
     * @return object
116
     */
117
    public function createFileHeader($localname, $filename)
118
    {
119
        return (object) array(
120
            'filename' => $filename,
121
            'stored_filename' => $localname,
122
            'size' => filesize($filename),
123
            'compressed_size' => ceil(filesize($filename)
124
                / self::AVERAGE_ZIP_COMPRESSION_RATIO),
125
            'mtime' => filemtime($filename),
126
            'comment' => null,
127
            'folder' => is_dir($filename),
128
            'status' => 'ok',
129
        );
130
    }
131
132
    /**
133
     * Creates a new archive
134
     * Two ways of usage:
135
     * <code>create($content, [$addDir, [$removeDir]])</code>
136
     * <code>create($content, [... options ...]])</code>
137
     */
138
    public function create($content)
139
    {
140
        if (is_array($content)) $paths_list = $content;
141
        else $paths_list = explode(',', $content);
142
143
        $options = $this->makeOptionsFromArguments(func_get_args());
144
145
        // filters initiation
146
        $filters = $this->createFilters($options);
147
        list($preAddCallback, $postAddCallback) = $this->extractCallbacks($options, PCLZIP_CB_PRE_ADD, PCLZIP_CB_POST_ADD);
148
149
        if (!empty($comment = $this->buildComment($options, null)))
150
            $this->archive->setComment($comment);
151
152
        // scan filesystem for files list
153
        return $this->addSnippets($paths_list, $filters, $preAddCallback, $postAddCallback);
154
    }
155
156
    /**
157
     * @param string $fileToAdd
158
     * @param array $filters
159
     * @param callable|null $preAddCallback
160
     * @param callable|null $postAddCallback
161
     * @return object
162
     */
163
    private function addSnippet($fileToAdd, array $filters, callable $preAddCallback, callable $postAddCallback)
164
    {
165
        if (is_file($fileToAdd) || is_dir($fileToAdd)) {
166
            // apply filters to a file
167
            $localname = $fileToAdd;
168
            $filename = $fileToAdd;
169
170
            foreach ($filters as $filter)
171
                call_user_func($filter, $localname, $filename);
172
173
            $file_header = $this->createFileHeader($localname, $filename);
174
            if (call_user_func($preAddCallback, $file_header) == 1) {
175
                //
176
                // Check for max length > 255
177
                //
178
                if (strlen(basename($file_header->stored_filename)) > 255)
179
                    $file_header->status = 'filename_too_long';
180
                if (is_file($filename)) {
181
                    $this->archive->addFiles([
182
                        $file_header->stored_filename => $file_header->filename,
183
                    ]);
184
                } else if (is_dir($filename)) {
185
//                    $this->archive->addEmptyDir($file_header->stored_filename);
186
                }
187
188
                call_user_func($postAddCallback, $file_header);
189
190
            } else {
191
                //
192
                // File was skipped
193
                //
194
                $file_header->status = 'skipped';
195
            }
196
197
            return $file_header;
198
        }
199
    }
200
201
    /**
202
     * Lists archive content
203
     * @throws Exceptions\NonExistentArchiveFileException
204
     */
205
    public function listContent()
206
    {
207
        $filesList = [];
208
209
        foreach ($this->archive->getFileNames() as $i => $fileName) {
210
            $fileData = $this->archive->getFileData($fileName);
211
212
            $filesList[] = [
213
                'filename' => $fileData->path,
214
                'stored_filename' => $fileData->path,
215
                'size' => $fileData->uncompressedSize,
216
                'compressed_size' => $fileData->compressedSize,
217
                'mtime' => $fileData->modificationTime,
218
                'comment' => $fileData->comment,
219
                'folder' => false/*in_array(substr($statIndex['name'], -1),
220
                    array('/', '\\'))*/,
221
                'index' => $i,
222
                'status' => 'ok',
223
            ];
224
        }
225
226
        return $filesList;
227
    }
228
229
    /**
230
     * Extracts files
231
     * Two ways of usage:
232
     * <code>extract([$extractPath, [$removePath]])</code>
233
     * <code>extract([... options ...]])</code>
234
     */
235
    public function extract()
236
    {
237
        $options = func_get_args();
238
        //array_shift($options);
239
240
        // parse options
241
        if (isset($options[0]) && is_string($options[0])) {
242
            $options[PCLZIP_OPT_PATH] = $options[0];
243
            if (isset($options[1]) && is_string($options[1])) {
244
                $options[PCLZIP_OPT_REMOVE_PATH] = $options[1];
245
            }
246
        } else {
247
            $options = $this->makeKeyValueArrayFromList($options);
248
        }
249
250
        // filters initiation
251
        if (isset($options[PCLZIP_OPT_PATH]))
252
            $extractPath = rtrim($options[PCLZIP_OPT_PATH], '/');
253
        else $extractPath = rtrim(getcwd(), '/');
254
255
        $filters = $this->createFilters($options);
256
        list($preExtractCallback, $postExtractCallback) = $this->extractCallbacks($options, PCLZIP_CB_PRE_EXTRACT, PCLZIP_CB_POST_EXTRACT);
257
        $selectFilter = $this->createSelector($options);
258
259
        if (isset($options[PCLZIP_OPT_EXTRACT_AS_STRING]))
260
            $anotherOutputFormat = PCLZIP_OPT_EXTRACT_AS_STRING;
261
        else if (isset($options[PCLZIP_OPT_EXTRACT_IN_OUTPUT]))
262
            $anotherOutputFormat = PCLZIP_OPT_EXTRACT_IN_OUTPUT;
263
        else $anotherOutputFormat = false;
264
265
        if (isset($options[PCLZIP_OPT_REPLACE_NEWER]))
266
            $doNotReplaceNewer = false;
267
        else $doNotReplaceNewer = true;
268
269
        if (isset($options[PCLZIP_OPT_EXTRACT_DIR_RESTRICTION]))
270
            $restrictExtractDir = $options[PCLZIP_OPT_EXTRACT_DIR_RESTRICTION];
271
        else $restrictExtractDir = false;
272
273
        $report = array();
274
        foreach ($this->listContent() as $file_header) {
275
            $file_header = (object)$file_header;
276
            // add file information to report
277
            $report[] = $file_header;
278
            // refuse by select rule
279
            if (call_user_func($selectFilter, $file_header->stored_filename,
280
                    $file_header->filename, $file_header->index)
281
                === self::SELECT_FILTER_REFUSE) {
282
                //
283
                // I don't know need to remain this file in report or not,
284
                // but for now I remove
285
                array_pop($report);
286
                // $file_header->status = 'filtered';
287
                //
288
                continue;
289
            }
290
291
            //
292
            // add extract path in case of extraction
293
            // for some reason need to do it before call pre extract callback
294
            // (pclzip.lib.php v2.8.2, line 3670)
295
            // so I decided to do it here too
296
            //
297
            if ($anotherOutputFormat === false) {
298
                $file_header->filename = realpath($extractPath).'/'.
299
                    $file_header->filename;
300
                //
301
                // check for path correlation with restricted path
302
                //
303
                if ($restrictExtractDir !== false) {
304
                    $filename = $file_header->filename;
305
                    $restrictedDir = realpath($restrictExtractDir);
306
                    if (strncasecmp($restrictedDir, $filename,
307
                            strlen($restrictedDir)) !== 0) {
308
                        // refuse file extraction
309
                        $file_header->status = 'filtered';
310
                        continue;
311
                    }
312
                }
313
            }
314
315
            // apply pre extract callback
316
            $callback_result = call_user_func($preExtractCallback,
317
                $file_header);
318
            if ($callback_result == 1) {
319
                // go on ...
320
            } elseif ($callback_result == 0) {
321
                // skip current file
322
                $file_header->status = 'skipped';
323
                continue;
324
            } elseif ($callback_result == 2) {
325
                // skip & stop extraction
326
                $file_header->status = 'aborted';
327
                break;
328
            }
329
330
            // return content
331
            if ($anotherOutputFormat == PCLZIP_OPT_EXTRACT_AS_STRING) {
332
                $file_header->content
333
                    = $this->archive->getFileContent($file_header->stored_filename);
334
            }
335
            // echo content
336
            else if ($anotherOutputFormat == PCLZIP_OPT_EXTRACT_IN_OUTPUT) {
337
                echo $this->archive->getFileContent($file_header->stored_filename);
338
            }
339
            // extract content
340
            else if ($anotherOutputFormat === false) {
341
                // apply path filters
342
                foreach ($filters as $filter) call_user_func($filter,
343
                    $file_header->stored_filename, $file_header->filename);
344
                // dir extraction process
345
                if ($file_header->folder) {
346
                    // if dir doesn't exist
347
                    if (!is_dir($file_header->filename)) {
348
                        // try to create folder
349
                        if (!mkdir($file_header->filename)) {
350
                            $file_header->status = 'path_creation_fail';
351
                            continue;
352
                        }
353
                    }
354
                }
355
                // file extraction process
356
                else {
357
                    // check if path is already taken by a folder
358
                    if (is_dir($file_header->filename)) {
359
                        $file_header->status = 'already_a_directory';
360
                        continue;
361
                    }
362
363
                    // check if file exists and it's newer
364
                    if (is_file($file_header->filename)) {
365
                        if (filemtime($file_header->filename)
366
                            > $file_header->mtime) {
367
                            // skip extraction if option EXTRACT_NEWER isn't set
368
                            if ($doNotReplaceNewer) {
369
                                $file_header->status = 'newer_exist';
370
                                continue;
371
                            }
372
                        }
373
                    }
374
                    $directory = dirname($file_header->filename);
375
                    // check if running process can not create extraction folder
376
                    if (!is_dir($directory) && !mkdir($directory)) {
377
                            $file_header->status = 'path_creation_fail';
378
                            continue;
379
                    } else if (!is_writable($directory)) {
380
                        // check if file path is not writable
381
                        $file_header->status = 'write_protected';
382
                        continue;
383
                    }
384
                    // extraction
385
                    $stream = $this->archive->getFileStream($file_header->stored_filename);
386
                    if (file_put_contents($file_header->filename, $stream)) {
387
                        // ok
388
                    }
389
                    // extraction fails
390
                    else {
391
                        $file_header->status = 'write_error';
392
                        continue;
393
                    }
394
                }
395
            }
396
397
            // apply post extract callback
398
            $callback_result = call_user_func($postExtractCallback,
399
                $file_header);
400
            if ($callback_result == 1) {
401
                // go on
402
            } elseif ($callback_result == 2) {
403
                // skip & stop extraction
404
                break;
405
            }
406
        }
407
408
        foreach ($report as $i => $reportItem) {
409
            $report[$i] = (array)$reportItem;
410
        }
411
412
        return $report;
413
    }
414
415
    /**
416
     * Reads properties of archive
417
     */
418
    public function properties()
419
    {
420
        return [
421
            'nb' => $this->archive->countFiles(),
422
            'comment' => $this->archive->getComment(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->archive->getComment() targeting wapmorgan\UnifiedArchive...edArchive::getComment() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

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.

Loading history...
423
            'status' => 'OK',
424
        ];
425
    }
426
427
    /**
428
     * Adds files in archive
429
     * <code>add($content, [$addDir, [$removeDir]])</code>
430
     * <code>add($content, [ ... options ... ])</code>
431
     */
432
    public function add($content)
433
    {
434
        if (is_array($content)) $paths_list = $content;
435
        else $paths_list = explode(',', $content);
436
437
        $options = $this->makeOptionsFromArguments(func_get_args());
438
        $filters = $this->createFilters($options);
439
        list($preAddCallback, $postAddCallback) = $this->extractCallbacks($options, PCLZIP_CB_PRE_ADD, PCLZIP_CB_POST_ADD);
440
441
        if (!empty($comment = $this->buildComment($options, $this->archive->getComment())))
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->archive->getComment() targeting wapmorgan\UnifiedArchive...edArchive::getComment() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

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.

Loading history...
442
            $this->archive->setComment($comment);
443
444
        // scan filesystem for files list
445
        return $this->addSnippets($paths_list, $filters, $preAddCallback, $postAddCallback);
446
    }
447
448
    /**
449
     * Removes files from archive
450
     * Usage:
451
     * <code>delete([... options ...])</code>
452
     */
453
    public function delete()
454
    {
455
        $options = $this->makeKeyValueArrayFromList(func_get_args());
456
        $selectFilter = $this->createSelector($options);
457
458
        $report = [];
459
        foreach ($this->listContent() as $file_header) {
460
            $file_header = (object)$file_header;
461
            // select by select rule
462
            if (call_user_func($selectFilter, $file_header->stored_filename,
463
                    $file_header->filename, $file_header->index)
464
                === self::SELECT_FILTER_REFUSE) {
465
                // delete file from archive
466
                if ($this->archive->deleteFiles($file_header->stored_filename)) {
467
                    // ok
468
                    continue;
469
                }
470
                // deletion fails
471
                else {
472
                    return 0;
473
                }
474
            }
475
            // unselected file add in report
476
            $report[] = $file_header;
477
        }
478
479
        foreach ($report as $i => $reportItem) {
480
            $report[$i] = (array)$reportItem;
481
        }
482
483
        return $report;
484
    }
485
486
    /**
487
     * Merges given archive into current archive
488
     * Two ways of usage:
489
     * <code>merge($filename)</code>
490
     * <code>merge(UnifiedArchive $unifiedArchiveInstance)</code>
491
     * This implementation is more intelligent than original' one.
492
     */
493
    public function merge($a)
494
    {
495
        // filename
496
        if (is_string($a)) {
497
            if (($a = UnifiedArchive::open($a)) !== null) {
498
                // ok
499
            } else {
500
                // // unsupported type of archive
501
                return 0;
502
            }
503
        }
504
        // UnifiedArchive instance
505
        else if ($a instanceof UnifiedArchive) {
506
            // go on
507
        }
508
        // invalid argument
509
        else {
510
            return 0;
511
        }
512
513
        $tempDir = tempnam(PCLZIP_TEMPORARY_DIR, 'merging');
514
        if (file_exists($tempDir)) unlink($tempDir);
515
        if (!mkdir($tempDir)) return 0;
516
517
        // go through archive content list and copy all files
518
        foreach ($a->getFileNames() as $filename) {
519
            // dir merging process
520
            if (in_array(substr($filename, -1), array('/', '\\'))) {
521
                $this->archive->addEmptyDir(rtrim($filename, '/\\'));
0 ignored issues
show
Bug introduced by
The method addEmptyDir() does not exist on wapmorgan\UnifiedArchive\UnifiedArchive. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

521
                $this->archive->/** @scrutinizer ignore-call */ 
522
                                addEmptyDir(rtrim($filename, '/\\'));

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
522
            }
523
            // file merging process
524
            else {
525
                // extract file in temporary dir
526
                if ($a->extractNode($tempDir, '/'.$filename)) {
0 ignored issues
show
Bug introduced by
The method extractNode() does not exist on wapmorgan\UnifiedArchive\UnifiedArchive. Did you maybe mean extractFiles()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

526
                if ($a->/** @scrutinizer ignore-call */ extractNode($tempDir, '/'.$filename)) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
527
                    // go on
528
                } else {
529
                    // extraction fails
530
                    return 0;
531
                }
532
                // add file in archive
533
                if ($this->archive->addFile($tempDir.'/'.$filename,
534
                    $filename)) {
535
                    // ok
536
                } else {
537
                    return 0;
538
                }
539
            }
540
        }
541
542
        call_user_func(function ($directory) {
543
            foreach (glob($directory.'/*') as $f) {
544
                if (is_dir($f)) call_user_func(__FUNCTION__, $f);
545
                else unlink($f);
546
            }
547
        }, $tempDir);
548
549
        return 1;
550
    }
551
552
    /**
553
     * Duplicates archive
554
     * @param $clone_filename
555
     * @return int
556
     */
557
    public function duplicate($clone_filename)
558
    {
559
        return copy($this->archive->filename, $clone_filename) ? 1 : 0;
0 ignored issues
show
Bug introduced by
The property filename does not seem to exist on wapmorgan\UnifiedArchive\UnifiedArchive.
Loading history...
560
    }
561
562
    /**
563
     * @param array $options
564
     * @return array
565
     */
566
    public function createFilters(array $options)
567
    {
568
        $filters = array();
569
        if (isset($options[PCLZIP_OPT_REMOVE_PATH])
570
            && !isset($options[PCLZIP_OPT_REMOVE_ALL_PATH]))
571
            $filters[] = function (&$key, &$value) use ($options) {
0 ignored issues
show
Unused Code introduced by
The import $options is not used and could be removed.

This check looks for imports that have been defined, but are not used in the scope.

Loading history...
Unused Code introduced by
The parameter $value is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

571
            $filters[] = function (&$key, /** @scrutinizer ignore-unused */ &$value) use ($options) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
572
                $key = str_replace($key, null, $key);
573
            };
574
        if (isset($options[PCLZIP_OPT_REMOVE_ALL_PATH]))
575
            $filters[] = function (&$key, &$value) {
0 ignored issues
show
Unused Code introduced by
The parameter $value is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

575
            $filters[] = function (&$key, /** @scrutinizer ignore-unused */ &$value) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
576
                $key = basename($key);
577
            };
578
        if (isset($options[PCLZIP_OPT_ADD_PATH]))
579
            $filters[] = function (&$key, &$value) use ($options) {
0 ignored issues
show
Unused Code introduced by
The parameter $value is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

579
            $filters[] = function (&$key, /** @scrutinizer ignore-unused */ &$value) use ($options) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
580
                $key = rtrim($options[PCLZIP_OPT_ADD_PATH], '/') . '/' .
581
                    ltrim($key, '/');
582
            };
583
        return $filters;
584
    }
585
586
    /**
587
     * @param array $options
588
     * @param string $preCallbackConst
589
     * @param string $postCallbackConst
590
     * @return callable[]|\Closure[]
591
     */
592
    private function extractCallbacks(array $options, $preCallbackConst, $postCallbackConst)
593
    {
594
        $preCallback = $postCallback = function () { return true; };
595
596
        if (isset($options[$preCallbackConst]) && is_callable($options[$preCallbackConst]))
597
            $preCallback = $options[$preCallbackConst];
598
599
        if (isset($options[$postCallbackConst]) && is_callable($options[$postCallbackConst]))
600
            $postCallback = $options[$postCallbackConst];
601
602
        return [$preCallback, $postCallback];
603
    }
604
605
    /**
606
     * @param array $options
607
     * @return array
608
     */
609
    private function makeKeyValueArrayFromList(array $options)
610
    {
611
        $keys = array_filter($options, function ($v) {return ($v%2) == 0;}, ARRAY_FILTER_USE_KEY);
612
        $values = array_filter($options, function ($v) {return ($v%2) == 1;}, ARRAY_FILTER_USE_KEY);
613
        if (count($values) < count($keys)) $values[] = true;
614
        return array_combine($keys, $values);
615
    }
616
617
    /**
618
     * @param array $pathsList
619
     * @param array $filters
620
     * @param callable $preAddCallback
621
     * @param callable $postAddCallback
622
     * @return array
623
     */
624
    public function addSnippets(array $pathsList, array $filters, callable $preAddCallback, callable $postAddCallback)
625
    {
626
        $report = [];
627
628
        foreach ($pathsList as $file_to_add) {
629
            $report[] = $this->addSnippet($file_to_add, $filters,
630
                $preAddCallback, $postAddCallback);
631
632
            // additional dir contents
633
            if (is_dir($file_to_add)) {
634
                $directory_contents = new \RecursiveIteratorIterator(
635
                    new \RecursiveDirectoryIterator(
636
                        $file_to_add, \RecursiveDirectoryIterator::SKIP_DOTS),
637
                    RecursiveIteratorIterator::SELF_FIRST);
638
                foreach ($directory_contents as $indir_file_to_add) {
639
                    $report[] = $this->addSnippet($indir_file_to_add, $filters,
640
                        $preAddCallback, $postAddCallback);
641
                }
642
            }
643
        }
644
        return $report;
645
    }
646
647
    /**
648
     * @param array $indexes
649
     * @return \Closure
650
     */
651
    protected function createByIndexSelector(array $indexes)
652
    {
653
        $allowedIndexes = array();
654
        foreach ($indexes as $rule) {
655
            $parts = explode('-', $rule);
656
            if (count($parts) == 1) $allowedIndexes[] = $rule;
657
            else $allowedIndexes = array_merge(
658
                range($parts[0], $parts[1]), $allowedIndexes);
659
        }
660
661
        return function ($key, $value, $index) use ($allowedIndexes) {
662
            return in_array($index, $allowedIndexes)
663
                ? self::SELECT_FILTER_PASS
664
                : self::SELECT_FILTER_REFUSE;
665
        };
666
    }
667
668
    /**
669
     * @param string|array $names
670
     * @return \Closure
671
     */
672
    protected function createByNameSelector($names)
673
    {
674
        $allowedNames = is_array($names)
675
            ? $names
676
            : explode(',', $names);
677
678
        return function ($key, $value) use ($allowedNames) {
0 ignored issues
show
Unused Code introduced by
The parameter $value is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

678
        return function ($key, /** @scrutinizer ignore-unused */ $value) use ($allowedNames) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
679
            foreach ($allowedNames as $name) {
680
                // select directory with nested files
681
                if (in_array(substr($name, -1), ['/', '\\'])) {
682
                    if (strncasecmp($name, $key, strlen($name)) === 0) {
683
                        // that's a file inside a dir or that dir
684
                        return self::SELECT_FILTER_PASS;
685
                    }
686
                } else {
687
                    // select exact name only
688
                    if (strcasecmp($name, $key) === 0) {
689
                        // that's a file with this name
690
                        return self::SELECT_FILTER_PASS;
691
                    }
692
                }
693
            }
694
695
            // that file is not in allowed list
696
            return self::SELECT_FILTER_REFUSE;
697
        };
698
    }
699
700
    /**
701
     * @param string $regex
702
     * @return \Closure
703
     */
704
    protected function createByEregSelector($regex)
705
    {
706
        return function ($key, $value) use ($regex) {
0 ignored issues
show
Unused Code introduced by
The parameter $value is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

706
        return function ($key, /** @scrutinizer ignore-unused */ $value) use ($regex) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
707
            return (ereg($regex, $key) !== false)
708
                ? self::SELECT_FILTER_PASS
709
                : self::SELECT_FILTER_REFUSE;
710
        };
711
    }
712
713
    /**
714
     * @param $regex
715
     * @return \Closure
716
     */
717
    protected function createByPregSelector($regex)
718
    {
719
        return function ($key, $value) use ($regex) {
0 ignored issues
show
Unused Code introduced by
The parameter $value is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

719
        return function ($key, /** @scrutinizer ignore-unused */ $value) use ($regex) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
720
            return preg_match($regex, $key)
721
                ? self::SELECT_FILTER_PASS
722
                : self::SELECT_FILTER_REFUSE;
723
        };
724
    }
725
726
    /**
727
     * @param array $options
728
     * @return callable
729
     */
730
    protected function createSelector(array $options)
731
    {
732
        // exact matching
733
        if (isset($options[PCLZIP_OPT_BY_NAME]))
734
            $selectFilter = $this->createByNameSelector($options[PCLZIP_OPT_BY_NAME]);
735
        // <ereg> rule
736
        else if (isset($options[PCLZIP_OPT_BY_EREG]) && function_exists('ereg'))
737
            $selectFilter = $this->createByEregSelector($options[PCLZIP_OPT_BY_EREG]);
738
        // <preg_match> rule
739
        else if (isset($options[PCLZIP_OPT_BY_PREG]))
740
            $selectFilter = $this->createByPregSelector($options[PCLZIP_OPT_BY_PREG]);
741
        // index rule
742
        else if (isset($options[PCLZIP_OPT_BY_INDEX]))
743
            $selectFilter = $this->createByIndexSelector($options[PCLZIP_OPT_BY_INDEX]);
744
        // no rule
745
        else
746
            $selectFilter = function () {
747
                return self::SELECT_FILTER_PASS;
748
            };
749
        return $selectFilter;
750
    }
751
752
    /**
753
     * @param array $args
754
     * @return array
755
     */
756
    protected function makeOptionsFromArguments(array $args)
757
    {
758
        array_shift($args);
759
760
        // parse options
761
        if (isset($args[0]) && is_string($args[0])) {
762
            $options = [
763
                PCLZIP_OPT_ADD_PATH => $args[0]
764
            ];
765
766
            if (isset($args[1]) && is_string($args[1])) {
767
                $options[PCLZIP_OPT_REMOVE_PATH] = $args[1];
768
            }
769
        } else {
770
            $options = $this->makeKeyValueArrayFromList($args);
771
        }
772
        return $options;
773
    }
774
775
    /**
776
     * @param array $options
777
     * @param string|null $currentComment
778
     * @return mixed|string|null
779
     */
780
    protected function buildComment(array $options, $currentComment)
781
    {
782
        $comment = null;
783
        if (isset($options[PCLZIP_OPT_COMMENT]))
784
            $comment = $options[PCLZIP_OPT_COMMENT];
785
        else if (isset($options[PCLZIP_OPT_ADD_COMMENT])) {;
786
            $comment = $currentComment . $options[PCLZIP_OPT_ADD_COMMENT];
787
        } else if (isset($options[PCLZIP_OPT_PREPEND_COMMENT])) {
788
            $comment = $options[PCLZIP_OPT_PREPEND_COMMENT] . $currentComment;
789
        }
790
        return $comment;
791
    }
792
}
793