Passed
Push — 0.1.x ( 72f342...82f677 )
by f
01:42
created

TarArchive::scanArchive()   B

Complexity

Conditions 6
Paths 8

Size

Total Lines 29
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 22
nc 8
nop 0
dl 0
loc 29
rs 8.9457
c 0
b 0
f 0
1
<?php
2
namespace wapmorgan\UnifiedArchive;
3
4
use Archive_Tar;
0 ignored issues
show
Bug introduced by
The type Archive_Tar was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
5
use Exception;
6
use FilesystemIterator;
7
use Phar;
8
use PharData;
9
use RecursiveIteratorIterator;
10
11
class TarArchive extends BasicArchive
12
{
13
    const TAR = 'tar';
14
    const TAR_GZIP = 'tgz';
15
    const TAR_BZIP = 'tbz2';
16
    const TAR_LZMA = 'txz';
17
    const TAR_LZW = 'tar.z';
18
19
    /** @var string */
20
    protected $path;
21
22
    /** @var Archive_Tar|PharData */
23
    protected $tar;
24
25
    /** @var bool */
26
    static protected $enabledPearTar;
27
28
    /** @var bool */
29
    static protected $enabledPharData;
30
31
    /** @var int */
32
    protected $numberOfFiles;
33
34
    /** @var array */
35
    protected $files;
36
37
    /** @var int */
38
    protected $uncompressedFilesSize;
39
40
    /** @var int */
41
    protected $compressedFilesSize;
42
43
    /** @var int */
44
    protected $archiveSize;
45
46
    /** @var float */
47
    protected $compressionRatio;
48
49
    const PHAR_FLAGS = FilesystemIterator::UNIX_PATHS;
50
51
    /**
52
     * @param $fileName
53
     * @return null|TarArchive
54
     * @throws Exception
55
     */
56
    public static function open($fileName)
57
    {
58
        self::checkRequirements();
0 ignored issues
show
Bug introduced by
The method checkRequirements() does not exist on wapmorgan\UnifiedArchive\TarArchive. ( Ignorable by Annotation )

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

58
        self::/** @scrutinizer ignore-call */ 
59
              checkRequirements();

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...
59
60
        if (!file_exists($fileName) || !is_readable($fileName))
61
            throw new Exception('Count not open file: '.$fileName);
62
63
        $type = self::detectArchiveType($fileName);
0 ignored issues
show
Bug introduced by
The method detectArchiveType() does not exist on wapmorgan\UnifiedArchive\TarArchive. ( Ignorable by Annotation )

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

63
        /** @scrutinizer ignore-call */ 
64
        $type = self::detectArchiveType($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...
64
        if (!self::canOpenType($type)) {
0 ignored issues
show
Bug introduced by
The method canOpenType() does not exist on wapmorgan\UnifiedArchive\TarArchive. ( Ignorable by Annotation )

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

64
        if (!self::/** @scrutinizer ignore-call */ canOpenType($type)) {

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...
65
            return null;
66
        }
67
68
        return new self($fileName, $type);
69
    }
70
71
    /**
72
     * TarArchive constructor.
73
     * @param $fileName
74
     * @param null $type
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $type is correct as it would always require null to be passed?
Loading history...
75
     * @throws Exception
76
     */
77
    public function __construct($fileName, $type = null)
78
    {
79
        self::checkRequirements();
80
81
        $this->path = realpath($fileName);
82
83
        if (!self::$enabledPharData && !self::$enabledPearTar)
84
            throw new Exception('Archive_Tar nor PharData not available');
85
86
        $this->openArchive($type);
87
        $this->scanArchive();
88
    }
89
90
    /**
91
     * Destructor
92
     */
93
    public function __destruct()
94
    {
95
        $this->tar = null;
96
    }
97
98
    /**
99
     * @return array
100
     */
101
    public function getFileNames()
102
    {
103
        return $this->files;
104
    }
105
106
107
108
    /**
109
     * @param $fileName
110
     * @return bool|ArchiveEntry
111
     */
112
    public function getFileData($fileName)
113
    {
114
        if (!in_array($fileName, $this->files, true))
115
            return false;
116
117
        if ($this->tar instanceof Archive_Tar) {
118
            $index = array_search($fileName, $this->files, true);
119
120
            $Content = $this->tar->listContent();
0 ignored issues
show
Bug introduced by
The method listContent() does not exist on PharData. ( Ignorable by Annotation )

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

120
            /** @scrutinizer ignore-call */ 
121
            $Content = $this->tar->listContent();

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...
121
            $data = $Content[$index];
122
            unset($Content);
123
124
            return new ArchiveEntry($fileName, $data['size'] / $this->tarCompressionRatio,
0 ignored issues
show
Bug Best Practice introduced by
The property tarCompressionRatio does not exist on wapmorgan\UnifiedArchive\TarArchive. Did you maybe forget to declare it?
Loading history...
125
                $data['size'], $data['mtime'], in_array(strtolower(pathinfo($this->tar->path,
0 ignored issues
show
Bug introduced by
The property path does not seem to exist on PharData.
Loading history...
126
                    PATHINFO_EXTENSION)), array('gz', 'bz2', 'xz', 'Z')));
127
        } else {
128
            /** @var \PharFileInfo $entry_info */
129
            $entry_info = $this->tar[$fileName];
130
            return new ArchiveEntry($fileName, $entry_info->getSize(), filesize($entry_info->getPathname()),
131
                0, $entry_info->isCompressed());
132
        }
133
    }
134
135
    /**
136
     * @param $fileName
137
     * @return string
138
     */
139
    public function getFileContent($fileName)
140
    {
141
        if (!in_array($fileName, $this->files, true))
142
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type string.
Loading history...
143
        if ($this->tar instanceof Archive_Tar)
144
            return $this->tar->extractInString($fileName);
0 ignored issues
show
Bug introduced by
The method extractInString() does not exist on PharData. ( Ignorable by Annotation )

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

144
            return $this->tar->/** @scrutinizer ignore-call */ extractInString($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...
145
        else
146
            return $this->tar[$fileName]->getContent();
147
    }
148
149
    /**
150
     * @return array
151
     */
152
    public function getHierarchy()
153
    {
154
        $tree = array(DIRECTORY_SEPARATOR);
155
        foreach ($this->files as $filename) {
156
            if (in_array(substr($filename, -1), array('/', '\\')))
157
                $tree[] = DIRECTORY_SEPARATOR.$filename;
158
        }
159
160
        return $tree;
161
    }
162
163
    /**
164
     * @param                   $outputFolder
165
     * @param string|array|null $files
166
     * @param bool              $expandFilesList
167
     *
168
     * @return bool|int
169
     */
170
    public function extractFiles($outputFolder, $files = null, $expandFilesList = false)
171
    {
172
        if ($expandFilesList && $files !== null)
173
            $files = self::expandFileList($this->files, $files);
174
175
        $list = array();
176
        if ($files === null) {
177
            $list = array_values($this->files);
178
        } else {
179
            foreach ($this->files as $fname) {
180
                if (strpos($fname, $files) === 0) {
181
                    $list[] = $fname;
182
                }
183
            }
184
        }
185
186
        if ($this->tar instanceof Archive_Tar) {
187
            $result = $this->tar->extractList($list, $outputFolder);
0 ignored issues
show
Bug introduced by
The method extractList() does not exist on PharData. Did you maybe mean extractTo()? ( Ignorable by Annotation )

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

187
            /** @scrutinizer ignore-call */ 
188
            $result = $this->tar->extractList($list, $outputFolder);

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...
188
        } else {
189
            $result = $this->tar->extractTo($outputFolder, $list, true);
190
        }
191
192
        if ($result === true) {
193
            return count($list);
194
        } else {
195
            return false;
196
        }
197
    }
198
199
    /**
200
     * @param string|array $fileOrFiles
201
     * @param bool         $expandFilesList
202
     *
203
     * @return bool|int
204
     */
205
    public function deleteFiles($fileOrFiles, $expandFilesList = false)
206
    {
207
        if ($this->tar instanceof Archive_Tar)
208
            return false;
209
210
        if ($expandFilesList && $fileOrFiles !== null)
211
            $fileOrFiles = self::expandFileList($this->files, $fileOrFiles);
212
213
        $files = is_string($fileOrFiles) ? array($fileOrFiles) : $fileOrFiles;
214
        $deleted = 0;
215
216
        foreach ($files as $i => $file) {
217
            if (!in_array($file, $this->files, true))
218
                continue;
219
220
            if ($this->tar->delete($file))
221
                $deleted++;
222
        }
223
224
        $this->tar = null;
225
        $this->openArchive();
226
        $this->scanArchive();
227
228
        return $deleted;
229
    }
230
231
    /**
232
     * @param $fileOrFiles
233
     *
234
     * @return int|bool
235
     * @throws \Exception
236
     */
237
    public function addFiles($fileOrFiles)
238
    {
239
        $fileOrFiles = self::createFilesList($fileOrFiles);
240
241
        $added_files = 0;
242
243
        if ($this->tar instanceof Archive_Tar) {
244
            foreach ($fileOrFiles as $localname => $filename) {
245
                $remove_dir = dirname($filename);
246
                $add_dir = dirname($localname);
247
                if (is_null($filename)) {
248
                    if ($this->tar->addString($localname, "") === false)
0 ignored issues
show
Bug introduced by
The method addString() does not exist on PharData. ( Ignorable by Annotation )

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

248
                    if ($this->tar->/** @scrutinizer ignore-call */ addString($localname, "") === false)

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...
249
                        return false;
250
                } else {
251
                    if ($this->tar->addModify($filename, $add_dir, $remove_dir) === false)
0 ignored issues
show
Bug introduced by
The method addModify() does not exist on PharData. ( Ignorable by Annotation )

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

251
                    if ($this->tar->/** @scrutinizer ignore-call */ addModify($filename, $add_dir, $remove_dir) === false)

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...
252
                        return false;
253
                    $added_files++;
254
                }
255
            }
256
        } else {
257
            try {
258
                foreach ($fileOrFiles as $localname => $filename) {
259
                    if (is_null($filename)) {
260
                        $this->tar->addEmptyDir($localname);
261
                    } else {
262
                        $this->tar->addFile($filename, $localname);
263
                        $added_files++;
264
                    }
265
                }
266
            } catch (Exception $e) {
267
                return false;
268
            }
269
            $this->tar = null;
270
            // reopen to refresh files list properly
271
            $this->openArchive();
272
        }
273
274
275
        $this->scanArchive();
276
277
        return $added_files;
278
    }
279
280
    /**
281
     * @return int
282
     */
283
    public function countFiles()
284
    {
285
        return $this->numberOfFiles;
286
    }
287
288
    /**
289
     * @return int
290
     */
291
    public function getArchiveSize()
292
    {
293
        return filesize($this->path);
294
    }
295
296
    /**
297
     * @return string
298
     */
299
    public function getArchiveType()
300
    {
301
        return 'tar';
302
    }
303
304
    /**
305
     * @return int
306
     */
307
    public function countCompressedFilesSize()
308
    {
309
        return $this->compressedFilesSize;
310
    }
311
312
    /**
313
     * @return int
314
     */
315
    public function countUncompressedFilesSize()
316
    {
317
        return $this->uncompressedFilesSize;
318
    }
319
320
    /**
321
     * @param string|string[]|array[] $fileOrFiles
322
     * @param $archiveName
323
     * @param bool $emulate
324
     * @return array|bool
325
     * @throws Exception
326
     */
327
    public static function archiveFiles($fileOrFiles, $archiveName, $emulate = false)
328
    {
329
        self::checkRequirements();
330
331
        if (file_exists($archiveName))
332
            throw new Exception('Archive '.$archiveName.' already exists!');
333
334
        $fileOrFiles = self::createFilesList($fileOrFiles);
335
336
        // fake creation: return archive data
337
        if ($emulate) {
338
            $totalSize = 0;
339
            foreach ($fileOrFiles as $fn) $totalSize += filesize($fn);
340
341
            return array(
342
                'totalSize' => $totalSize,
343
                'numberOfFiles' => count($fileOrFiles),
0 ignored issues
show
Bug introduced by
It seems like $fileOrFiles can also be of type boolean; however, parameter $var of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

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

343
                'numberOfFiles' => count(/** @scrutinizer ignore-type */ $fileOrFiles),
Loading history...
344
                'files' => $fileOrFiles,
345
                'type' => 'tar',
346
            );
347
        }
348
349
        if (self::$enabledPearTar) {
350
            $compression = null;
351
            switch (strtolower(pathinfo($archiveName, PATHINFO_EXTENSION))) {
352
                case 'gz':
353
                case 'tgz':
354
                    $compression = 'gz';
355
                    break;
356
                case 'bz2':
357
                case 'tbz2':
358
                    $compression = 'bz2';
359
                    break;
360
                case 'xz':
361
                    $compression = 'lzma2';
362
                    break;
363
                case 'z':
364
                    $tar_aname = 'compress.lzw://' . $archiveName;
365
                    break;
366
            }
367
368
            if (isset($tar_aname))
369
                $tar = new Archive_Tar($tar_aname, $compression);
370
            else
371
                $tar = new Archive_Tar($archiveName, $compression);
372
373
            foreach ($fileOrFiles as $localname => $filename) {
374
                $remove_dir = dirname($filename);
375
                $add_dir = dirname($localname);
376
377
                if (is_null($filename)) {
378
                    if ($tar->addString($localname, "") === false)
379
                        return false;
380
                } else {
381
                    if ($tar->addModify($filename, $add_dir, $remove_dir)
382
                        === false) return false;
383
                }
384
            }
385
            $tar = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $tar is dead and can be removed.
Loading history...
386
        } else if (self::$enabledPharData) {
387
            if (preg_match('~^(.+)\.(tar\.(gz|bz2))$~i', $archiveName, $match)) {
388
                $ext = $match[2];
389
                $basename = $match[1];
390
            } else {
391
                $ext = pathinfo($archiveName, PATHINFO_EXTENSION);
392
                $basename = dirname($archiveName).'/'.basename($archiveName, '.'.$ext);
393
            }
394
            $tar = new PharData($basename.'.tar', 0, null, Phar::TAR);
395
396
            try {
397
                foreach ($fileOrFiles as $localname => $filename) {
398
                    if (is_null($filename)) {
399
                        if (!in_array($localname, ['/', ''], true)) {
400
                            if ($tar->addEmptyDir($localname) === false) {
0 ignored issues
show
Bug introduced by
Are you sure the usage of $tar->addEmptyDir($localname) targeting Phar::addEmptyDir() 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...
401
                                return false;
402
                            }
403
                        }
404
                    } else {
405
                        if ($tar->addFile($filename, $localname) === false) {
0 ignored issues
show
Bug introduced by
Are you sure the usage of $tar->addFile($filename, $localname) targeting Phar::addFile() 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...
406
                            return false;
407
                        }
408
                    }
409
                }
410
            } catch (Exception $e) {
411
                return false;
412
            }
413
414
            switch (strtolower(pathinfo($archiveName, PATHINFO_EXTENSION))) {
415
                case 'gz':
416
                case 'tgz':
417
                    $tar->compress(Phar::GZ, $ext);
418
                    break;
419
                case 'bz2':
420
                case 'tbz2':
421
                    $tar->compress(Phar::BZ2, $ext);
422
                    break;
423
            }
424
            $tar = null;
425
        } else {
426
            throw new Exception('Archive_Tar nor PharData not available');
427
        }
428
429
        return count($fileOrFiles);
0 ignored issues
show
Bug Best Practice introduced by
The expression return count($fileOrFiles) returns the type integer which is incompatible with the documented return type array|boolean.
Loading history...
430
    }
431
432
    /**
433
     * Rescans array
434
     */
435
    protected function scanArchive()
436
    {
437
        $this->files = [];
438
        $this->compressedFilesSize =
439
        $this->uncompressedFilesSize = 0;
440
441
        if ($this->tar instanceof Archive_Tar) {
442
            $Content = $this->tar->listContent();
443
            $this->numberOfFiles = count($Content);
444
            foreach ($Content as $i => $file) {
445
                // BUG workaround: http://pear.php.net/bugs/bug.php?id=20275
446
                if ($file['filename'] === 'pax_global_header') {
447
                    $this->numberOfFiles--;
448
                    continue;
449
                }
450
                $this->files[$i] = $file['filename'];
451
                $this->uncompressedFilesSize += $file['size'];
452
            }
453
454
            $this->compressedFilesSize = $this->archiveSize;
455
            $this->compressionRatio = $this->uncompressedFilesSize != 0 ? ceil($this->archiveSize
456
                / $this->uncompressedFilesSize) : 1;
457
        } else {
458
            $this->numberOfFiles = $this->tar->count();
459
            $stream_path_length = strlen('phar://'.$this->path.'/');
460
            foreach (new RecursiveIteratorIterator($this->tar) as $i => $file) {
461
                $this->files[$i] = substr($file->getPathname(), $stream_path_length);
462
                $this->compressedFilesSize += $file->getCompressedSize();
463
                $this->uncompressedFilesSize += filesize($file->getPathname());
464
            }
465
        }
466
    }
467
468
469
470
    /**
471
     * Checks that file exists in archive
472
     * @param string $fileName Name of file
473
     * @return boolean
474
     */
475
    public function isFileExists($fileName)
476
    {
477
        return in_array($fileName, $this->files, true);
478
    }
479
480
    /**
481
     * Returns a resource for reading file from archive
482
     * @param string $fileName
483
     * @return resource|false
484
     */
485
    public function getFileResource($fileName)
486
    {
487
        if (!in_array($fileName, $this->files, true))
488
            return false;
489
490
        $resource = fopen('php://temp', 'r+');
491
        if ($this->tar instanceof Archive_Tar)
492
            fwrite($resource, $this->tar->extractInString($fileName));
0 ignored issues
show
Bug introduced by
It seems like $resource can also be of type false; however, parameter $handle of fwrite() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

492
            fwrite(/** @scrutinizer ignore-type */ $resource, $this->tar->extractInString($fileName));
Loading history...
493
        else
494
            fwrite($resource, $this->tar[$fileName]->getContent());
495
496
        rewind($resource);
0 ignored issues
show
Bug introduced by
It seems like $resource can also be of type false; however, parameter $handle of rewind() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

496
        rewind(/** @scrutinizer ignore-type */ $resource);
Loading history...
497
        return $resource;
498
    }
499
500
    /**
501
     * @param $type
502
     *
503
     * @throws \Exception
504
     */
505
    private function openArchive($type = null)
506
    {
507
        if ($type === null) {
508
            $type = strtolower(pathinfo($this->path, PATHINFO_EXTENSION));
509
        }
510
511
        switch ($type) {
512
            case self::TAR_GZIP:
513
            case 'gz':
514
                if (self::$enabledPharData) {
515
                    $this->tar = new PharData($this->path, self::PHAR_FLAGS);
516
                } else {
517
                    $this->tar = new Archive_Tar($this->path, 'gz');
518
                }
519
                break;
520
521
            case self::TAR_BZIP:
522
            case 'bz2':
523
                if (self::$enabledPharData) {
524
                    $this->tar = new PharData($this->path, self::PHAR_FLAGS);
525
                } else {
526
                    $this->tar = new Archive_Tar($this->path, 'bz2');
527
                }
528
                break;
529
530
            case self::TAR_LZMA:
531
            case 'xz':
532
                if (!self::$enabledPharData) {
533
                    throw new Exception('Archive_Tar not available');
534
                }
535
                $this->tar = new Archive_Tar($this->path, 'lzma2');
536
                break;
537
538
            case self::TAR_LZW:
539
            case 'z':
540
                if (!self::$enabledPharData) {
541
                    throw new Exception('Archive_Tar not available');
542
                }
543
                $this->tar = new Archive_Tar('compress.lzw://' . $this->path);
544
                break;
545
546
            default:
547
                if (self::$enabledPharData) {
548
                    $this->tar = new PharData($this->path, self::PHAR_FLAGS, null, Phar::TAR);
549
                } else {
550
                    $this->tar = new Archive_Tar($this->path);
551
                }
552
                break;
553
        }
554
    }
555
}
556