Passed
Push — 1.1.x ( bc52f7...e8e75c )
by f
12:04
created

PclZipInterface::extract()   F

Complexity

Conditions 36
Paths 15120

Size

Total Lines 178
Code Lines 95

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 36
eloc 95
nc 15120
nop 0
dl 0
loc 178
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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->addFiles([
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->getFileNames() 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->deleteFiles($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->getFileNames() as $filename) {
524
            // dir merging process
525
            if (in_array(substr($filename, -1), array('/', '\\'))) {
526
                $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

526
                $this->archive->/** @scrutinizer ignore-call */ 
527
                                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...
527
            }
528
            // file merging process
529
            else {
530
                // extract file in temporary dir
531
                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

531
                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...
532
                    // go on
533
                } else {
534
                    // extraction fails
535
                    return 0;
536
                }
537
                // add file in archive
538
                if ($this->archive->addFile($tempDir.'/'.$filename,
539
                    $filename)) {
540
                    // ok
541
                } else {
542
                    return 0;
543
                }
544
            }
545
        }
546
547
        call_user_func(function ($directory) {
548
            foreach (glob($directory.'/*') as $f) {
549
                if (is_dir($f)) call_user_func(__FUNCTION__, $f);
550
                else unlink($f);
551
            }
552
        }, $tempDir);
553
554
        return 1;
555
    }
556
557
    /**
558
     * Duplicates archive
559
     * @param $clone_filename
560
     * @return int
561
     */
562
    public function duplicate($clone_filename)
563
    {
564
        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...
565
    }
566
567
    /**
568
     * @param array $options
569
     * @return array
570
     */
571
    public function createFilters(array $options)
572
    {
573
        $filters = array();
574
        if (isset($options[PCLZIP_OPT_REMOVE_PATH])
575
            && !isset($options[PCLZIP_OPT_REMOVE_ALL_PATH]))
576
            $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

576
            $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...
577
                $key = str_replace($key, null, $key);
578
            };
579
        if (isset($options[PCLZIP_OPT_REMOVE_ALL_PATH]))
580
            $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

580
            $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...
581
                $key = basename($key);
582
            };
583
        if (isset($options[PCLZIP_OPT_ADD_PATH]))
584
            $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

584
            $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...
585
                $key = rtrim($options[PCLZIP_OPT_ADD_PATH], '/') . '/' .
586
                    ltrim($key, '/');
587
            };
588
        return $filters;
589
    }
590
591
    /**
592
     * @param array $options
593
     * @param string $preCallbackConst
594
     * @param string $postCallbackConst
595
     * @return callable[]|\Closure[]
596
     */
597
    private function extractCallbacks(array $options, $preCallbackConst, $postCallbackConst)
598
    {
599
        $preCallback = $postCallback = function () { return true; };
600
601
        if (isset($options[$preCallbackConst]) && is_callable($options[$preCallbackConst]))
602
            $preCallback = $options[$preCallbackConst];
603
604
        if (isset($options[$postCallbackConst]) && is_callable($options[$postCallbackConst]))
605
            $postCallback = $options[$postCallbackConst];
606
607
        return [$preCallback, $postCallback];
608
    }
609
610
    /**
611
     * @param array $options
612
     * @return array
613
     */
614
    private function makeKeyValueArrayFromList(array $options)
615
    {
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) {
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

683
        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...
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) {
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

711
        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...
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) {
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

724
        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...
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