PclZipInterface::extract()   F
last analyzed

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->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(),
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...
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())))
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...
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)) {
0 ignored issues
show
Bug introduced by
The method extractNode() does not exist on wapmorgan\UnifiedArchive\UnifiedArchive. Did you maybe mean extract()? ( 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->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;
0 ignored issues
show
Bug introduced by
The property filename does not seem to exist on wapmorgan\UnifiedArchive\UnifiedArchive.
Loading history...
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) {
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) 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...
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...
576
                $key = str_replace($key, null, $key);
577
            };
578
        if (isset($options[PCLZIP_OPT_REMOVE_ALL_PATH]))
579
            $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

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

583
            $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...
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) {
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