Passed
Push — master ( b1d4f6...7824cd )
by f
10:25
created

SevenZip::canCreateArchive()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 9
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 13
ccs 0
cts 2
cp 0
crap 6
rs 9.9666

1 Method

Rating   Name   Duplication   Size   Complexity  
A SevenZip::canEncrypt() 0 3 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
    /** @var Archive7z */
19
    protected $sevenZip;
20
21
    /**
22
     * @var string
23
     */
24
    protected $format;
25
26
    const COMMENT_FILE = 'descript.ion';
27
28 1
    /**
29
     * @return array
30
     */
31 1
    public static function getSupportedFormats()
32
    {
33
        return [
34
            Formats::SEVEN_ZIP,
35
            Formats::ZIP,
36
//            Formats::RAR,
37
            Formats::TAR,
38
            // disabled
39
//            Formats::TAR_GZIP,
40
//            Formats::TAR_BZIP,
41
            Formats::CAB,
42
            Formats::ISO,
43
            Formats::ARJ,
44
            Formats::LZMA,
45
            Formats::UEFI,
46
            Formats::GPT,
47
            Formats::MBR,
48
            Formats::MSI,
49
            Formats::DMG,
50
            Formats::RPM,
51
            Formats::DEB,
52
            Formats::UDF,
53
        ];
54
    }
55
56
    /**
57
     * @param string $format
58 3
     * @return array
59
     * @throws \Archive7z\Exception
60 3
     */
61 3
    public static function checkFormatSupport($format)
62
    {
63
        $available = class_exists('\Archive7z\Archive7z') && Archive7z::getBinaryVersion() !== false;
64
        if (!$available) {
65 3
            return [];
66
        }
67
68
        // in 4.0.0 version it was supporting only 7z
69 3
        if (!Archive7z::supportsAllFormats() && $format !== Formats::SEVEN_ZIP) {
70 3
            return [];
71 2
        }
72 2
73
        $abilities = [
74
            BasicDriver::OPEN,
75 1
            BasicDriver::EXTRACT_CONTENT,
76 1
        ];
77
78
        if (static::canRenameFiles()) {
79
            if (in_array($format, [Formats::SEVEN_ZIP, Formats::RAR, Formats::ZIP], true)) {
80
                $abilities[] = BasicDriver::OPEN_ENCRYPTED;
81
            }
82
83
            if (in_array($format, [Formats::ZIP, Formats::SEVEN_ZIP], true)) {
84
                $abilities[] = BasicDriver::CREATE_ENCRYPTED;
85
            }
86
87 3
            if (in_array($format, [Formats::SEVEN_ZIP,
88
                Formats::BZIP,
89
                Formats::GZIP,
90
                Formats::TAR,
91
                Formats::LZMA,
92
                Formats::ZIP], true)) {
93
                $abilities[] = BasicDriver::CREATE;
94
                $abilities[] = BasicDriver::APPEND;
95
                $abilities[] = BasicDriver::DELETE;
96
            }
97
98
            if ($format === Formats::SEVEN_ZIP) {
99
                $abilities[] = BasicDriver::GET_COMMENT;
100
                $abilities[] = BasicDriver::SET_COMMENT;
101
            }
102
103
        }
104
        return $abilities;
105
    }
106
107
    /**
108
     * @inheritDoc
109
     */
110
    public static function getDescription()
111
    {
112
        return 'php-library and console program'
113
            .(class_exists('\Archive7z\Archive7z') && ($version = Archive7z::getBinaryVersion()) !== false
114
                ? ' ('.$version.')'
115
                : null);
116
    }
117
118
    /**
119 6
     * @inheritDoc
120
     */
121
    public static function getInstallationInstruction()
122 6
    {
123 6
        if (!class_exists('\Archive7z\Archive7z'))
124 6
            return 'install library [gemorroj/archive7z]: `composer require gemorroj/archive7z`' . "\n"
125 6
                . ' and console program p7zip [7za]: `apt install p7zip-full` - depends on OS';
126
127
        if (Archive7z::getBinaryVersion() === false)
128
            return 'install console program p7zip [7za]: `apt install p7zip-full` - depends on OS';
129 6
130
        return null;
131
    }
132
133
    /**
134 6
     * @inheritDoc
135
     */
136 6
    public function __construct($archiveFileName, $format, $password = null)
137
    {
138 6
        try {
139 6
            $this->format = $format;
140
            $this->sevenZip = new Archive7z($archiveFileName, null, null);
141
            if ($password !== null)
142
                $this->sevenZip->setPassword($password);
143 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...
144 6
            throw new Exception('Could not open 7Zip archive: '.$e->getMessage(), $e->getCode(), $e);
145
        }
146 6
    }
147 6
148
    /**
149 6
     * @return ArchiveInformation
150 6
     */
151
    public function getArchiveInformation()
152 6
    {
153
        $information = new ArchiveInformation();
154
155
        foreach ($this->sevenZip->getEntries() as $entry) {
156
            if ($entry->isDirectory()) {
157
                continue;
158
            }
159
160
            if (!isset($can_get_unix_path))
161
                $can_get_unix_path = method_exists($entry, 'getUnixPath');
162
163
            $information->files[] = $can_get_unix_path
164
                ? $entry->getUnixPath()
165
                : str_replace('\\', '/', $entry->getPath());
166
            $information->compressedFilesSize += (int)$entry->getPackedSize();
167
            $information->uncompressedFilesSize += (int)$entry->getSize();
168
        }
169
        return $information;
170
    }
171
172
    /**
173
     * @return array
174
     */
175
    public function getFileNames()
176
    {
177
        $files = [];
178
        foreach ($this->sevenZip->getEntries() as $entry) {
179
            if ($entry->isDirectory())
180
                continue;
181
            $files[] = $entry->getPath();
182
        }
183
        return $files;
184 1
    }
185
186 1
    /**
187 1
     * @param string $fileName
188 1
     *
189
     * @return bool
190
     */
191
    public function isFileExists($fileName)
192
    {
193
        return $this->sevenZip->getEntry($fileName) !== null;
194
    }
195
196 1
    /**
197
     * @param string $fileName
198 1
     *
199 1
     * @return ArchiveEntry|false
200
     */
201
    public function getFileData($fileName)
202
    {
203
        $entry = $this->sevenZip->getEntry($fileName);
204
        return new ArchiveEntry(
205
            $fileName,
206
            $entry->getPackedSize(),
207 1
            $entry->getSize(),
208
            strtotime($entry->getModified()),
209 1
            $entry->getSize() !== $entry->getPackedSize(),
210 1
            $entry->getComment(),
211
            $entry->getCrc()
212
        );
213
    }
214
215
    /**
216
     * @param string $fileName
217
     *
218
     * @return string|false
219
     * @throws NonExistentArchiveFileException
220
     */
221
    public function getFileContent($fileName)
222
    {
223
        $entry = $this->sevenZip->getEntry($fileName);
224
        if ($entry === null) {
225
            throw new NonExistentArchiveFileException('File ' . $fileName . ' does not exist');
226
        }
227
        return $entry->getContent();
228
    }
229
230
    /**
231
     * @param string $fileName
232
     *
233
     * @return bool|resource|string
234
     */
235
    public function getFileStream($fileName)
236
    {
237
        $entry = $this->sevenZip->getEntry($fileName);
238
        return self::wrapStringInStream($entry->getContent());
239
    }
240
241
    /**
242
     * @param string $outputFolder
243
     * @param array $files
244
     * @return int
245
     * @throws ArchiveExtractionException
246
     */
247
    public function extractFiles($outputFolder, array $files)
248
    {
249
        $count = 0;
250
        try {
251
            $this->sevenZip->setOutputDirectory($outputFolder);
252
253
            foreach ($files as $file) {
254
                $this->sevenZip->extractEntry($file);
255
                $count++;
256
            }
257
            return $count;
258
        } catch (Exception $e) {
259
            throw new ArchiveExtractionException('Could not extract archive: '.$e->getMessage(), $e->getCode(), $e);
260
        }
261
    }
262
263
    /**
264
     * @param string $outputFolder
265
     *
266
     * @return bool
267
     * @throws ArchiveExtractionException
268
     */
269
    public function extractArchive($outputFolder)
270
    {
271
        try {
272
            $this->sevenZip->setOutputDirectory($outputFolder);
273
            $this->sevenZip->extract();
274
            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...
275
        } catch (Exception $e) {
276
            throw new ArchiveExtractionException('Could not extract archive: '.$e->getMessage(), $e->getCode(), $e);
277
        }
278
    }
279
280
    /**
281
     * @param array $files
282
     * @return int Number of deleted files
283
     * @throws ArchiveModificationException
284
     */
285
    public function deleteFiles(array $files)
286
    {
287
        $count = 0;
288
        try {
289
            foreach ($files as $file) {
290
                $this->sevenZip->delEntry($file);
291
                $count++;
292
            }
293
            return $count;
294
        } catch (Exception $e) {
295
            throw new ArchiveModificationException('Could not modify archive: '.$e->getMessage(), $e->getCode(), $e);
296
        }
297
    }
298
299
    /**
300
     * @param array $files
301
     *
302
     * @return int
303
     * @throws ArchiveModificationException
304
     */
305
    public function addFiles(array $files)
306
    {
307
        $added_files = 0;
308
        try {
309
            foreach ($files as $localName => $filename) {
310
                if (!is_null($filename)) {
311
                    $this->sevenZip->addEntry($filename);
312
                    $this->sevenZip->renameEntry($filename, $localName);
313
                    $added_files++;
314
                }
315
            }
316
            return $added_files;
317
        } catch (Exception $e) {
318
            throw new ArchiveModificationException('Could not modify archive: '.$e->getMessage(), $e->getCode(), $e);
319
        }
320
    }
321
322
    /**
323
     * @param string $inArchiveName
324
     * @param string $content
325
     * @return bool|void
326
     * @throws ArchiveModificationException
327
     * @throws \Archive7z\Exception
328
     */
329
    public function addFileFromString($inArchiveName, $content)
330
    {
331
        $tmp_file = tempnam(sys_get_temp_dir(), 'ua');
332
        if (!$tmp_file)
333
            throw new ArchiveModificationException('Could not create temporarily file');
334
335
        file_put_contents($tmp_file, $content);
336
        $this->sevenZip->addEntry($tmp_file, true);
337
        $this->sevenZip->renameEntry($tmp_file, $inArchiveName);
338
        unlink($tmp_file);
339
        return true;
340
    }
341
342
    /**
343
     * @param array $files
344
     * @param string $archiveFileName
345
     * @param int $archiveFormat
346
     * @param int $compressionLevel
347
     * @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...
348
     * @param $fileProgressCallable
349
     * @return int
350
     * @throws ArchiveCreationException
351
     * @throws UnsupportedOperationException
352
     */
353
    public static function createArchive(
354
        array $files,
355
        $archiveFileName,
356
        $archiveFormat,
357
        $compressionLevel = self::COMPRESSION_AVERAGE,
358
        $password = null,
359
        $fileProgressCallable = null
360
    )
361
    {
362
        static $compressionLevelMap = [
363
            self::COMPRESSION_NONE => 0,
364
            self::COMPRESSION_WEAK => 2,
365
            self::COMPRESSION_AVERAGE => 4,
366
            self::COMPRESSION_STRONG => 7,
367
            self::COMPRESSION_MAXIMUM => 9,
368
        ];
369
370
        if ($password !== null && !static::canEncrypt($archiveFormat)) {
0 ignored issues
show
introduced by
The condition $password !== null is always false.
Loading history...
371
            throw new UnsupportedOperationException('SevenZip could not encrypt an archive of '.$archiveFormat.' format');
372
        }
373
374
        if ($fileProgressCallable !== null && !is_callable($fileProgressCallable)) {
375
            throw new ArchiveCreationException('File progress callable is not callable');
376
        }
377
378
        try {
379
            $current_file = 0;
380
            $total_files = count($files);
381
382
            $seven_zip = new Archive7z($archiveFileName);
383
            if ($password !== null)
0 ignored issues
show
introduced by
The condition $password !== null is always false.
Loading history...
384
                $seven_zip->setPassword($password);
385
            $seven_zip->setCompressionLevel($compressionLevelMap[$compressionLevel]);
386
            foreach ($files as $localName => $filename) {
387
                if ($filename !== null) {
388
                    $seven_zip->addEntry($filename, true);
389
                    $seven_zip->renameEntry($filename, $localName);
390
                }
391
                if ($fileProgressCallable !== null) {
392
                    call_user_func_array($fileProgressCallable, [$current_file++, $total_files, $filename, $localName]);
393
                }
394
            }
395
            unset($seven_zip);
396
        } catch (Exception $e) {
397
            throw new ArchiveCreationException('Could not create archive: '.$e->getMessage(), $e->getCode(), $e);
398
        }
399
        return count($files);
400
    }
401
402
    /**
403
     * @return bool
404
     * @throws \Archive7z\Exception
405
     */
406
    protected static function canRenameFiles()
407
    {
408
        $version = Archive7z::getBinaryVersion();
409
        return $version !== false && version_compare('9.30', $version, '<=');
410
    }
411
412
    /**
413
     * @param $format
414
     * @return bool
415
     * @throws \Archive7z\Exception
416
     */
417
    public static function canEncrypt($format)
418
    {
419
        return in_array($format, [Formats::ZIP, Formats::SEVEN_ZIP]) && self::canRenameFiles();
420
    }
421
422
    /**
423
     * @return string|null
424
     */
425
    public function getComment()
426
    {
427
        if ($this->format !== Formats::SEVEN_ZIP) {
428
            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...
429
        }
430
        try {
431
            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...
432
        } catch (NonExistentArchiveFileException $e) {
433
            return null;
434
        }
435
    }
436
437
    /**
438
     * @param string|null $comment
439
     * @return null
440
     * @throws ArchiveModificationException
441
     * @throws \Archive7z\Exception
442
     */
443
    public function setComment($comment)
444
    {
445
        if ($this->format !== Formats::SEVEN_ZIP) {
446
            return null;
447
        }
448
        $this->addFileFromString(static::COMMENT_FILE, $comment);
449
    }
450
}