Passed
Push — 0.1.x ( 3e3fb7...4a45f6 )
by f
02:35
created

PclzipZipInterface::create()   F

Complexity

Conditions 18
Paths 1536

Size

Total Lines 69
Code Lines 47

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 18
eloc 47
nc 1536
nop 1
dl 0
loc 69
rs 0.7
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
namespace wapmorgan\UnifiedArchive;
3
4
if (!defined('PCLZIP_ERR_NO_ERROR')) {
5
    // ----- Constants
6
    if (!defined('PCLZIP_READ_BLOCK_SIZE')) {
7
        define('PCLZIP_READ_BLOCK_SIZE', 2048);
8
    }
9
    if (!defined('PCLZIP_SEPARATOR')) {
10
        define('PCLZIP_SEPARATOR', ',');
11
    }
12
    if (!defined('PCLZIP_ERROR_EXTERNAL')) {
13
        define('PCLZIP_ERROR_EXTERNAL', 0);
14
    }
15
    if (!defined('PCLZIP_TEMPORARY_DIR')) {
16
        define('PCLZIP_TEMPORARY_DIR', sys_get_temp_dir());
17
    }
18
19
    define('PCLZIP_ERR_USER_ABORTED', 2);
20
    define('PCLZIP_ERR_NO_ERROR', 0);
21
    define('PCLZIP_ERR_WRITE_OPEN_FAIL', -1);
22
    define('PCLZIP_ERR_READ_OPEN_FAIL', -2);
23
    define('PCLZIP_ERR_INVALID_PARAMETER', -3);
24
    define('PCLZIP_ERR_MISSING_FILE', -4);
25
    define('PCLZIP_ERR_FILENAME_TOO_LONG', -5);
26
    define('PCLZIP_ERR_INVALID_ZIP', -6);
27
    define('PCLZIP_ERR_BAD_EXTRACTED_FILE', -7);
28
    define('PCLZIP_ERR_DIR_CREATE_FAIL', -8);
29
    define('PCLZIP_ERR_BAD_EXTENSION', -9);
30
    define('PCLZIP_ERR_BAD_FORMAT', -10);
31
    define('PCLZIP_ERR_DELETE_FILE_FAIL', -11);
32
    define('PCLZIP_ERR_RENAME_FILE_FAIL', -12);
33
    define('PCLZIP_ERR_BAD_CHECKSUM', -13);
34
    define('PCLZIP_ERR_INVALID_ARCHIVE_ZIP', -14);
35
    define('PCLZIP_ERR_MISSING_OPTION_VALUE', -15);
36
    define('PCLZIP_ERR_INVALID_OPTION_VALUE', -16);
37
    define('PCLZIP_ERR_ALREADY_A_DIRECTORY', -17);
38
    define('PCLZIP_ERR_UNSUPPORTED_COMPRESSION', -18);
39
    define('PCLZIP_ERR_UNSUPPORTED_ENCRYPTION', -19);
40
    define('PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE', -20);
41
    define('PCLZIP_ERR_DIRECTORY_RESTRICTION', -21);
42
43
    // ----- Options values
44
    define('PCLZIP_OPT_PATH', 77001);
45
    define('PCLZIP_OPT_ADD_PATH', 77002);
46
    define('PCLZIP_OPT_REMOVE_PATH', 77003);
47
    define('PCLZIP_OPT_REMOVE_ALL_PATH', 77004);
48
    define('PCLZIP_OPT_SET_CHMOD', 77005);
49
    define('PCLZIP_OPT_EXTRACT_AS_STRING', 77006);
50
    define('PCLZIP_OPT_NO_COMPRESSION', 77007);
51
    define('PCLZIP_OPT_BY_NAME', 77008);
52
    define('PCLZIP_OPT_BY_INDEX', 77009);
53
    define('PCLZIP_OPT_BY_EREG', 77010);
54
    define('PCLZIP_OPT_BY_PREG', 77011);
55
    define('PCLZIP_OPT_COMMENT', 77012);
56
    define('PCLZIP_OPT_ADD_COMMENT', 77013);
57
    define('PCLZIP_OPT_PREPEND_COMMENT', 77014);
58
    define('PCLZIP_OPT_EXTRACT_IN_OUTPUT', 77015);
59
    define('PCLZIP_OPT_REPLACE_NEWER', 77016);
60
    define('PCLZIP_OPT_STOP_ON_ERROR', 77017);
61
    // Having big trouble with crypt. Need to multiply 2 long int
62
    // which is not correctly supported by PHP ...
63
    //define( 'PCLZIP_OPT_CRYPT', 77018 );
64
    define('PCLZIP_OPT_EXTRACT_DIR_RESTRICTION', 77019);
65
    define('PCLZIP_OPT_TEMP_FILE_THRESHOLD', 77020);
66
    define('PCLZIP_OPT_ADD_TEMP_FILE_THRESHOLD', 77020); // alias
67
    define('PCLZIP_OPT_TEMP_FILE_ON', 77021);
68
    define('PCLZIP_OPT_ADD_TEMP_FILE_ON', 77021); // alias
69
    define('PCLZIP_OPT_TEMP_FILE_OFF', 77022);
70
    define('PCLZIP_OPT_ADD_TEMP_FILE_OFF', 77022); // alias
71
72
    // ----- File description attributes
73
    define('PCLZIP_ATT_FILE_NAME', 79001);
74
    define('PCLZIP_ATT_FILE_NEW_SHORT_NAME', 79002);
75
    define('PCLZIP_ATT_FILE_NEW_FULL_NAME', 79003);
76
    define('PCLZIP_ATT_FILE_MTIME', 79004);
77
    define('PCLZIP_ATT_FILE_CONTENT', 79005);
78
    define('PCLZIP_ATT_FILE_COMMENT', 79006);
79
80
    // ----- Call backs values
81
    define('PCLZIP_CB_PRE_EXTRACT', 78001);
82
    define('PCLZIP_CB_POST_EXTRACT', 78002);
83
    define('PCLZIP_CB_PRE_ADD', 78003);
84
    define('PCLZIP_CB_POST_ADD', 78004);
85
}
86
87
class PclzipZipInterface
88
{
89
    const SELECT_FILTER_PASS = 1;
90
    const SELECT_FILTER_REFUSE = 0;
91
92
    const AVERAGE_ZIP_COMPRESSION_RATIO = 2;
93
94
    private $archive;
95
96
    /**
97
     * PclzipZipInterface constructor.
98
     *
99
     * @param \ZipArchive $archive
100
     */
101
    public function __construct(\ZipArchive $archive)
102
    {
103
        $this->archive = $archive;
104
    }
105
106
    /**
107
     * @param $localname
108
     * @param $filename
109
     *
110
     * @return object
111
     */
112
    public function createFileHeader($localname, $filename)
113
    {
114
        return (object) array(
115
            'filename' => $filename,
116
            'stored_filename' => $localname,
117
            'size' => filesize($filename),
118
            'compressed_size' => ceil(filesize($filename)
119
                / self::AVERAGE_ZIP_COMPRESSION_RATIO),
120
            'mtime' => filemtime($filename),
121
            'comment' => null,
122
            'folder' => is_dir($filename),
123
            'status' => 'ok',
124
        );
125
    }
126
127
    /**
128
     * Creates a new archive
129
     * Two ways of usage:
130
     * <code>create($content, [$addDir, [$removeDir]])</code>
131
     * <code>create($content, [... options ...]])</code>
132
     */
133
    public function create($content)
134
    {
135
        if (is_array($content)) $paths_list = $content;
136
        else $paths_list = explode(',', $content);
137
        $report = array();
138
139
        $options = func_get_args();
140
        array_shift($options);
141
142
        // parse options
143
        if (isset($options[0]) && is_string($options[0])) {
144
            $options[PCLZIP_OPT_ADD_PATH] = $options[0];
145
            if (isset($options[1]) && is_string($options[1])) {
146
                $options[PCLZIP_OPT_REMOVE_PATH] = $options[1];
147
            }
148
        } else {
149
            $options = array_combine(
150
                array_filter($options, function ($v) {return (bool) $v&2;}),
151
                array_filter($options, function ($v) {return (bool) ($v-1)&2;})
152
            );
153
        }
154
155
        // filters initiation
156
        $filters = array();
157
        if (isset($options[PCLZIP_OPT_REMOVE_PATH])
158
            && !isset($options[PCLZIP_OPT_REMOVE_ALL_PATH]))
159
            $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

159
            $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...
160
                $key = str_replace($key, null, $key); };
161
        if (isset($options[PCLZIP_OPT_REMOVE_ALL_PATH]))
162
            $filters[] = function (&$key, &$value) { $key = basename($key); };
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

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

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...
163
        if (isset($options[PCLZIP_OPT_ADD_PATH]))
164
            $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

164
            $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...
165
                $key = rtrim($options[PCLZIP_OPT_ADD_PATH], '/').'/'.
166
                    ltrim($key, '/');
167
            };
168
169
        if (isset($options[PCLZIP_CB_PRE_ADD])
170
            && is_callable($options[PCLZIP_CB_PRE_ADD]))
171
            $preAddCallback = $options[PCLZIP_CB_PRE_ADD];
172
        else $preAddCallback = function () { return 1; };
173
174
        if (isset($options[PCLZIP_CB_POST_ADD])
175
            && is_callable($options[PCLZIP_CB_POST_ADD]))
176
            $postAddCallback = $options[PCLZIP_CB_POST_ADD];
177
        else $postAddCallback = function () { return 1; };
178
179
        if (isset($options[PCLZIP_OPT_COMMENT]))
180
            $this->archive->setArchiveComment($options[PCLZIP_OPT_COMMENT]);
181
182
        // scan filesystem for files list
183
        $files_list = array();
0 ignored issues
show
Unused Code introduced by
The assignment to $files_list is dead and can be removed.
Loading history...
184
        foreach ($content as $file_to_add) {
185
            $report[] = $this->addSnippet($file_to_add, $filters,
186
                $preAddCallback, $postAddCallback);
187
188
            // additional dir contents
189
            if (is_dir($file_to_add)) {
190
                $directory_contents = new \RecursiveIteratorIterator(
191
                    new \RecursiveDirectoryIterator(
192
                        $file_to_add, \RecursiveDirectoryIterator::SKIP_DOTS),
193
                    RecursiveIteratorIterator::SELF_FIRST);
0 ignored issues
show
Bug introduced by
The type wapmorgan\UnifiedArchive\RecursiveIteratorIterator was not found. Did you mean RecursiveIteratorIterator? If so, make sure to prefix the type with \.
Loading history...
194
                foreach ($directory_contents as $file_to_add) {
0 ignored issues
show
Comprehensibility Bug introduced by
$file_to_add is overwriting a variable from outer foreach loop.
Loading history...
195
                    $report[] = $this->addSnippet($file_to_add, $filters,
196
                        $preAddCallback, $postAddCallback);
197
                }
198
            }
199
        }
200
        // ...
201
        return $report;
202
    }
203
204
    private function addSnippet($file_to_add, array $filters, $preAddCallback,
205
        $postAddCallback)
0 ignored issues
show
Unused Code introduced by
The parameter $postAddCallback 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

205
        /** @scrutinizer ignore-unused */ $postAddCallback)

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...
206
    {
207
        if (is_file($file_to_add) || is_dir($file_to_add)) {
208
            // apply filters to a file
209
            $localname = $file_to_add;
210
            $filename = $file_to_add;
211
            foreach ($filters as $filter)
212
                call_user_func($filter, $localname, $filename);
213
            $file_header = $this->createFileHeader($localname, $filename);
214
            if (call_user_func($preAddCallback, $file_header) == 1) {
215
                //
216
                // Check for max length > 255
217
                //
218
                if (strlen(basename($file_header->stored_filename)) > 255)
219
                    $file_header->status = 'filename_too_long';
220
                if (is_file($filename))
221
                    $this->archive->addFile($file_header->filename,
222
                        $file_header->stored_filename);
223
                else if (is_dir($filename))
224
                    $this->archive->addEmptyDir($file_header->stored_filename);
225
            } else {
226
                //
227
                // File was skipped
228
                //
229
                $file_header->status = 'skipped';
230
            }
231
232
            return $file_header;
233
        }
234
    }
235
236
    /**
237
     * Lists archive content
238
     */
239
    public function listContent()
240
    {
241
        $filesList = array();
242
        $numFiles = $this->archive->numFiles;
243
        for ($i = 0; $i < $numFiles; $i++) {
244
            $statIndex = $this->archive->statIndex($i);
245
            $filesList[] = (object) array(
246
                'filename' => $statIndex['name'],
247
                'stored_filename' => $statIndex['name'],
248
                'size' => $statIndex['size'],
249
                'compressed_size' => $statIndex['comp_size'],
250
                'mtime' => $statIndex,
251
                'comment' => ($comment = $this->archive->getCommentIndex
252
                    ($statIndex['index']) !== false) ? $comment : null,
253
                'folder' => in_array(substr($statIndex['name'], -1),
254
                    array('/', '\\')),
255
                'index' => $statIndex['index'],
256
                'status' => 'ok',
257
            );
258
        }
259
260
        return $filesList;
261
    }
262
263
    /**
264
     * Extracts files
265
     * Two ways of usage:
266
     * <code>extract([$extractPath, [$removePath]])</code>
267
     * <code>extract([... options ...]])</code>
268
     */
269
    public function extract()
270
    {
271
        $options = func_get_args();
272
        array_shift($options);
273
274
        // parse options
275
        if (isset($options[0]) && is_string($options[0])) {
276
            $options[PCLZIP_OPT_PATH] = $options[0];
277
            if (isset($options[1]) && is_string($options[1])) {
278
                $options[PCLZIP_OPT_REMOVE_PATH] = $options[1];
279
            }
280
        } else {
281
            $options = array_combine(
282
                array_filter($options, function ($v) {return (bool) $v&2;}),
283
                array_filter($options, function ($v) {return (bool) ($v-1)&2;})
284
            );
285
        }
286
287
        // filters initiation
288
        if (isset($options[PCLZIP_OPT_PATH]))
289
            $extractPath = rtrim($options[PCLZIP_OPT_PATH], '/');
290
        else $extractPath = rtrim(getcwd(), '/');
291
292
        $filters = array();
293
        if (isset($options[PCLZIP_OPT_REMOVE_PATH])
294
            && !isset($options[PCLZIP_OPT_REMOVE_ALL_PATH]))
295
            $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

295
            $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...
296
                $key = str_replace($key, null, $key);
297
            };
298
        if (isset($options[PCLZIP_OPT_REMOVE_ALL_PATH]))
299
            $filters[] = function (&$key, &$value) { $key = basename($key); };
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

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

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...
300
        if (isset($options[PCLZIP_OPT_ADD_PATH]))
301
            $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

301
            $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...
302
                $key = rtrim($options[PCLZIP_OPT_ADD_PATH], '/').'/'.
303
                    ltrim($key, '/');
304
            };
305
306
        if (isset($options[PCLZIP_CB_PRE_EXTRACT])
307
            && is_callable($options[PCLZIP_CB_PRE_EXTRACT]))
308
            $preExtractCallback = $options[PCLZIP_CB_PRE_EXTRACT];
309
        else $preExtractCallback = function () { return 1; };
310
311
        if (isset($options[PCLZIP_CB_POST_EXTRACT])
312
            && is_callable($options[PCLZIP_CB_POST_EXTRACT]))
313
            $postExtractCallback = $options[PCLZIP_CB_POST_EXTRACT];
314
        else $postExtractCallback = function () { return 1; };
315
316
        // exact matching
317
        if (isset($options[PCLZIP_OPT_BY_NAME]))
318
            $selectFilter = 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

318
            $selectFilter = 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...
319
                $allowedNames = is_array($options[PCLZIP_OPT_BY_NAME])
320
                    ? $options[PCLZIP_OPT_BY_NAME]
321
                    : explode(',', $options[PCLZIP_OPT_BY_NAME]);
322
                foreach ($allowedNames as $name) {
323
                    // select directory with nested files
324
                    if (in_array(substr($name, -1), array('/', '\\'))) {
325
                        if (strncasecmp($name, $key, strlen($name)) === 0) {
326
                            // that's a file inside a dir or that dir
327
                            return self::SELECT_FILTER_PASS;
328
                        }
329
                    } else {
330
                        // select exact name only
331
                        if (strcasecmp($name, $key) === 0) {
332
                            // that's a file with this name
333
                            return self::SELECT_FILTER_PASS;
334
                        }
335
                    }
336
                }
337
338
                // that file is not in allowed list
339
                return self::SELECT_FILTER_REFUSE;
340
            };
341
        // <ereg> rule
342
        else if (isset($options[PCLZIP_OPT_BY_EREG]) && function_exists('ereg'))
343
            $selectFilter = 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

343
            $selectFilter = 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...
344
                return (ereg($options[PCLZIP_OPT_BY_EREG], $key) !== false)
0 ignored issues
show
Deprecated Code introduced by
The function ereg() has been deprecated: 5.3.0 Use preg_match() instead ( Ignorable by Annotation )

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

344
                return (/** @scrutinizer ignore-deprecated */ ereg($options[PCLZIP_OPT_BY_EREG], $key) !== false)

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
345
                    ? self::SELECT_FILTER_PASS
346
                    : self::SELECT_FILTER_REFUSE;
347
            };
348
        // <preg_match> rule
349
        else if (isset($options[PCLZIP_OPT_BY_PREG]))
350
            $selectFilter = 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

350
            $selectFilter = 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...
351
                return preg_match($options[PCLZIP_OPT_BY_PREG], $key)
352
                    ? self::SELECT_FILTER_PASS
353
                    : self::SELECT_FILTER_REFUSE;
354
            };
355
        // index rule
356
        else if (isset($options[PCLZIP_OPT_BY_INDEX]))
357
            $selectFilter = function ($key, $value, $index) use ($options) {
358
                $allowedIndexes = array();
359
                foreach ($options[PCLZIP_OPT_BY_INDEX] as $rule) {
360
                    $parts = explode('-', $rule);
361
                    if (count($parts) == 1) $allowedIndexes[] = $rule;
362
                    else $allowedIndexes = array_merge(
363
                        range($parts[0], $parts[1]), $allowedIndexes);
364
                }
365
366
                return in_array($index, $allowedIndexes) ? self::SELECT_FILTER_PASS
367
                    : self::SELECT_FILTER_REFUSE;
368
            };
369
        // no rule
370
        else
371
            $selectFilter = function () { return self::SELECT_FILTER_PASS; };
372
373
        if (isset($options[PCLZIP_OPT_EXTRACT_AS_STRING]))
374
            $anotherOutputFormat = PCLZIP_OPT_EXTRACT_AS_STRING;
375
        else if (isset($options[PCLZIP_OPT_EXTRACT_IN_OUTPUT]))
376
            $anotherOutputFormat = PCLZIP_OPT_EXTRACT_IN_OUTPUT;
377
        else $anotherOutputFormat = false;
378
379
        if (isset($options[PCLZIP_OPT_REPLACE_NEWER]))
380
            $doNotReplaceNewer = false;
381
        else $doNotReplaceNewer = true;
382
383
        if (isset($options[PCLZIP_OPT_EXTRACT_DIR_RESTRICTION]))
384
            $restrictExtractDir = $options[PCLZIP_OPT_EXTRACT_DIR_RESTRICTION];
385
        else $restrictExtractDir = false;
386
387
        $report = array();
388
        foreach ($this->listContent() as $file_header) {
389
            // add file information to report
390
            $report[] = $file_header;
391
            // refuse by select rule
392
            if (call_user_func($selectFilter, $file_header->stored_filename,
393
                    $file_header->filename, $file_header->index)
394
                === self::SELECT_FILTER_REFUSE) {
395
                //
396
                // I don't know need to remain this file in report or not,
397
                // but for now I remove
398
                array_pop($report);
399
                // $file_header->status = 'filtered';
400
                //
401
                continue;
402
            }
403
404
            //
405
            // add extract path in case of extraction
406
            // for some reason need to do it before call pre extract callback
407
            // (pclzip.lib.php v2.8.2, line 3670)
408
            // so I decided to do it here too
409
            //
410
            if ($anotherOutputFormat === false) {
411
                $file_header->filename = realpath($extractPath.'/'.
412
                    $file_header->filename);
413
                //
414
                // check for path correlation with restricted path
415
                //
416
                if ($restrictExtractDir !== false) {
417
                    $filename = $file_header->filename;
418
                    $restrictedDir = realpath($restrictExtractDir);
419
                    if (strncasecmp($restrictedDir, $filename,
420
                            strlen($restrictedDir)) !== 0) {
421
                        // refuse file extraction
422
                        $file_header->status = 'filtered';
423
                        continue;
424
                    }
425
                }
426
            }
427
428
            // apply pre extract callback
429
            $callback_result = call_user_func($preExtractCallback,
430
                $file_header);
431
            if ($callback_result == 1) {
432
                // go on ...
433
            } elseif ($callback_result == 0) {
434
                // skip current file
435
                $file_header->status = 'skipped';
436
                continue;
437
            } elseif ($callback_result == 2) {
438
                // skip & stop extraction
439
                $file_header->status = 'aborted';
440
                break;
441
            }
442
443
            // return content
444
            if ($anotherOutputFormat == PCLZIP_OPT_EXTRACT_AS_STRING) {
445
                $file_header->content
446
                    = $this->archive->getFromName($file_header->stored_filename);
447
            }
448
            // echo content
449
            else if ($anotherOutputFormat == PCLZIP_OPT_EXTRACT_IN_OUTPUT) {
450
                echo $this->archive->getFromName($file_header->stored_filename);
451
            }
452
            // extract content
453
            else if ($anotherOutputFormat === false) {
454
                // apply path filters
455
                foreach ($filters as $filter) call_user_func($filter,
456
                    $file_header->stored_filename, $file_header->filename);
457
                // dir extraction process
458
                if ($file_header->folder) {
459
                    // if dir doesn't exist
460
                    if (!is_dir($file_header->filename)) {
461
                        // try to create folder
462
                        if (!mkdir($file_header)) {
463
                            $file_header->status = 'path_creation_fail';
464
                            continue;
465
                        }
466
                    }
467
                }
468
                // file extraction process
469
                else {
470
                    // check if path is already taken by a folder
471
                    if (is_dir($file_header->filename)) {
472
                        $file_header->status = 'already_a_directory';
473
                        continue;
474
                    }
475
                    // check if file path is not writable
476
                    if (!is_writable($file_header->filename)) {
477
                        $file_header->status = 'write_protected';
478
                        continue;
479
                    }
480
                    // check if file exists and it's newer
481
                    if (is_file($file_header->filename)) {
482
                        if (filemtime($file_header->filename)
483
                            > $file_header->mtime) {
484
                            // skip extraction if option EXTRACT_NEWER isn't set
485
                            if ($doNotReplaceNewer) {
486
                                $file_header->status = 'newer_exist';
487
                                continue;
488
                            }
489
                        }
490
                    }
491
                    $directory = dirname($file_header->filename);
492
                    // check if running process can not create extraction folder
493
                    if (!is_dir($directory)) {
494
                        if (!mkdir($directory)) {
495
                            $file_header->status = 'path_creation_fail';
496
                            continue;
497
                        }
498
                    }
499
                    // extraction
500
                    if (copy("zip://".$this->archive->filename."#"
501
                        .$file_header->stored_filename
502
                        , $file_header->filename)) {
503
                        // ok
504
                    }
505
                    // extraction fails
506
                    else {
507
                        $file_header->status = 'write_error';
508
                        continue;
509
                    }
510
                }
511
            }
512
513
            // apply post extract callback
514
            $callback_result = call_user_func($postExtractCallback,
515
                $file_header);
516
            if ($callback_result == 1) {
517
                // go on
518
            } elseif ($callback_result == 2) {
519
                // skip & stop extraction
520
                break;
521
            }
522
        }
523
524
        return $report;
525
    }
526
527
    /**
528
     * Reads properties of archive
529
     */
530
    public function properties()
531
    {
532
        return array(
533
            'nb' => $this->archive->numFiles,
534
            'comment' =>
535
                (($comment = $this->archive->getArchiveComment() !== false)
536
                    ? $comment : null),
537
            'status' => 'OK',
538
        );
539
    }
540
541
    /**
542
     * Adds files in archive
543
     * <code>add($content, [$addDir, [$removeDir]])</code>
544
     * <code>add($content, [ ... options ... ])</code>
545
     */
546
    public function add($content)
547
    {
548
        if (is_array($content)) $paths_list = $content;
549
        else $paths_list = array_map(explode(',', $content));
0 ignored issues
show
Bug introduced by
The call to array_map() has too few arguments starting with arr1. ( Ignorable by Annotation )

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

549
        else $paths_list = /** @scrutinizer ignore-call */ array_map(explode(',', $content));

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
550
        $report = array();
551
552
        $options = func_get_args();
553
        array_shift($options);
554
555
        // parse options
556
        if (isset($options[0]) && is_string($options[0])) {
557
            $options[PCLZIP_OPT_ADD_PATH] = $options[0];
558
            if (isset($options[1]) && is_string($options[1])) {
559
                $options[PCLZIP_OPT_REMOVE_PATH] = $options[1];
560
            }
561
        } else {
562
            $options = array_combine(
563
                array_filter($options, function ($v) {return (bool) $v&2;}),
564
                array_filter($options, function ($v) {return (bool) ($v-1)&2;})
565
            );
566
        }
567
568
        // filters initiation
569
        $filters = array();
570
        if (isset($options[PCLZIP_OPT_REMOVE_PATH])
571
            && !isset($options[PCLZIP_OPT_REMOVE_ALL_PATH]))
572
            $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

572
            $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...
573
                $key = str_replace($key, null, $key);
574
            };
575
        if (isset($options[PCLZIP_OPT_REMOVE_ALL_PATH]))
576
            $filters[] = function (&$key, &$value) { $key = basename($key); };
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

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

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
        if (isset($options[PCLZIP_OPT_ADD_PATH]))
578
            $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

578
            $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...
579
                $key = rtrim($options[PCLZIP_OPT_ADD_PATH], '/').'/'.
580
                    ltrim($key, '/');
581
            };
582
583
        if (isset($options[PCLZIP_CB_PRE_ADD])
584
            && is_callable($options[PCLZIP_CB_PRE_ADD]))
585
            $preAddCallback = $options[PCLZIP_CB_PRE_ADD];
586
        else $preAddCallback = function () { return 1; };
587
588
        if (isset($options[PCLZIP_CB_POST_ADD])
589
            && is_callable($options[PCLZIP_CB_POST_ADD]))
590
            $postAddCallback = $options[PCLZIP_CB_POST_ADD];
591
        else $postAddCallback = function () { return 1; };
592
593
        if (isset($options[PCLZIP_OPT_COMMENT]))
594
            $this->archive->setArchiveComment($options[PCLZIP_OPT_COMMENT]);
595
        if (isset($options[PCLZIP_OPT_ADD_COMMENT])) {
596
            $comment =
597
                ($comment = $this->archive->getArchiveComment() !== false)
598
                    ? $comment : null;
599
            $this->archive->setArchiveComment(
600
                $comment . $options[PCLZIP_OPT_ADD_COMMENT]);
0 ignored issues
show
Bug introduced by
Are you sure $comment of type null|true can be used in concatenation? ( Ignorable by Annotation )

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

600
                /** @scrutinizer ignore-type */ $comment . $options[PCLZIP_OPT_ADD_COMMENT]);
Loading history...
601
        }
602
        if (isset($options[PCLZIP_OPT_PREPEND_COMMENT])) {
603
            $comment =
604
                ($comment = $this->archive->getArchiveComment() !== false)
605
                    ? $comment : null;
606
            $this->archive->setArchiveComment(
607
                $options[PCLZIP_OPT_PREPEND_COMMENT] . $comment);
608
        }
609
610
611
        // scan filesystem for files list
612
        $files_list = array();
0 ignored issues
show
Unused Code introduced by
The assignment to $files_list is dead and can be removed.
Loading history...
613
        foreach ($content as $file_to_add) {
614
            $report[] = $this->addSnippet($file_to_add, $filters,
615
                $preAddCallback, $postAddCallback);
616
617
            // additional dir contents
618
            if (is_dir($file_to_add)) {
619
                $directory_contents = new \RecursiveIteratorIterator(
620
                    new \RecursiveDirectoryIterator(
621
                        $file_to_add, \RecursiveDirectoryIterator::SKIP_DOTS),
622
                    RecursiveIteratorIterator::SELF_FIRST);
623
                foreach ($directory_contents as $file_to_add) {
0 ignored issues
show
Comprehensibility Bug introduced by
$file_to_add is overwriting a variable from outer foreach loop.
Loading history...
624
                    $report[] = $this->addSnippet($file_to_add, $filters,
625
                        $preAddCallback, $postAddCallback);
626
                }
627
            }
628
        }
629
        // ...
630
        return $report;
631
    }
632
633
    /**
634
     * Removes files from archive
635
     * Usage:
636
     * <code>delete([... options ...])</code>
637
     */
638
    public function delete()
639
    {
640
        $report = array();
641
        $options = func_get_args();
642
        $options = array_combine(
643
            array_filter($options, function ($v) {return (bool) $v&2;}),
644
            array_filter($options, function ($v) {return (bool) ($v-1)&2;})
645
        );
646
647
        // exact matching
648
        if (isset($options[PCLZIP_OPT_BY_NAME]))
649
            $selectFilter = 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

649
            $selectFilter = 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...
650
                $allowedNames = is_array($options[PCLZIP_OPT_BY_NAME])
651
                    ? $options[PCLZIP_OPT_BY_NAME]
652
                    : explode(',', $options[PCLZIP_OPT_BY_NAME]);
653
                foreach ($allowedNames as $name) {
654
                    // select directory with nested files
655
                    if (in_array(substr($name, -1), array('/', '\\'))) {
656
                        if (strncasecmp($name, $key, strlen($name)) === 0) {
657
                            // that's a file inside a dir or that dir
658
                            return self::SELECT_FILTER_PASS;
659
                        }
660
                    } else {
661
                        // select exact name only
662
                        if (strcasecmp($name, $key) === 0) {
663
                            // that's a file with this name
664
                            return self::SELECT_FILTER_PASS;
665
                        }
666
                    }
667
                }
668
669
                // that file is not in allowed list
670
                return self::SELECT_FILTER_REFUSE;
671
            };
672
        // <ereg> rule
673
        else if (isset($options[PCLZIP_OPT_BY_EREG]) && function_exists('ereg'))
674
            $selectFilter = 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

674
            $selectFilter = 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...
675
                return (ereg($options[PCLZIP_OPT_BY_EREG], $key) !== false)
0 ignored issues
show
Deprecated Code introduced by
The function ereg() has been deprecated: 5.3.0 Use preg_match() instead ( Ignorable by Annotation )

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

675
                return (/** @scrutinizer ignore-deprecated */ ereg($options[PCLZIP_OPT_BY_EREG], $key) !== false)

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
676
                    ? self::SELECT_FILTER_PASS
677
                    : self::SELECT_FILTER_REFUSE;
678
            };
679
        // <preg_match> rule
680
        else if (isset($options[PCLZIP_OPT_BY_PREG]))
681
            $selectFilter = 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

681
            $selectFilter = 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...
682
                return preg_match($options[PCLZIP_OPT_BY_PREG], $key)
683
                    ? self::SELECT_FILTER_PASS
684
                    : self::SELECT_FILTER_REFUSE;
685
            };
686
        // index rule
687
        else if (isset($options[PCLZIP_OPT_BY_INDEX]))
688
            $selectFilter = function ($key, $value, $index) use ($options) {
689
                $allowedIndexes = array();
690
                foreach ($options[PCLZIP_OPT_BY_INDEX] as $rule) {
691
                    $parts = explode('-', $rule);
692
                    if (count($parts) == 1) $allowedIndexes[] = $rule;
693
                    else $allowedIndexes = array_merge(
694
                        range($parts[0], $parts[1]), $allowedIndexes);
695
                }
696
697
                return in_array($index, $allowedIndexes)
698
                    ? self::SELECT_FILTER_PASS
699
                    : self::SELECT_FILTER_REFUSE;
700
            };
701
        // no rule
702
        else
703
            $selectFilter = function () { return self::SELECT_FILTER_PASS; };
704
705
        foreach ($this->listContent() as $file_header) {
706
            // select by select rule
707
            if (call_user_func($selectFilter, $file_header->stored_filename,
708
                    $file_header->filename, $file_header->index)
709
                === self::SELECT_FILTER_REFUSE) {
710
                // delete file from archive
711
                if ($this->archive->deleteName($file_header->stored_filename)) {
712
                    // ok
713
                    continue;
714
                }
715
                // deletion fails
716
                else {
717
                    return 0;
718
                }
719
            }
720
            // unselected file add in report
721
            $report[] = $file_header;
722
        }
723
724
        return $report;
725
    }
726
727
    /**
728
     * Merges given archive into current archive
729
     * Two ways of usage:
730
     * <code>merge($filename)</code>
731
     * <code>merge(UnifiedArchive $unifiedArchiveInstance)</code>
732
     * This implementation is more intelligent than original' one.
733
     */
734
    public function merge($a)
735
    {
736
        // filename
737
        if (is_string($a)) {
738
            if ($a = UnifiedArchive::open($a) !== null) {
739
                // ok
740
            } else {
741
                // // unsupported type of archive
742
                return 0;
743
            }
744
        }
745
        // UnifiedArchive instance
746
        else if ($a instanceof UnifiedArchive) {
747
            // go on
748
        }
749
        // invalid argument
750
        else {
751
            return 0;
752
        }
753
754
        $tempDir = tempnam(PCLZIP_TEMPORARY_DIR, 'merging');
755
        if (file_exists($tempDir)) unlink($tempDir);
756
        if (!mkdir($tempDir)) return 0;
757
758
        // go through archive content list and copy all files
759
        foreach ($a->getFileNames() as $filename) {
760
            // dir merging process
761
            if (in_array(substr($filename, -1), array('/', '\\'))) {
762
                $this->archive->addEmptyDir(rtrim($filename, '/\\'));
763
            }
764
            // file merging process
765
            else {
766
                // extract file in temporary dir
767
                if ($a->extractNode($tempDir, '/'.$filename)) {
768
                    // go on
769
                } else {
770
                    // extraction fails
771
                    return 0;
772
                }
773
                // add file in archive
774
                if ($this->archive->addFile($tempDir.'/'.$filename,
775
                    $filename)) {
776
                    // ok
777
                } else {
778
                    return 0;
779
                }
780
            }
781
        }
782
783
        call_user_func(function ($directory) {
784
            foreach (glob($directory.'/*') as $f) {
785
                if (is_dir($f)) call_user_func(__FUNCTION__, $f);
786
                else unlink($f);
787
            }
788
        }, $tempDir);
789
790
        return 1;
791
    }
792
793
    /**
794
     * Duplicates archive
795
     */
796
    public function duplicate($clone_filename)
797
    {
798
        return copy($this->archive->filename, $clone_filename) ? 1 : 0;
799
    }
800
}