Issues (155)

src/Drivers/SevenZip.php (1 issue)

Checks if the types of returned expressions are compatible with the documented types.

Best Practice Bug Major
1
<?php
2
namespace wapmorgan\UnifiedArchive\Drivers;
3
4
use Exception;
5
use wapmorgan\UnifiedArchive\Abilities;
6
use wapmorgan\UnifiedArchive\Archive7z;
7
use wapmorgan\UnifiedArchive\ArchiveEntry;
8
use wapmorgan\UnifiedArchive\ArchiveInformation;
9
use wapmorgan\UnifiedArchive\Drivers\Basic\BasicDriver;
10
use wapmorgan\UnifiedArchive\Drivers\Basic\BasicUtilityDriver;
11
use wapmorgan\UnifiedArchive\Exceptions\ArchiveCreationException;
12
use wapmorgan\UnifiedArchive\Exceptions\ArchiveExtractionException;
13
use wapmorgan\UnifiedArchive\Exceptions\ArchiveModificationException;
14
use wapmorgan\UnifiedArchive\Exceptions\NonExistentArchiveFileException;
15
use wapmorgan\UnifiedArchive\Exceptions\UnsupportedOperationException;
16
use wapmorgan\UnifiedArchive\Formats;
17
18
class SevenZip extends BasicUtilityDriver
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 getFormats()
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 getFormatAbilities($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
            Abilities::OPEN,
107
            Abilities::EXTRACT_CONTENT,
108
        ];
109
110
        if (static::canRenameFiles()) {
111
            if (in_array($format, [Formats::SEVEN_ZIP, Formats::RAR, Formats::ZIP], true)) {
112
                $abilities[] = Abilities::OPEN_ENCRYPTED;
113
            }
114
115
            if (in_array($format, [Formats::ZIP, Formats::SEVEN_ZIP], true)) {
116
                $abilities[] = Abilities::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[] = Abilities::CREATE;
126
                $abilities[] = Abilities::APPEND;
127
                $abilities[] = Abilities::DELETE;
128
            }
129 6
130
            if ($format === Formats::SEVEN_ZIP) {
131
                $abilities[] = Abilities::GET_COMMENT;
132
                $abilities[] = Abilities::SET_COMMENT;
133
            }
134 6
135
        }
136 6
        return $abilities;
137
    }
138 6
139 6
    /**
140
     * @inheritDoc
141
     * @throws Exception
142
     */
143 6
    public function __construct($archiveFileName, $format, $password = null)
144 6
    {
145
        parent::__construct($archiveFileName, $format);
146 6
        try {
147 6
            $this->format = $format;
148
            $this->sevenZip = new Archive7z($archiveFileName, null, null);
149 6
            if ($password !== null)
150 6
                $this->sevenZip->setPassword($password);
151
        } catch (\Archive7z\Exception $e) {
152 6
            throw new Exception('Could not open 7Zip archive: '.$e->getMessage(), $e->getCode(), $e);
153
        }
154
    }
155
156
    /**
157
     * @return ArchiveInformation
158
     */
159
    public function getArchiveInformation()
160
    {
161
        $information = new ArchiveInformation();
162
163
        foreach ($this->sevenZip->getEntries() as $entry) {
164
            if ($entry->isDirectory()) {
165
                continue;
166
            }
167
168
            if (!isset($can_get_unix_path))
169
                $can_get_unix_path = method_exists($entry, 'getUnixPath');
170
171
            $information->files[] = $can_get_unix_path
172
                ? $entry->getUnixPath()
173
                : str_replace('\\', '/', $entry->getPath());
174
            $information->compressedFilesSize += (int)$entry->getPackedSize();
175
            $information->uncompressedFilesSize += (int)$entry->getSize();
176
        }
177
        return $information;
178
    }
179
180
    /**
181
     * @return array
182
     */
183
    public function getFileNames()
184 1
    {
185
        $files = [];
186 1
        foreach ($this->sevenZip->getEntries() as $entry) {
187 1
            if ($entry->isDirectory())
188 1
                continue;
189
            $files[] = $entry->getPath();
190
        }
191
        return $files;
192
    }
193
194
    /**
195
     * @param string $fileName
196 1
     *
197
     * @return bool
198 1
     */
199 1
    public function isFileExists($fileName)
200
    {
201
        return $this->sevenZip->getEntry($fileName) !== null;
202
    }
203
204
    /**
205
     * @param string $fileName
206
     *
207 1
     * @return ArchiveEntry|false
208
     */
209 1
    public function getFileData($fileName)
210 1
    {
211
        $entry = $this->sevenZip->getEntry($fileName);
212
        return new ArchiveEntry(
213
            $fileName,
214
            $entry->getPackedSize(),
215
            $entry->getSize(),
216
            strtotime($entry->getModified()),
217
            $entry->getSize() !== $entry->getPackedSize(),
218
            $entry->getComment(),
219
            $this->format === Formats::ZIP ? $entry->getCrc() : null
220
        );
221
    }
222
223
    /**
224
     * @param string $fileName
225
     *
226
     * @return string|false
227
     * @throws NonExistentArchiveFileException
228
     */
229
    public function getFileContent($fileName)
230
    {
231
        $entry = $this->sevenZip->getEntry($fileName);
232
        if ($entry === null) {
233
            throw new NonExistentArchiveFileException('File ' . $fileName . ' does not exist');
234
        }
235
        return $entry->getContent();
236
    }
237
238
    /**
239
     * @param string $fileName
240
     *
241
     * @return bool|resource|string
242
     */
243
    public function getFileStream($fileName)
244
    {
245
        $entry = $this->sevenZip->getEntry($fileName);
246
        return self::wrapStringInStream($entry->getContent());
247
    }
248
249
    /**
250
     * @param string $outputFolder
251
     * @param array $files
252
     * @return int
253
     * @throws ArchiveExtractionException
254
     */
255
    public function extractFiles($outputFolder, array $files)
256
    {
257
        $count = 0;
258
        try {
259
            $this->sevenZip->setOutputDirectory($outputFolder);
260
261
            foreach ($files as $file) {
262
                $this->sevenZip->extractEntry($file);
263
                $count++;
264
            }
265
            return $count;
266
        } catch (Exception $e) {
267
            throw new ArchiveExtractionException('Could not extract archive: '.$e->getMessage(), $e->getCode(), $e);
268
        }
269
    }
270
271
    /**
272
     * @param string $outputFolder
273
     *
274
     * @return bool
275
     * @throws ArchiveExtractionException
276
     */
277
    public function extractArchive($outputFolder)
278
    {
279
        try {
280
            $this->sevenZip->setOutputDirectory($outputFolder);
281
            $this->sevenZip->extract();
282
            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...
283
        } catch (Exception $e) {
284
            throw new ArchiveExtractionException('Could not extract archive: '.$e->getMessage(), $e->getCode(), $e);
285
        }
286
    }
287
288
    /**
289
     * @param array $files
290
     * @return int Number of deleted files
291
     * @throws ArchiveModificationException
292
     */
293
    public function deleteFiles(array $files)
294
    {
295
        $count = 0;
296
        try {
297
            foreach ($files as $file) {
298
                $this->sevenZip->delEntry($file);
299
                $count++;
300
            }
301
            return $count;
302
        } catch (Exception $e) {
303
            throw new ArchiveModificationException('Could not modify archive: '.$e->getMessage(), $e->getCode(), $e);
304
        }
305
    }
306
307
    /**
308
     * @param array $files
309
     *
310
     * @return int
311
     * @throws ArchiveModificationException
312
     */
313
    public function addFiles(array $files)
314
    {
315
        $added_files = 0;
316
        try {
317
            foreach ($files as $localName => $filename) {
318
                if (!is_null($filename)) {
319
                    $this->sevenZip->addEntry($filename);
320
                    $this->sevenZip->renameEntry($filename, $localName);
321
                    $added_files++;
322
                }
323
            }
324
            return $added_files;
325
        } catch (Exception $e) {
326
            throw new ArchiveModificationException('Could not modify archive: '.$e->getMessage(), $e->getCode(), $e);
327
        }
328
    }
329
330
    /**
331
     * @param string $inArchiveName
332
     * @param string $content
333
     * @return bool|void
334
     * @throws ArchiveModificationException
335
     * @throws \Archive7z\Exception
336
     */
337
    public function addFileFromString($inArchiveName, $content)
338
    {
339
        $tmp_file = tempnam(sys_get_temp_dir(), 'ua');
340
        if (!$tmp_file)
341
            throw new ArchiveModificationException('Could not create temporarily file');
342
343
        file_put_contents($tmp_file, $content);
344
        $this->sevenZip->addEntry($tmp_file, true);
345
        $this->sevenZip->renameEntry($tmp_file, $inArchiveName);
346
        unlink($tmp_file);
347
        return true;
348
    }
349
350
    /**
351
     * @param array $files
352
     * @param string $archiveFileName
353
     * @param int $archiveFormat
354
     * @param int $compressionLevel
355
     * @param null $password
356
     * @param $fileProgressCallable
357
     * @return int
358
     * @throws ArchiveCreationException
359
     * @throws UnsupportedOperationException
360
     */
361
    public static function createArchive(
362
        array $files,
363
        $archiveFileName,
364
        $archiveFormat,
365
        $compressionLevel = self::COMPRESSION_AVERAGE,
366
        $password = null,
367
        $fileProgressCallable = null
368
    )
369
    {
370
        static $compressionLevelMap = [
371
            self::COMPRESSION_NONE => 0,
372
            self::COMPRESSION_WEAK => 2,
373
            self::COMPRESSION_AVERAGE => 4,
374
            self::COMPRESSION_STRONG => 7,
375
            self::COMPRESSION_MAXIMUM => 9,
376
        ];
377
378
        if ($password !== null && !static::canEncrypt($archiveFormat)) {
379
            throw new UnsupportedOperationException('SevenZip could not encrypt an archive of '.$archiveFormat.' format');
380
        }
381
382
        if ($fileProgressCallable !== null && !is_callable($fileProgressCallable)) {
383
            throw new ArchiveCreationException('File progress callable is not callable');
384
        }
385
386
        try {
387
            $current_file = 0;
388
            $total_files = count($files);
389
390
            $seven_zip = new Archive7z($archiveFileName);
391
            if ($password !== null)
392
                $seven_zip->setPassword($password);
393
            $seven_zip->setCompressionLevel($compressionLevelMap[$compressionLevel]);
394
            foreach ($files as $archiveName => $localName) {
395
                if ($localName !== null) {
396
                    $seven_zip->addEntry($localName, true);
397
                    $seven_zip->renameEntry($localName, $archiveName);
398
                }
399
                if ($fileProgressCallable !== null) {
400
                    call_user_func_array($fileProgressCallable, [$current_file++, $total_files, $localName, $archiveName]);
401
                }
402
            }
403
            unset($seven_zip);
404
        } catch (Exception $e) {
405
            throw new ArchiveCreationException('Could not create archive: '.$e->getMessage(), $e->getCode(), $e);
406
        }
407
        return count($files);
408
    }
409
410
    /**
411
     * @return bool
412
     * @throws \Archive7z\Exception
413
     */
414
    protected static function canRenameFiles()
415
    {
416
        $version = Archive7z::getBinaryVersion();
417
        return $version !== false && version_compare('9.30', $version, '<=');
418
    }
419
420
    /**
421
     * @param $format
422
     * @return bool
423
     * @throws \Archive7z\Exception
424
     */
425
    public static function canEncrypt($format)
426
    {
427
        return in_array($format, [Formats::ZIP, Formats::SEVEN_ZIP]) && self::canRenameFiles();
428
    }
429
430
    /**
431
     * @return string|null
432
     */
433
    public function getComment()
434
    {
435
        if ($this->format !== Formats::SEVEN_ZIP) {
436
            return null;
437
        }
438
        try {
439
            return $this->getFileContent(static::COMMENT_FILE);
440
        } catch (NonExistentArchiveFileException $e) {
441
            return null;
442
        }
443
    }
444
445
    /**
446
     * @param string|null $comment
447
     * @return null
448
     * @throws ArchiveModificationException
449
     * @throws \Archive7z\Exception
450
     */
451
    public function setComment($comment)
452
    {
453
        if ($this->format !== Formats::SEVEN_ZIP) {
454
            return null;
455
        }
456
        $this->addFileFromString(static::COMMENT_FILE, $comment);
457
    }
458
}
459