Passed
Push — master ( e653a8...40146c )
by f
39:43 queued 24:20
created

TarByPhar::canStream()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 3
rs 10
ccs 2
cts 2
cp 1
crap 1
1
<?php
2
namespace wapmorgan\UnifiedArchive\Drivers;
3
4
use Exception;
5
use FilesystemIterator;
6
use Phar;
7
use PharData;
8
use PharFileInfo;
9
use RecursiveIteratorIterator;
10
use wapmorgan\UnifiedArchive\ArchiveEntry;
11
use wapmorgan\UnifiedArchive\ArchiveInformation;
12
use wapmorgan\UnifiedArchive\Drivers\BasicDriver;
13
use wapmorgan\UnifiedArchive\Exceptions\ArchiveCreationException;
14
use wapmorgan\UnifiedArchive\Exceptions\ArchiveExtractionException;
15
use wapmorgan\UnifiedArchive\Exceptions\ArchiveModificationException;
16
use wapmorgan\UnifiedArchive\Exceptions\UnsupportedOperationException;
17
use wapmorgan\UnifiedArchive\Formats;
18
19
class TarByPhar extends BasicDriver
20
{
21
    public static $disabled = false;
22
23
    /**
24
     * @var false|string
25
     */
26
    protected $archiveFileName;
27
28
    /**
29
     * @var PharData
30
     */
31
    protected $tar;
32
33
    /**
34
     * @var float
35
     */
36
    protected $compressRatio;
37
38
    protected $pureFilesNumber;
39 1
40
    /**
41
     * @var int Flags for iterator
42 1
     */
43
    const PHAR_FLAGS = FilesystemIterator::UNIX_PATHS;
44
45
    /**
46
     * @return array
47
     */
48
    public static function getSupportedFormats()
49
    {
50
        return [
51
            Formats::TAR,
52
            Formats::TAR_GZIP,
53 3
            Formats::TAR_BZIP,
54
//            Formats::ZIP,
55 3
        ];
56
    }
57 3
58
    /**
59 1
     * @param $format
60 2
     * @return bool
61 1
     */
62 1
    public static function checkFormatSupport($format)
63 1
    {
64
        if (static::$disabled) {
65
            return false;
66
        }
67
        $availability = class_exists('\PharData');
68
        switch ($format) {
69
            case Formats::TAR:
70
//            case Formats::ZIP:
71
                return $availability;
72
            case Formats::TAR_GZIP:
73
                return $availability && extension_loaded('zlib');
74
            case Formats::TAR_BZIP:
75
                return $availability && extension_loaded('bz2');
76
        }
77
    }
78
79
    /**
80
     * @inheritDoc
81
     */
82
    public static function getDescription()
83
    {
84
        return 'adapter for ext-phar';
85
    }
86
87
    /**
88 10
     * @inheritDoc
89
     */
90 10
    public static function getInstallationInstruction()
91 10
    {
92 10
        return !extension_loaded('phar')
93
            ? 'install `phar` extension and optionally php-extensions (zlib, bzip2)'
94
            : null;
95
    }
96
97 10
    /**
98
     * @inheritDoc
99 10
     */
100 10
    public function __construct($archiveFileName, $format, $password = null)
101
    {
102
        $this->archiveFileName = realpath($archiveFileName);
103
        $this->open();
104
    }
105 10
106
    /**
107 10
     *
108 10
     */
109
    protected function open()
110
    {
111
        $this->tar = new PharData($this->archiveFileName, self::PHAR_FLAGS);
112
    }
113 10
114 10
    /**
115 10
     * @inheritDoc
116 10
     */
117
    public function getArchiveInformation()
118 10
    {
119
        $information = new ArchiveInformation();
120
        $stream_path_length = strlen('phar://'.$this->archiveFileName.'/');
121
        $information->compressedFilesSize = filesize($this->archiveFileName);
0 ignored issues
show
Bug introduced by
It seems like $this->archiveFileName can also be of type boolean; however, parameter $filename of filesize() 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

121
        $information->compressedFilesSize = filesize(/** @scrutinizer ignore-type */ $this->archiveFileName);
Loading history...
122
        /**
123
         * @var string $i
124
         * @var PharFileInfo $file
125
         */
126
        foreach (new RecursiveIteratorIterator($this->tar) as $i => $file) {
127
            $information->files[] = substr($file->getPathname(), $stream_path_length);
128
            $information->uncompressedFilesSize += $file->getSize();
129
        }
130
        $this->compressRatio = $information->compressedFilesSize > 0
131
            ? $information->uncompressedFilesSize / $information->compressedFilesSize
132
            : 0;
133
        $this->pureFilesNumber = count($information->files);
134
        return $information;
135
    }
136
137
    /**
138
     * @inheritDoc
139
     */
140
    public function getFileNames()
141
    {
142
        $files = [];
143
144
        $stream_path_length = strlen('phar://'.$this->archiveFileName.'/');
145
        foreach (new RecursiveIteratorIterator($this->tar) as $i => $file) {
146
            $files[] = substr($file->getPathname(), $stream_path_length);
147
        }
148
149
        return $files;
150
    }
151
152 3
    /**
153
     * @inheritDoc
154
     */
155 3
    public function isFileExists($fileName)
156 3
    {
157 3
        try {
158
            $this->tar->offsetGet($fileName);
159
            return true;
160
        } catch (Exception $e) {
161
            return false;
162
        }
163 4
    }
164
165 4
    /**
166
     * @inheritDoc
167
     */
168
    public function getFileData($fileName)
169
    {
170
        /** @var \PharFileInfo $entry_info */
171 3
        $entry_info = $this->tar->offsetGet($fileName);
172
        return new ArchiveEntry(
173 3
            $fileName,
174
            ($this->compressRatio > 0 ? floor($entry_info->getSize() / $this->compressRatio) : 0), //$entry_info->getCompressedSize(),
175
            $entry_info->getSize(),
176
            $entry_info->getMTime(),
177
            $entry_info->isCompressed());
178
    }
179
180
    /**
181
     * @inheritDoc
182
     */
183
    public function getFileContent($fileName)
184
    {
185
        return $this->tar->offsetGet($fileName)->getContent();
186
    }
187
188
    /**
189
     * @inheritDoc
190
     */
191
    public function getFileStream($fileName)
192
    {
193
        return fopen('phar://'.$this->archiveFileName . '/' . $fileName, 'rb');
194
    }
195
196
    /**
197
     * @inheritDoc
198
     */
199
    public function extractFiles($outputFolder, array $files)
200
    {
201
        $result = $this->tar->extractTo($outputFolder, $files, true);
202
        if ($result === false) {
203
            throw new ArchiveExtractionException('Error when extracting from '.$this->archiveFileName);
204 1
        }
205
        return count($files);
206 1
    }
207
208 1
    /**
209 1
     * @inheritDoc
210 1
     */
211
    public function extractArchive($outputFolder)
212
    {
213 1
        $result = $this->tar->extractTo($outputFolder, null, true);
214 1
        if ($result === false) {
215
            throw new ArchiveExtractionException('Error when extracting from '.$this->archiveFileName);
216 1
        }
217
218
        return $this->pureFilesNumber;
219
    }
220
221
    /**
222 1
     * @inheritDoc
223
     */
224 1
    public function deleteFiles(array $files)
225
    {
226 1
        $deleted = 0;
227 1
228
        foreach ($files as $i => $file) {
229
            if ($this->tar->delete($file))
230 1
                $deleted++;
231 1
        }
232
233
        $this->tar = null;
234
        $this->open();
235
236
        return $deleted;
237 1
    }
238
239 1
    /**
240 1
     * @inheritDoc
241
     */
242
    public function addFiles(array $files)
243
    {
244
        $added = 0;
245
        try {
246
            foreach ($files as $localName => $filename) {
247 1
                if (is_null($filename)) {
248
                    $this->tar->addEmptyDir($localName);
249 1
                } else {
250
                    $this->tar->addFile($filename, $localName);
251
                    $added++;
252
                }
253
            }
254
        } catch (Exception $e) {
255
            throw new ArchiveModificationException('Could not add file "'.$filename.'": '.$e->getMessage(), $e->getCode());
256 1
        }
257
        $this->tar = null;
258 1
        // reopen to refresh files list properly
259
        $this->open();
260
        return $added;
261
    }
262
263
    /**
264
     * @param $format
265 1
     * @return bool
266
     */
267 1
    public static function canCreateArchive($format)
268
    {
269
        return true;
270
    }
271
272
    /**
273
     * @param $format
274
     * @return bool
275
     */
276
    public static function canAddFiles($format)
277
    {
278
        return true;
279
    }
280 1
281
    /**
282 1
     * @param $format
283
     * @return bool
284
     */
285
    public static function canDeleteFiles($format)
286 1
    {
287
        return true;
288
    }
289
290 1
    /**
291 1
     * @param $format
292
     * @return bool
293
     */
294 1
    public static function canStream($format)
295
    {
296 1
        return true;
297 1
    }
298
299
    /**
300 1
     * @param array $files
301 1
     * @param string $archiveFileName
302
     * @param int $archiveFormat
303
     * @param int $compressionLevel
304
     * @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...
305
     * @param $fileProgressCallable
306 1
     * @return int
307
     * @throws ArchiveCreationException
308
     * @throws UnsupportedOperationException
309 1
     */
310
    public static function createArchive(
311
        array $files,
312
        $archiveFileName,
313
        $archiveFormat,
314
        $compressionLevel = self::COMPRESSION_AVERAGE,
315 1
        $password = null,
316 1
        $fileProgressCallable = null
317 1
    )
318
    {
319
        if ($password !== null) {
0 ignored issues
show
introduced by
The condition $password !== null is always false.
Loading history...
320
            throw new UnsupportedOperationException('One-file format ('.__CLASS__.') could not encrypt an archive');
321 1
        }
322 1
323 1
        if ($fileProgressCallable !== null && !is_callable($fileProgressCallable)) {
324 1
            throw new ArchiveCreationException('File progress callable is not callable');
325 1
        }
326
327
        if (preg_match('~^(.+)\.(tar\.(gz|bz2))$~i', $archiveFileName, $match)) {
328
            $ext = $match[2];
329 1
            $basename = $match[1];
330 1
        } else {
331
            $ext = pathinfo($archiveFileName, PATHINFO_EXTENSION);
332
            $basename = dirname($archiveFileName).'/'.basename($archiveFileName, '.'.$ext);
0 ignored issues
show
Bug introduced by
Are you sure $ext of type array|string 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

332
            $basename = dirname($archiveFileName).'/'.basename($archiveFileName, '.'./** @scrutinizer ignore-type */ $ext);
Loading history...
333
        }
334
335
        $compression = null;
336
        switch ($ext) {
337
            case 'tar.gz':
338
            case 'tgz':
339 1
                $compression = Phar::GZ;
340
                break;
341
            case 'tar.bz2':
342 1
            case 'tbz2':
343
                $compression = Phar::BZ2;
344
                break;
345
        }
346 1
347
        $destination_file = $basename.'.tar';
348
        // if compression used and there is .tar archive with that's name,
349 1
        // use temp file
350
        if ($compression !== null && file_exists($basename.'.tar')) {
351
            $temp_basename = tempnam(sys_get_temp_dir(), 'tar-archive');
352
            unlink($temp_basename);
353
            $destination_file = $temp_basename.'.tar';
354 1
        }
355
356
        $tar = new PharData(
357
            $destination_file,
358 1
            0, null, Phar::TAR
359
        );
360
361
        try {
362
            $current_file = 0;
363
            $total_files = count($files);
364
365
            foreach ($files as $localName => $filename) {
366
                if (is_null($filename)) {
367
                    if (!in_array($localName, ['/', ''], true)) {
368
                        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...
369
                            throw new ArchiveCreationException('Error when adding directory '.$localName.' to archive');
370
                        }
371
                    }
372
                } else {
373
                    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...
374
                        throw new ArchiveCreationException('Error when adding file '.$localName.' to archive');
375
                    }
376
                }
377
                if ($fileProgressCallable !== null) {
378
                    call_user_func_array($fileProgressCallable, [$current_file++, $total_files, $filename, $localName]);
379
                }
380
            }
381
        } catch (Exception $e) {
382
            throw new ArchiveCreationException('Error when creating archive: '.$e->getMessage(), $e->getCode(), $e);
383
        }
384
385
        switch ($compression) {
386
            case Phar::GZ:
387
                $tar->compress(Phar::GZ, $ext);
388
                break;
389
            case Phar::BZ2:
390
                $tar->compress(Phar::BZ2, $ext);
391
                break;
392
        }
393
        $tar = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $tar is dead and can be removed.
Loading history...
394
395
        // if compression used and original .tar file exist, clean it
396
        if ($compression !== null && file_exists($destination_file)) {
397
            unlink($destination_file);
398
        }
399
400
        // it temp file was used, rename it to destination archive name
401
        if (isset($temp_basename)) {
402
            rename($temp_basename.'.'.$ext, $archiveFileName);
403
        }
404
405
        return count($files);
406
    }
407
408
    /**
409
     * @param string $inArchiveName
410
     * @param string $content
411
     * @return bool
412
     */
413
    public function addFileFromString($inArchiveName, $content)
414
    {
415
        $this->tar->addFromString($inArchiveName, $content);
416
        return true;
417
    }
418
}
419