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

SevenZip::isInstalled()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 1
c 0
b 0
f 0
nc 2
nop 0
dl 0
loc 3
rs 10
ccs 1
cts 1
cp 1
crap 2
1
<?php
2
namespace wapmorgan\UnifiedArchive\Drivers;
3
4
use Exception;
5
use wapmorgan\UnifiedArchive\Archive7z;
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
16
class SevenZip extends BasicDriver
17
{
18
    const TYPE = self::TYPE_UTILITIES;
19
20
    /** @var Archive7z */
21
    protected $sevenZip;
22
23
    /**
24
     * @var string
25
     */
26
    protected $format;
27
28 1
    const COMMENT_FILE = 'descript.ion';
29
30
    public static function isInstalled()
31 1
    {
32
        return class_exists('\Archive7z\Archive7z') && Archive7z::getBinaryVersion() !== false;
33
    }
34
35
    /**
36
     * @inheritDoc
37
     */
38
    public static function getInstallationInstruction()
39
    {
40
        if (!class_exists('\Archive7z\Archive7z'))
41
            return 'install library [gemorroj/archive7z]: `composer require gemorroj/archive7z`' . "\n"
42
                . ' and console program p7zip [7za]: `apt install p7zip-full` - depends on OS';
43
44
        if (Archive7z::getBinaryVersion() === false)
45
            return 'install console program p7zip [7za]: `apt install p7zip-full` - depends on OS';
46
47
        return null;
48
    }
49
50
    /**
51
     * @inheritDoc
52
     */
53
    public static function getDescription()
54
    {
55
        return 'php-library and console program'
56
            .(class_exists('\Archive7z\Archive7z') && ($version = Archive7z::getBinaryVersion()) !== false
57
                ? ' ('.$version.')'
58 3
                : null);
59
    }
60 3
61 3
    /**
62
     * @return array
63
     */
64
    public static function getSupportedFormats()
65 3
    {
66
        return [
67
            Formats::SEVEN_ZIP,
68
            Formats::ZIP,
69 3
//            Formats::RAR,
70 3
            Formats::TAR,
71 2
            // disabled
72 2
//            Formats::TAR_GZIP,
73
//            Formats::TAR_BZIP,
74
            Formats::CAB,
75 1
            Formats::ISO,
76 1
            Formats::ARJ,
77
            Formats::LZMA,
78
            Formats::UEFI,
79
            Formats::GPT,
80
            Formats::MBR,
81
            Formats::MSI,
82
            Formats::DMG,
83
            Formats::RPM,
84
            Formats::DEB,
85
            Formats::UDF,
86
        ];
87 3
    }
88
89
    /**
90
     * @param string $format
91
     * @return array
92
     * @throws \Archive7z\Exception
93
     */
94
    public static function checkFormatSupport($format)
95
    {
96
        if (!static::isInstalled()) {
97
            return [];
98
        }
99
100
        // in 4.0.0 version it was supporting only 7z
101
        if (!Archive7z::supportsAllFormats() && $format !== Formats::SEVEN_ZIP) {
102
            return [];
103
        }
104
105
        $abilities = [
106
            BasicDriver::OPEN,
107
            BasicDriver::EXTRACT_CONTENT,
108
        ];
109
110
        if (static::canRenameFiles()) {
111
            if (in_array($format, [Formats::SEVEN_ZIP, Formats::RAR, Formats::ZIP], true)) {
112
                $abilities[] = BasicDriver::OPEN_ENCRYPTED;
113
            }
114
115
            if (in_array($format, [Formats::ZIP, Formats::SEVEN_ZIP], true)) {
116
                $abilities[] = BasicDriver::CREATE_ENCRYPTED;
117
            }
118
119 6
            if (in_array($format, [Formats::SEVEN_ZIP,
120
                Formats::BZIP,
121
                Formats::GZIP,
122 6
                Formats::TAR,
123 6
                Formats::LZMA,
124 6
                Formats::ZIP], true)) {
125 6
                $abilities[] = BasicDriver::CREATE;
126
                $abilities[] = BasicDriver::APPEND;
127
                $abilities[] = BasicDriver::DELETE;
128
            }
129 6
130
            if ($format === Formats::SEVEN_ZIP) {
131
                $abilities[] = BasicDriver::GET_COMMENT;
132
                $abilities[] = BasicDriver::SET_COMMENT;
133
            }
134 6
135
        }
136 6
        return $abilities;
137
    }
138 6
139 6
    /**
140
     * @inheritDoc
141
     */
142
    public function __construct($archiveFileName, $format, $password = null)
143 6
    {
144 6
        try {
145
            $this->format = $format;
146 6
            $this->sevenZip = new Archive7z($archiveFileName, null, null);
147 6
            if ($password !== null)
148
                $this->sevenZip->setPassword($password);
149 6
        } catch (\Archive7z\Exception $e) {
0 ignored issues
show
Bug introduced by
The type Archive7z\Exception 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...
150 6
            throw new Exception('Could not open 7Zip archive: '.$e->getMessage(), $e->getCode(), $e);
151
        }
152 6
    }
153
154
    /**
155
     * @return ArchiveInformation
156
     */
157
    public function getArchiveInformation()
158
    {
159
        $information = new ArchiveInformation();
160
161
        foreach ($this->sevenZip->getEntries() as $entry) {
162
            if ($entry->isDirectory()) {
163
                continue;
164
            }
165
166
            if (!isset($can_get_unix_path))
167
                $can_get_unix_path = method_exists($entry, 'getUnixPath');
168
169
            $information->files[] = $can_get_unix_path
170
                ? $entry->getUnixPath()
171
                : str_replace('\\', '/', $entry->getPath());
172
            $information->compressedFilesSize += (int)$entry->getPackedSize();
173
            $information->uncompressedFilesSize += (int)$entry->getSize();
174
        }
175
        return $information;
176
    }
177
178
    /**
179
     * @return array
180
     */
181
    public function getFileNames()
182
    {
183
        $files = [];
184 1
        foreach ($this->sevenZip->getEntries() as $entry) {
185
            if ($entry->isDirectory())
186 1
                continue;
187 1
            $files[] = $entry->getPath();
188 1
        }
189
        return $files;
190
    }
191
192
    /**
193
     * @param string $fileName
194
     *
195
     * @return bool
196 1
     */
197
    public function isFileExists($fileName)
198 1
    {
199 1
        return $this->sevenZip->getEntry($fileName) !== null;
200
    }
201
202
    /**
203
     * @param string $fileName
204
     *
205
     * @return ArchiveEntry|false
206
     */
207 1
    public function getFileData($fileName)
208
    {
209 1
        $entry = $this->sevenZip->getEntry($fileName);
210 1
        return new ArchiveEntry(
211
            $fileName,
212
            $entry->getPackedSize(),
213
            $entry->getSize(),
214
            strtotime($entry->getModified()),
215
            $entry->getSize() !== $entry->getPackedSize(),
216
            $entry->getComment(),
217
            $entry->getCrc()
218
        );
219
    }
220
221
    /**
222
     * @param string $fileName
223
     *
224
     * @return string|false
225
     * @throws NonExistentArchiveFileException
226
     */
227
    public function getFileContent($fileName)
228
    {
229
        $entry = $this->sevenZip->getEntry($fileName);
230
        if ($entry === null) {
231
            throw new NonExistentArchiveFileException('File ' . $fileName . ' does not exist');
232
        }
233
        return $entry->getContent();
234
    }
235
236
    /**
237
     * @param string $fileName
238
     *
239
     * @return bool|resource|string
240
     */
241
    public function getFileStream($fileName)
242
    {
243
        $entry = $this->sevenZip->getEntry($fileName);
244
        return self::wrapStringInStream($entry->getContent());
245
    }
246
247
    /**
248
     * @param string $outputFolder
249
     * @param array $files
250
     * @return int
251
     * @throws ArchiveExtractionException
252
     */
253
    public function extractFiles($outputFolder, array $files)
254
    {
255
        $count = 0;
256
        try {
257
            $this->sevenZip->setOutputDirectory($outputFolder);
258
259
            foreach ($files as $file) {
260
                $this->sevenZip->extractEntry($file);
261
                $count++;
262
            }
263
            return $count;
264
        } catch (Exception $e) {
265
            throw new ArchiveExtractionException('Could not extract archive: '.$e->getMessage(), $e->getCode(), $e);
266
        }
267
    }
268
269
    /**
270
     * @param string $outputFolder
271
     *
272
     * @return bool
273
     * @throws ArchiveExtractionException
274
     */
275
    public function extractArchive($outputFolder)
276
    {
277
        try {
278
            $this->sevenZip->setOutputDirectory($outputFolder);
279
            $this->sevenZip->extract();
280
            return true;
0 ignored issues
show
Bug Best Practice introduced by
The expression return true returns the type true which is incompatible with the return type mandated by wapmorgan\UnifiedArchive...river::extractArchive() of integer.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
281
        } catch (Exception $e) {
282
            throw new ArchiveExtractionException('Could not extract archive: '.$e->getMessage(), $e->getCode(), $e);
283
        }
284
    }
285
286
    /**
287
     * @param array $files
288
     * @return int Number of deleted files
289
     * @throws ArchiveModificationException
290
     */
291
    public function deleteFiles(array $files)
292
    {
293
        $count = 0;
294
        try {
295
            foreach ($files as $file) {
296
                $this->sevenZip->delEntry($file);
297
                $count++;
298
            }
299
            return $count;
300
        } catch (Exception $e) {
301
            throw new ArchiveModificationException('Could not modify archive: '.$e->getMessage(), $e->getCode(), $e);
302
        }
303
    }
304
305
    /**
306
     * @param array $files
307
     *
308
     * @return int
309
     * @throws ArchiveModificationException
310
     */
311
    public function addFiles(array $files)
312
    {
313
        $added_files = 0;
314
        try {
315
            foreach ($files as $localName => $filename) {
316
                if (!is_null($filename)) {
317
                    $this->sevenZip->addEntry($filename);
318
                    $this->sevenZip->renameEntry($filename, $localName);
319
                    $added_files++;
320
                }
321
            }
322
            return $added_files;
323
        } catch (Exception $e) {
324
            throw new ArchiveModificationException('Could not modify archive: '.$e->getMessage(), $e->getCode(), $e);
325
        }
326
    }
327
328
    /**
329
     * @param string $inArchiveName
330
     * @param string $content
331
     * @return bool|void
332
     * @throws ArchiveModificationException
333
     * @throws \Archive7z\Exception
334
     */
335
    public function addFileFromString($inArchiveName, $content)
336
    {
337
        $tmp_file = tempnam(sys_get_temp_dir(), 'ua');
338
        if (!$tmp_file)
339
            throw new ArchiveModificationException('Could not create temporarily file');
340
341
        file_put_contents($tmp_file, $content);
342
        $this->sevenZip->addEntry($tmp_file, true);
343
        $this->sevenZip->renameEntry($tmp_file, $inArchiveName);
344
        unlink($tmp_file);
345
        return true;
346
    }
347
348
    /**
349
     * @param array $files
350
     * @param string $archiveFileName
351
     * @param int $archiveFormat
352
     * @param int $compressionLevel
353
     * @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...
354
     * @param $fileProgressCallable
355
     * @return int
356
     * @throws ArchiveCreationException
357
     * @throws UnsupportedOperationException
358
     */
359
    public static function createArchive(
360
        array $files,
361
        $archiveFileName,
362
        $archiveFormat,
363
        $compressionLevel = self::COMPRESSION_AVERAGE,
364
        $password = null,
365
        $fileProgressCallable = null
366
    )
367
    {
368
        static $compressionLevelMap = [
369
            self::COMPRESSION_NONE => 0,
370
            self::COMPRESSION_WEAK => 2,
371
            self::COMPRESSION_AVERAGE => 4,
372
            self::COMPRESSION_STRONG => 7,
373
            self::COMPRESSION_MAXIMUM => 9,
374
        ];
375
376
        if ($password !== null && !static::canEncrypt($archiveFormat)) {
0 ignored issues
show
introduced by
The condition $password !== null is always false.
Loading history...
377
            throw new UnsupportedOperationException('SevenZip could not encrypt an archive of '.$archiveFormat.' format');
378
        }
379
380
        if ($fileProgressCallable !== null && !is_callable($fileProgressCallable)) {
381
            throw new ArchiveCreationException('File progress callable is not callable');
382
        }
383
384
        try {
385
            $current_file = 0;
386
            $total_files = count($files);
387
388
            $seven_zip = new Archive7z($archiveFileName);
389
            if ($password !== null)
0 ignored issues
show
introduced by
The condition $password !== null is always false.
Loading history...
390
                $seven_zip->setPassword($password);
391
            $seven_zip->setCompressionLevel($compressionLevelMap[$compressionLevel]);
392
            foreach ($files as $localName => $filename) {
393
                if ($filename !== null) {
394
                    $seven_zip->addEntry($filename, true);
395
                    $seven_zip->renameEntry($filename, $localName);
396
                }
397
                if ($fileProgressCallable !== null) {
398
                    call_user_func_array($fileProgressCallable, [$current_file++, $total_files, $filename, $localName]);
399
                }
400
            }
401
            unset($seven_zip);
402
        } catch (Exception $e) {
403
            throw new ArchiveCreationException('Could not create archive: '.$e->getMessage(), $e->getCode(), $e);
404
        }
405
        return count($files);
406
    }
407
408
    /**
409
     * @return bool
410
     * @throws \Archive7z\Exception
411
     */
412
    protected static function canRenameFiles()
413
    {
414
        $version = Archive7z::getBinaryVersion();
415
        return $version !== false && version_compare('9.30', $version, '<=');
416
    }
417
418
    /**
419
     * @param $format
420
     * @return bool
421
     * @throws \Archive7z\Exception
422
     */
423
    public static function canEncrypt($format)
424
    {
425
        return in_array($format, [Formats::ZIP, Formats::SEVEN_ZIP]) && self::canRenameFiles();
426
    }
427
428
    /**
429
     * @return string|null
430
     */
431
    public function getComment()
432
    {
433
        if ($this->format !== Formats::SEVEN_ZIP) {
434
            return parent::getComment();
0 ignored issues
show
Bug introduced by
Are you sure the usage of parent::getComment() targeting wapmorgan\UnifiedArchive...sicDriver::getComment() 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...
435
        }
436
        try {
437
            return $this->getFileContent(static::COMMENT_FILE);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getFileCon...t(static::COMMENT_FILE) could also return false which is incompatible with the documented return type null|string. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
438
        } catch (NonExistentArchiveFileException $e) {
439
            return null;
440
        }
441
    }
442
443
    /**
444
     * @param string|null $comment
445
     * @return null
446
     * @throws ArchiveModificationException
447
     * @throws \Archive7z\Exception
448
     */
449
    public function setComment($comment)
450
    {
451
        if ($this->format !== Formats::SEVEN_ZIP) {
452
            return null;
453
        }
454
        $this->addFileFromString(static::COMMENT_FILE, $comment);
455
    }
456
}