Passed
Push — master ( e9611b...a3681d )
by f
13:07
created

TarByPear::isInstalled()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
ccs 0
cts 0
cp 0
crap 2
1
<?php
2
namespace wapmorgan\UnifiedArchive\Drivers;
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 Vtiful\Kernel\Format;
6
use wapmorgan\UnifiedArchive\ArchiveEntry;
7
use wapmorgan\UnifiedArchive\ArchiveInformation;
8
use wapmorgan\UnifiedArchive\Drivers\BasicDriver;
9
use wapmorgan\UnifiedArchive\Exceptions\ArchiveCreationException;
10
use wapmorgan\UnifiedArchive\Exceptions\ArchiveExtractionException;
11
use wapmorgan\UnifiedArchive\Exceptions\ArchiveModificationException;
12
use wapmorgan\UnifiedArchive\Exceptions\NonExistentArchiveFileException;
13
use wapmorgan\UnifiedArchive\Exceptions\UnsupportedOperationException;
14
use wapmorgan\UnifiedArchive\Formats;
15
use wapmorgan\UnifiedArchive\LzwStreamWrapper;
16
17
class TarByPear extends BasicDriver
18
{
19
    const TYPE = self::TYPE_PURE_PHP;
20
21
    /**
22
     * @var string Full path to archive
23
     */
24
    protected $archiveFileName;
25
26
    /**
27
     * @var Archive_Tar
28
     */
29
    protected $tar;
30
31
    /**
32
     * @var float Overall compression ratio of Tar archive when Archive_Tar is used
33
     */
34
    protected $pearCompressionRatio;
35
36
    /**
37
     * @var array<string, integer> List of files and their index in listContent() result
38
     */
39
    protected $pearFilesIndex;
40
41 1
    protected $pureFilesNumber;
42
43
    /**
44 1
     * @inheritDoc
45
     */
46
    public static function getDescription()
47
    {
48
        return 'php-library for tar';
49
    }
50
51
    public static function isInstalled()
52
    {
53
        return class_exists('\Archive_Tar');
54
    }
55
56
    /**
57 4
     * @inheritDoc
58
     */
59 4
    public static function getInstallationInstruction()
60 4
    {
61
        return 'install library [pear/archive_tar]: `composer require pear/archive_tar`' . "\n"  . ' and optionally php-extensions (zlib, bz2)';
62 4
    }
63 1
64
    /**
65 3
     * @return array
66 1
     */
67
    public static function getSupportedFormats()
68 2
    {
69 1
        return [
70
            Formats::TAR,
71 1
            Formats::TAR_GZIP,
72 1
            Formats::TAR_BZIP,
73
            Formats::TAR_LZMA,
74
            Formats::TAR_LZW,
75
        ];
76
    }
77
78
    /**
79
     * @param $format
80
     * @return array
81
     * @throws \Exception
82
     */
83
    public static function checkFormatSupport($format)
84
    {
85
        if (!static::isInstalled()) {
86
            return [];
87
        }
88
89
        $abilities = [
90
            BasicDriver::OPEN,
91
            BasicDriver::EXTRACT_CONTENT,
92
            BasicDriver::APPEND,
93
            BasicDriver::CREATE,
94
        ];
95
96
        switch ($format) {
97
            case Formats::TAR:
98
                return $abilities;
99
100
            case Formats::TAR_GZIP:
101
                if (!extension_loaded('zlib')) {
102
                    return [];
103
                }
104
                return $abilities;
105
106
            case Formats::TAR_BZIP:
107
                if (!extension_loaded('bz2')) {
108
                    return [];
109
                }
110
                return $abilities;
111
112
            case Formats::TAR_LZMA:
113
                if (!extension_loaded('xz')) {
114
                    return [];
115
                }
116
                return $abilities;
117
118
            case Formats::TAR_LZW:
119
                if (!LzwStreamWrapper::isBinaryAvailable()) {
120
                    return [];
121
                }
122
                return $abilities;
123
        }
124
    }
125
126
    /**
127
     * @param array $files
128
     * @param string $archiveFileName
129
     * @param int $archiveFormat
130
     * @param int $compressionLevel
131
     * @param null $password
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $password is correct as it would always require null to be passed?
Loading history...
132
     * @param $fileProgressCallable
133
     * @return int
134
     * @throws ArchiveCreationException
135
     * @throws UnsupportedOperationException
136
     */
137
    public static function createArchive(
138
        array $files,
139
        $archiveFileName,
140
        $archiveFormat,
141
        $compressionLevel = self::COMPRESSION_AVERAGE,
142
        $password = null,
143
        $fileProgressCallable = null
144
    )
145
    {
146
        if ($password !== null) {
0 ignored issues
show
introduced by
The condition $password !== null is always false.
Loading history...
147
            throw new UnsupportedOperationException('One-file format ('.__CLASS__.') could not encrypt an archive');
148
        }
149
150
        if ($fileProgressCallable !== null && !is_callable($fileProgressCallable)) {
151
            throw new ArchiveCreationException('File progress callable is not callable');
152
        }
153
154
        $compression = null;
155
        switch (strtolower(pathinfo($archiveFileName, PATHINFO_EXTENSION))) {
0 ignored issues
show
Bug introduced by
It seems like pathinfo($archiveFileNam...ers\PATHINFO_EXTENSION) can also be of type array; however, parameter $string of strtolower() does only seem to accept string, 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

155
        switch (strtolower(/** @scrutinizer ignore-type */ pathinfo($archiveFileName, PATHINFO_EXTENSION))) {
Loading history...
156 3
            case 'gz':
157
            case 'tgz':
158 3
                $compression = 'gz';
159 3
                break;
160 3
            case 'bz2':
161
            case 'tbz2':
162 3
                $compression = 'bz2';
163
                break;
164
            case 'xz':
165 3
                $compression = 'lzma2';
166
                break;
167
            case 'z':
168
                $tar_aname = 'compress.lzw://' . $archiveFileName;
169 3
                break;
170
        }
171
172
        if (isset($tar_aname))
173 3
            $tar = new Archive_Tar($tar_aname, $compression);
174 3
        else
175 3
            $tar = new Archive_Tar($archiveFileName, $compression);
176
177
        $current_file = 0;
178
        $total_files = count($files);
179
        foreach ($files as $localName => $filename) {
180
            $remove_dir = dirname($filename);
181
            $add_dir = dirname($localName);
182
183
            if (is_null($filename)) {
184
                if ($tar->addString($localName, '') === false)
185
                    throw new ArchiveCreationException('Error when adding directory '.$localName.' to archive');
186 3
            } else {
187
                if ($tar->addModify($filename, $add_dir, $remove_dir) === false)
188
                    throw new ArchiveCreationException('Error when adding file '.$filename.' to archive');
189
            }
190
            if ($fileProgressCallable !== null) {
191 3
                call_user_func_array($fileProgressCallable, [$current_file++, $total_files, $filename, $localName]);
192
            }
193 3
        }
194 3
        $tar = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $tar is dead and can be removed.
Loading history...
195
196 3
        return count($files);
197
    }
198 3
199
    /**
200
     * @inheritDoc
201
     */
202 3
    public function __construct($archiveFileName, $format, $password = null)
203 3
    {
204 3
        $this->archiveFileName = realpath($archiveFileName);
205 3
        $this->open($format);
206 3
    }
207
208
    protected function open($archiveType)
209 3
    {
210 3
        switch ($archiveType) {
211 3
            case Formats::TAR_GZIP:
212
                $this->tar = new Archive_Tar($this->archiveFileName, 'gz');
213 3
                break;
214
215
            case Formats::TAR_BZIP:
216
                $this->tar = new Archive_Tar($this->archiveFileName, 'bz2');
217
                break;
218
219
            case Formats::TAR_LZMA:
220
                $this->tar = new Archive_Tar($this->archiveFileName, 'lzma2');
221
                break;
222
223
            case Formats::TAR_LZW:
224
                LzwStreamWrapper::registerWrapper();
225
                $this->tar = new Archive_Tar('compress.lzw://' . $this->archiveFileName);
226
                break;
227
228
            default:
229
                $this->tar = new Archive_Tar($this->archiveFileName);
230
                break;
231
        }
232
    }
233
234
    /**
235
     * @inheritDoc
236
     */
237
    public function getArchiveInformation()
238
    {
239
        $information = new ArchiveInformation();
240
        $this->pearFilesIndex = [];
241
        $this->pureFilesNumber = 0;
242
243
        foreach ($this->tar->listContent() as $i => $file) {
244
            // BUG workaround: http://pear.php.net/bugs/bug.php?id=20275
245
            if ($file['filename'] === 'pax_global_header') {
246 1
                continue;
247
            }
248 1
            // skip directories
249
            if ($file['typeflag'] == '5' || substr($file['filename'], -1) === '/')
250
                continue;
251 1
            $information->files[] = $file['filename'];
252
            $information->uncompressedFilesSize += $file['size'];
253 1
            $this->pearFilesIndex[$file['filename']] = $i;
254 1
            $this->pureFilesNumber++;
255
        }
256
257 1
        $information->compressedFilesSize = filesize($this->archiveFileName);
258 1
        $this->pearCompressionRatio = $information->uncompressedFilesSize != 0
259
            ? ceil($information->compressedFilesSize / $information->uncompressedFilesSize)
260 1
            : 1;
261 1
        return $information;
262 1
    }
263
264
    /**
265
     * @inheritDoc
266
     */
267
    public function getFileNames()
268 1
    {
269
        $files = [];
270 1
271
        $Content = $this->tar->listContent();
272
        foreach ($Content as $i => $file) {
273 1
            // BUG workaround: http://pear.php.net/bugs/bug.php?id=20275
274
            if ($file['filename'] === 'pax_global_header') {
275
                continue;
276
            }
277
            $files[] = $file['filename'];
278
        }
279 1
280
        return $files;
281 1
    }
282
283
    /**
284 1
     * @inheritDoc
285
     */
286
    public function isFileExists($fileName)
287
    {
288
        return isset($this->pearFilesIndex[$fileName]);
289
    }
290
291
    /**
292
     * @inheritDoc
293
     */
294
    public function getFileData($fileName)
295
    {
296
        if (!isset($this->pearFilesIndex[$fileName]))
297
            throw new NonExistentArchiveFileException('File '.$fileName.' is not found in archive files list');
298
299
        $index = $this->pearFilesIndex[$fileName];
300
301
        $files_list = $this->tar->listContent();
302
        if (!isset($files_list[$index]))
303
            throw new NonExistentArchiveFileException('File '.$fileName.' is not found in Tar archive');
304
305
        $data = $files_list[$index];
306
        unset($files_list);
307
308
        return new ArchiveEntry($fileName, $data['size'] / $this->pearCompressionRatio,
0 ignored issues
show
Bug introduced by
$data['size'] / $this->pearCompressionRatio of type double is incompatible with the type integer expected by parameter $compressedSize of wapmorgan\UnifiedArchive...iveEntry::__construct(). ( Ignorable by Annotation )

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

308
        return new ArchiveEntry($fileName, /** @scrutinizer ignore-type */ $data['size'] / $this->pearCompressionRatio,
Loading history...
309
            $data['size'], $data['mtime'], in_array(strtolower(pathinfo($this->archiveFileName,
0 ignored issues
show
Bug introduced by
It seems like pathinfo($this->archiveF...ers\PATHINFO_EXTENSION) can also be of type array; however, parameter $string of strtolower() does only seem to accept string, 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

309
            $data['size'], $data['mtime'], in_array(strtolower(/** @scrutinizer ignore-type */ pathinfo($this->archiveFileName,
Loading history...
310
                PATHINFO_EXTENSION)), ['gz', 'bz2', 'xz', 'z']));
311
    }
312
313
    /**
314
     * @inheritDoc
315
     */
316
    public function getFileContent($fileName)
317
    {
318
        if (!isset($this->pearFilesIndex[$fileName]))
319
            throw new NonExistentArchiveFileException('File '.$fileName.' is not found in archive files list');
320
321
        return $this->tar->extractInString($fileName);
322
    }
323
324
    /**
325
     * @inheritDoc
326
     */
327
    public function getFileStream($fileName)
328
    {
329
        if (!isset($this->pearFilesIndex[$fileName]))
330
            throw new NonExistentArchiveFileException('File '.$fileName.' is not found in archive files list');
331
332
        return self::wrapStringInStream($this->tar->extractInString($fileName));
333
    }
334
335
    /**
336
     * @inheritDoc
337
     */
338
    public function extractFiles($outputFolder, array $files)
339
    {
340
        $result = $this->tar->extractList($files, $outputFolder);
341
        if ($result === false) {
342
            throw new ArchiveExtractionException('Error when extracting from '.$this->archiveFileName);
343
        }
344
345
        return count($files);
346
    }
347
348
    /**
349
     * @inheritDoc
350
     */
351
    public function extractArchive($outputFolder)
352
    {
353
        $result = $this->tar->extract($outputFolder);
354
        if ($result === false) {
355
            throw new ArchiveExtractionException('Error when extracting from '.$this->archiveFileName);
356
        }
357
358
        return $this->pureFilesNumber;
359
    }
360
361
    /**
362
     * @inheritDoc
363
     */
364
    public function addFiles(array $files)
365
    {
366
        $added = 0;
367
        foreach ($files as $localName => $filename) {
368
            $remove_dir = dirname($filename);
369
            $add_dir = dirname($localName);
370
            if (is_null($filename)) {
371
                if ($this->tar->addString($localName, "") === false) {
372
                    throw new ArchiveModificationException('Could not add directory "'.$filename.'": '.$this->tar->error_object->message, $this->tar->error_object->code);
373
                }
374
            } else {
375
                if ($this->tar->addModify($filename, $add_dir, $remove_dir) === false) {
376
                    throw new ArchiveModificationException('Could not add file "'.$filename.'": '.$this->tar->error_object->message, $this->tar->error_object->code);
377
                }
378
                $added++;
379
            }
380
        }
381
        return $added;
382
    }
383
384
    /**
385
     * @param string $inArchiveName
386
     * @param string $content
387
     * @return bool|true
388
     */
389
    public function addFileFromString($inArchiveName, $content)
390
    {
391
        return $this->tar->addString($inArchiveName, $content);
392
    }
393
}