Passed
Push — master ( 32b8d0...7c729d )
by f
21:23 queued 06:22
created

TarByPhar::checkFormatSupport()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 14
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 7.7656

Importance

Changes 0
Metric Value
cc 7
eloc 10
nc 7
nop 1
dl 0
loc 14
ccs 6
cts 8
cp 0.75
crap 7.7656
rs 8.8333
c 0
b 0
f 0
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
    protected $pureFilesNumber;
34
35
    /**
36
     * @var int Flags for iterator
37
     */
38
    const PHAR_FLAGS = FilesystemIterator::UNIX_PATHS;
39 1
40
    /**
41
     * @return array
42 1
     */
43
    public static function getSupportedFormats()
44
    {
45
        return [
46
            Formats::TAR,
47
            Formats::TAR_GZIP,
48
            Formats::TAR_BZIP,
49
//            Formats::ZIP,
50
        ];
51
    }
52
53 3
    /**
54
     * @param $format
55 3
     * @return bool
56
     */
57 3
    public static function checkFormatSupport($format)
58
    {
59 1
        if (static::$disabled) {
60 2
            return false;
61 1
        }
62 1
        $availability = class_exists('\PharData');
63 1
        switch ($format) {
64
            case Formats::TAR:
65
//            case Formats::ZIP:
66
                return $availability;
67
            case Formats::TAR_GZIP:
68
                return $availability && extension_loaded('zlib');
69
            case Formats::TAR_BZIP:
70
                return $availability && extension_loaded('bz2');
71
        }
72
    }
73
74
    /**
75
     * @inheritDoc
76
     */
77
    public static function getDescription()
78
    {
79
        return 'adapter for ext-phar';
80
    }
81
82
    /**
83
     * @inheritDoc
84
     */
85
    public static function getInstallationInstruction()
86
    {
87
        return !extension_loaded('phar')
88 10
            ? 'install `phar` extension and optionally php-extensions (zlib, bzip2)'
89
            : null;
90 10
    }
91 10
92 10
    /**
93
     * @inheritDoc
94
     */
95
    public function __construct($archiveFileName, $format, $password = null)
96
    {
97 10
        $this->archiveFileName = realpath($archiveFileName);
98
        $this->open();
99 10
    }
100 10
101
    /**
102
     *
103
     */
104
    protected function open()
105 10
    {
106
        $this->tar = new PharData($this->archiveFileName, self::PHAR_FLAGS);
107 10
    }
108 10
109
    /**
110
     * @inheritDoc
111
     */
112
    public function getArchiveInformation()
113 10
    {
114 10
        $information = new ArchiveInformation();
115 10
        $stream_path_length = strlen('phar://'.$this->archiveFileName.'/');
116 10
        /**
117
         * @var string $i
118 10
         * @var PharFileInfo $file
119
         */
120
        foreach (new RecursiveIteratorIterator($this->tar) as $i => $file) {
121
            $information->files[] = substr($file->getPathname(), $stream_path_length);
122
            $information->compressedFilesSize += $file->getCompressedSize();
123
            $information->uncompressedFilesSize += filesize($file->getPathname());
124
        }
125
        $this->pureFilesNumber = count($information->files);
126
        return $information;
127
    }
128
129
    /**
130
     * @inheritDoc
131
     */
132
    public function getFileNames()
133
    {
134
        $files = [];
135
136
        $stream_path_length = strlen('phar://'.$this->archiveFileName.'/');
137
        foreach (new RecursiveIteratorIterator($this->tar) as $i => $file) {
138
            $files[] = substr($file->getPathname(), $stream_path_length);
139
        }
140
141
        return $files;
142
    }
143
144
    /**
145
     * @inheritDoc
146
     */
147
    public function isFileExists($fileName)
148
    {
149
        try {
150
            $this->tar->offsetGet($fileName);
151
            return true;
152 3
        } catch (Exception $e) {
153
            return false;
154
        }
155 3
    }
156 3
157 3
    /**
158
     * @inheritDoc
159
     */
160
    public function getFileData($fileName)
161
    {
162
        /** @var \PharFileInfo $entry_info */
163 4
        $entry_info = $this->tar->offsetGet($fileName);
164
        return new ArchiveEntry($fileName, $entry_info->getSize(), filesize($entry_info->getPathname()),
165 4
            0, $entry_info->isCompressed());
166
    }
167
168
    /**
169
     * @inheritDoc
170
     */
171 3
    public function getFileContent($fileName)
172
    {
173 3
        return $this->tar->offsetGet($fileName)->getContent();
174
    }
175
176
    /**
177
     * @inheritDoc
178
     */
179
    public function getFileStream($fileName)
180
    {
181
        return self::wrapStringInStream($this->tar->offsetGet($fileName)->getContent());
182
    }
183
184
    /**
185
     * @inheritDoc
186
     */
187
    public function extractFiles($outputFolder, array $files)
188
    {
189
        $result = $this->tar->extractTo($outputFolder, $files, true);
190
        if ($result === false) {
191
            throw new ArchiveExtractionException('Error when extracting from '.$this->archiveFileName);
192
        }
193
        return count($files);
194
    }
195
196
    /**
197
     * @inheritDoc
198
     */
199
    public function extractArchive($outputFolder)
200
    {
201
        $result = $this->tar->extractTo($outputFolder, null, true);
202
        if ($result === false) {
203
            throw new ArchiveExtractionException('Error when extracting from '.$this->archiveFileName);
204 1
        }
205
206 1
        return $this->pureFilesNumber;
207
    }
208 1
209 1
    /**
210 1
     * @inheritDoc
211
     */
212
    public function deleteFiles(array $files)
213 1
    {
214 1
        $deleted = 0;
215
216 1
        foreach ($files as $i => $file) {
217
            if ($this->tar->delete($file))
218
                $deleted++;
219
        }
220
221
        $this->tar = null;
222 1
        $this->open();
223
224 1
        return $deleted;
225
    }
226 1
227 1
    /**
228
     * @inheritDoc
229
     */
230 1
    public function addFiles(array $files)
231 1
    {
232
        $added = 0;
233
        try {
234
            foreach ($files as $localName => $filename) {
235
                if (is_null($filename)) {
236
                    $this->tar->addEmptyDir($localName);
237 1
                } else {
238
                    $this->tar->addFile($filename, $localName);
239 1
                    $added++;
240 1
                }
241
            }
242
        } catch (Exception $e) {
243
            throw new ArchiveModificationException('Could not add file "'.$filename.'": '.$e->getMessage(), $e->getCode());
244
        }
245
        $this->tar = null;
246
        // reopen to refresh files list properly
247 1
        $this->open();
248
        return $added;
249 1
    }
250
251
    /**
252
     * @param $format
253
     * @return bool
254
     */
255
    public static function canCreateArchive($format)
256 1
    {
257
        return true;
258 1
    }
259
260
    /**
261
     * @param $format
262
     * @return bool
263
     */
264
    public static function canAddFiles($format)
265 1
    {
266
        return true;
267 1
    }
268
269
    /**
270
     * @param $format
271
     * @return bool
272
     */
273
    public static function canDeleteFiles($format)
274
    {
275
        return true;
276
    }
277
278
    /**
279
     * @param array $files
280 1
     * @param string $archiveFileName
281
     * @param int $archiveFormat
282 1
     * @param int $compressionLevel
283
     * @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...
284
     * @param $fileProgressCallable
285
     * @return int
286 1
     * @throws ArchiveCreationException
287
     * @throws UnsupportedOperationException
288
     */
289
    public static function createArchive(
290 1
        array $files,
291 1
        $archiveFileName,
292
        $archiveFormat,
293
        $compressionLevel = self::COMPRESSION_AVERAGE,
294 1
        $password = null,
295
        $fileProgressCallable = null
296 1
    )
297 1
    {
298
        if ($password !== null) {
0 ignored issues
show
introduced by
The condition $password !== null is always false.
Loading history...
299
            throw new UnsupportedOperationException('One-file format ('.__CLASS__.') could not encrypt an archive');
300 1
        }
301 1
302
        if ($fileProgressCallable !== null && !is_callable($fileProgressCallable)) {
303
            throw new ArchiveCreationException('File progress callable is not callable');
304
        }
305
306 1
        if (preg_match('~^(.+)\.(tar\.(gz|bz2))$~i', $archiveFileName, $match)) {
307
            $ext = $match[2];
308
            $basename = $match[1];
309 1
        } else {
310
            $ext = pathinfo($archiveFileName, PATHINFO_EXTENSION);
311
            $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

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