UnifiedArchive   F
last analyzed

Complexity

Total Complexity 141

Size/Duplication

Total Lines 1159
Duplicated Lines 0 %

Test Coverage

Coverage 52.4%

Importance

Changes 68
Bugs 2 Features 3
Metric Value
wmc 141
eloc 273
c 68
b 2
f 3
dl 0
loc 1159
ccs 109
cts 208
cp 0.524
rs 2

65 Methods

Rating   Name   Duplication   Size   Complexity  
A create() 0 22 2
A createInString() 0 22 2
A getCreationDriver() 0 24 6
A open() 0 17 6
B prepareForArchiving() 0 27 7
A canOpen() 0 6 4
A canCreateType() 0 3 1
A setComment() 0 3 1
A add() 0 11 2
A getOriginalSize() 0 3 1
A deleteFiles() 0 3 1
A test() 0 18 6
A getArchiveFormat() 0 3 1
A archiveFiles() 0 9 1
A valid() 0 4 1
A getArchiveSize() 0 3 1
A countFiles() 0 3 1
B importFilesFromDir() 0 14 7
A detectArchiveType() 0 3 1
A extract() 0 15 5
A archive() 0 9 1
A delete() 0 16 5
A archiveDirectory() 0 6 3
A getPclZipInterface() 0 3 1
A can() 0 3 1
A addFile() 0 8 3
A expandFileList() 0 11 4
A scanArchive() 0 7 1
A count() 0 4 1
A next() 0 4 1
A addDirectory() 0 8 4
A getFormat() 0 3 1
A countUncompressedFilesSize() 0 3 1
A addFiles() 0 3 1
A key() 0 4 1
A getSize() 0 3 1
A isFileExists() 0 3 1
C createFilesList() 0 56 15
A getArchiveType() 0 3 1
A getFiles() 0 11 4
A addFileFromString() 0 5 1
A getFileStream() 0 7 2
A current() 0 4 1
A getFileResource() 0 3 1
A getFileNames() 0 3 1
A offsetSet() 0 4 1
A canOpenArchive() 0 3 1
A getDriver() 0 3 1
A archiveFile() 0 7 2
A getMimeType() 0 3 1
A hasFile() 0 3 1
A getFileData() 0 7 2
A offsetExists() 0 4 1
A offsetGet() 0 4 1
A getDriverType() 0 3 1
A canOpenType() 0 3 1
A getFileContent() 0 7 2
A countCompressedFilesSize() 0 3 1
A __construct() 0 32 5
A rewind() 0 4 1
A __destruct() 0 3 1
A offsetUnset() 0 4 1
A getCompressedSize() 0 3 1
A getComment() 0 3 1
A extractFiles() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like UnifiedArchive often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use UnifiedArchive, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace wapmorgan\UnifiedArchive;
3
4
use ArrayAccess;
5
use Countable;
6
use InvalidArgumentException;
7
use Iterator;
8
use wapmorgan\UnifiedArchive\Drivers\Basic\BasicDriver;
9
use wapmorgan\UnifiedArchive\Exceptions\ArchiveExtractionException;
10
use wapmorgan\UnifiedArchive\Exceptions\ArchiveModificationException;
11
use wapmorgan\UnifiedArchive\Exceptions\EmptyFileListException;
12
use wapmorgan\UnifiedArchive\Exceptions\FileAlreadyExistsException;
13
use wapmorgan\UnifiedArchive\Exceptions\NonExistentArchiveFileException;
14
use wapmorgan\UnifiedArchive\Exceptions\UnsupportedArchiveException;
15
use wapmorgan\UnifiedArchive\Exceptions\UnsupportedOperationException;
16
17
/**
18
 * Class which represents archive in one of supported formats.
19
 */
20
class UnifiedArchive implements ArrayAccess, Iterator, Countable
21
{
22
    const VERSION = '1.2.0';
23
24
    /** @var string Type of current archive */
25
    protected $format;
26
27
    /** @var BasicDriver Adapter for current archive */
28
    protected $archive;
29
30
    /** @var array List of files in current archive */
31
    protected $files;
32
33
    /**
34
     * @var int
35
     */
36
    protected $filesIterator = 0;
37
38
    /** @var int Number of files in archive */
39
    protected $filesQuantity;
40
41
    /** @var int Cumulative size of uncompressed files */
42
    protected $uncompressedFilesSize;
43
44
    /** @var int Cumulative size of compressed files */
45
    protected $compressedFilesSize;
46
47
    /** @var int Total size of archive file */
48
    protected $archiveSize;
49
50
    /** @var BasicDriver */
51
    protected $driver;
52
53
    /** @var string|null */
54
    private $password;
55
56
    /**
57
     * Creates a UnifiedArchive instance for passed archive
58
     *
59
     * @param string $fileName Archive filename
60
     * @param array|null|string $abilities List of supported abilities by driver. If passed string, used as password.
61
     * @param string|null $password Password to open archive
62 23
     * @return UnifiedArchive|null Returns UnifiedArchive in case of successful reading of the file
63
     */
64 23
    public static function open($fileName, $abilities = [], $password = null)
65
    {
66
        if (!file_exists($fileName) || !is_readable($fileName)) {
67
            throw new InvalidArgumentException('Could not open file: ' . $fileName . ' is not readable');
68 23
        }
69 23
70
        $format = Formats::detectArchiveFormat($fileName);
71
        if ($format === null) {
72
            return null;
73 23
        }
74
75
        if (!empty($abilities) && is_string($abilities)) {
76
            $password = $abilities;
77
            $abilities = [];
78
        }
79
80
        return new static($fileName, $format, $abilities, $password);
81
    }
82 21
83
    /**
84 21
     * Checks whether archive can be opened with current system configuration
85 21
     *
86
     * @param string $fileName Archive filename
87
     * @return bool
88
     */
89
    public static function canOpen($fileName, $passwordProtected = false)
90
    {
91
        $format = Formats::detectArchiveFormat($fileName);
92
        return $format !== null
93
            && Formats::canOpen($format)
94
            && (!$passwordProtected || Formats::can($format, Abilities::OPEN_ENCRYPTED));
95 23
    }
96
97 23
    /**
98 23
     * Prepare files list for archiving
99
     *
100
     * @param string|array $fileOrFiles File of list of files. See [[archiveFiles]] for details.
101
     * @param string|null $archiveName File name of archive. See [[archiveFiles]] for details.
102 23
     * @return array An array containing entries:
103 23
     * - totalSize (int) - size in bytes for all files
104 23
     * - numberOfFiles (int) - quantity of files
105
     * - files (array) - list of files prepared for archiving
106
     * - type (string|null) - prepared format for archive. One of class constants
107 23
     * @throws EmptyFileListException
108 23
     * @throws UnsupportedArchiveException
109 23
     */
110
    public static function prepareForArchiving($fileOrFiles, $archiveName = null)
111
    {
112
        if ($archiveName !== null) {
113
            $archiveType = Formats::detectArchiveFormat($archiveName, false);
114 23
            if ($archiveType === null) {
115
                throw new UnsupportedArchiveException('Could not detect archive type for name "' . $archiveName . '"');
116 23
            }
117 23
        }
118 23
119 23
        $files_list = static::createFilesList($fileOrFiles);
120 23
121 23
        if (empty($files_list)) {
122
            throw new EmptyFileListException('Files list is empty!');
123
        }
124
125
        $totalSize = 0;
126 23
        foreach ($files_list as $fn) {
127
            if ($fn !== null) {
128 23
                $totalSize += filesize($fn);
129 23
            }
130
        }
131
132
        return [
133
            'totalSize' => $totalSize,
134
            'numberOfFiles' => count($files_list),
0 ignored issues
show
Bug introduced by
It seems like $files_list can also be of type boolean; however, parameter $value of count() does only seem to accept Countable|array, 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

134
            'numberOfFiles' => count(/** @scrutinizer ignore-type */ $files_list),
Loading history...
135
            'files' => $files_list,
136
            'type' => $archiveName !== null ? $archiveType : null,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $archiveType does not seem to be defined for all execution paths leading up to this point.
Loading history...
137
        ];
138
    }
139
140
    /**
141
     * Creates an archive with passed files list
142
     *
143
     * @param string|string[]|array<string,string>|array<string,string[]> $fileOrFiles List of files.
144
     *  Can be one of three formats:
145 2
     *  1. A string containing path to file or directory.
146
     *     File will have it's basename.
147 2
     *      `UnifiedArchive::create('/etc/php.ini', 'archive.zip)` will store file with 'php.ini' name.
148
     *     Directory contents will be populated in archive root.
149
     *      `UnifiedArchive::create('/var/log/', 'archive.zip')` will store all directory contents in archive root.
150
     *  2. An array with strings containing paths to files or directories.
151
     *     Files and directories will be stored with full paths (expect leading slash).
152
     *      `UnifiedArchive::create(['/etc/php.ini', '/var/log/'], 'archive.zip)` will preserve full paths.
153
     *  3. An array with strings where keys are strings.
154
     *     Files will be named from key.
155
     *     Directory contents will be prefixed from key. If prefix is empty string, contents will be populated into
156
     *      archive root. If value is an array, all folder contents will have the same prefix.
157
     *      `UnifiedArchive::create([
158
     *          'doc.txt' => '/home/user/very_long_name_of_document.txt',
159
     *          'static' => '/var/www/html/static/',
160
     *          'collection' => ['/var/www/html/collection1/', '/var/www/html/collection2/'],
161
     *          '' => ['/var/www/html/readme/', '/var/www/html/docs/'], // root contents
162
     *      ], 'archive.zip')`
163
     *
164
     * @param string $archiveName File name of archive. Type of archive will be determined by its name.
165
     * @param int $compressionLevel Level of compression
166
     * @param string|null $password
167
     * @param callable|null $fileProgressCallable
168
     * @return int Count of stored files is returned.
169
     * @throws FileAlreadyExistsException
170
     * @throws UnsupportedOperationException
171
     */
172
    public static function create(
173
        $fileOrFiles,
174
        $archiveName,
175
        $compressionLevel = BasicDriver::COMPRESSION_AVERAGE,
176
        $password = null,
177
        $fileProgressCallable = null
178
    )
179
    {
180
        if (file_exists($archiveName)) {
181
            throw new FileAlreadyExistsException('Archive ' . $archiveName . ' already exists!');
182
        }
183
184
        $info = static::prepareForArchiving($fileOrFiles, $archiveName);
185
        $driver = static::getCreationDriver($info['type'], false, $password !== null);
186
187
        return $driver::createArchive(
188
            $info['files'],
0 ignored issues
show
Bug introduced by
It seems like $info['files'] can also be of type boolean; however, parameter $files of wapmorgan\UnifiedArchive...Driver::createArchive() does only seem to accept array, 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

188
            /** @scrutinizer ignore-type */ $info['files'],
Loading history...
189
            $archiveName,
190
            $info['type'],
191
            $compressionLevel,
192
            $password,
193
            $fileProgressCallable
194
        );
195
    }
196
197
    /**
198
     * Creates an archive with passed files list
199
     *
200
     * @param string|string[]|array<string,string> $fileOrFiles List of files. Can be one of three formats:
201
     *                             1. A string containing path to file or directory.
202
     *                                  File will have it's basename.
203
     *                                  `UnifiedArchive::archiveFiles('/etc/php.ini', 'archive.zip)` will store
204
     * file with 'php.ini' name.
205
     *                                  Directory contents will be stored in archive root.
206
     *                                  `UnifiedArchive::archiveFiles('/var/log/', 'archive.zip')` will store all
207
     * directory contents in archive root.
208
     *                             2. An array with strings containing pats to files or directories.
209
     *                                  Files and directories will be stored with full paths.
210 7
     *                                  `UnifiedArchive::archiveFiles(['/etc/php.ini', '/var/log/'], 'archive.zip)`
211
     * will preserve full paths.
212 7
     *                             3. An array with strings where keys are strings.
213
     *                                  Files will have name from key.
214
     *                                  Directories contents will have prefix from key.
215
     *                                  `UnifiedArchive::archiveFiles(['doc.txt' => 'very_long_name_of_document.txt',
216
     *  'static' => '/var/www/html/static/'], 'archive.zip')`
217
     * @param string $archiveFormat
218
     * @param int $compressionLevel Level of compression
219 7
     * @param string|null $password
220
     * @param callable|null $fileProgressCallable
221 7
     * @return int Count of stored files is returned.
222
     * @throws UnsupportedOperationException
223
     */
224
    public static function createInString(
225
        $fileOrFiles,
226
        $archiveFormat,
227
        $compressionLevel = BasicDriver::COMPRESSION_AVERAGE,
228
        $password = null,
229
        $fileProgressCallable = null
230
    )
231
    {
232
        $info = static::prepareForArchiving($fileOrFiles, '.' . Formats::getFormatExtension($archiveFormat));
233
        try {
234
            $driver = static::getCreationDriver($archiveFormat, true, $password !== null);
235
        } catch (UnsupportedArchiveException $e) {
236
            // if there is no driver with ability to create archive in string (in memory), use first driver for format and create it in temp folder
237
            $driver = static::getCreationDriver($archiveFormat, false, $password !== null);
238
        }
239 8
240
        return $driver::createArchiveInString(
241 8
            $info['files'],
0 ignored issues
show
Bug introduced by
It seems like $info['files'] can also be of type boolean; however, parameter $files of wapmorgan\UnifiedArchive...createArchiveInString() does only seem to accept array, 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

241
            /** @scrutinizer ignore-type */ $info['files'],
Loading history...
242
            $info['type'],
243
            $compressionLevel,
244
            $password,
0 ignored issues
show
Bug introduced by
It seems like $password can also be of type string; however, parameter $password of wapmorgan\UnifiedArchive...createArchiveInString() does only seem to accept null, 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

244
            /** @scrutinizer ignore-type */ $password,
Loading history...
245
            $fileProgressCallable
246
        );
247
    }
248
249
    /**
250
     * @throws UnsupportedOperationException
251
     * @return BasicDriver
252 8
     */
253
    protected static function getCreationDriver($archiveFormat, $inString, $encrypted)
254 8
    {
255 8
        if (!Formats::canCreate($archiveFormat)) {
256
            throw new UnsupportedArchiveException('Unsupported archive type: ' . $archiveFormat);
257
        }
258
259
        $abilities = [Abilities::CREATE];
260
        if ($inString) {
261
            $abilities[] = Abilities::CREATE_IN_STRING;
262
        }
263
264
        if ($encrypted) {
265
            if (!Formats::canEncrypt($archiveFormat)) {
266
                throw new UnsupportedOperationException('Archive type ' . $archiveFormat . ' can not be encrypted');
267
            }
268
            $abilities[] = Abilities::CREATE_ENCRYPTED;
269
        }
270
271
        /** @var BasicDriver $driver */
272 6
        $driver = Formats::getFormatDriver($archiveFormat, $abilities);
273
        if ($driver === null) {
274 6
            throw new UnsupportedArchiveException('Unsupported archive type: ' . $archiveFormat . ' of archive ');
275
        }
276
        return $driver;
277
    }
278 6
279
    /**
280
     * Opens the file as one of supported formats
281
     *
282
     * @param string $fileName Archive filename
283
     * @param string $format Archive type
284
     * @param string|null $password
285
     */
286
    public function __construct($fileName, $format, $abilities = [], $password = null, $driver = null)
287
    {
288 8
        if (empty($abilities)) {
289
            $abilities = [Abilities::OPEN];
290 8
            if (!empty($password)) {
291
                $abilities[] = Abilities::OPEN_ENCRYPTED;
292
            }
293
        }
294 8
        if ($driver === null) {
295
            $driver = Formats::getFormatDriver($format, $abilities);
296
            if ($driver === null) {
297
                throw new UnsupportedArchiveException(
298
                    'Format ' . $format . ' driver with abilities ('
299
                    . implode(
300
                        ', ',
301
                        array_map(function ($ability) {
302
                            return array_search($ability, Abilities::$abilitiesLabels);
303
                        }, $abilities)
304 6
                    )
305
                    . ') is not found'
306 6
                );
307
            }
308
        }
309
310 6
        $this->format = $format;
311
        $this->driver = $driver;
312
        $this->password = $password;
313
        $this->archiveSize = filesize($fileName);
314
315
        /** @var $driver BasicDriver */
316
        $this->archive = new $driver($fileName, $format, $password);
317
        $this->scanArchive();
318
    }
319
320
    /**
321
     * Rescans array after modification
322
     */
323
    protected function scanArchive()
324
    {
325
        $information = $this->archive->getArchiveInformation();
326
        $this->files = $information->files;
327
        $this->compressedFilesSize = $information->compressedFilesSize;
328
        $this->uncompressedFilesSize = $information->uncompressedFilesSize;
329
        $this->filesQuantity = count($information->files);
330
    }
331
332
    /**
333
     * Closes archive
334
     */
335
    public function __destruct()
336
    {
337
        unset($this->archive);
338
    }
339
340
    /**
341
     * Returns an instance of class implementing PclZipOriginalInterface
342
     * interface.
343
     *
344
     * @return PclZipInterface Returns an instance of a class implementing PclZip-like interface
345
     */
346
    public function getPclZipInterface()
347
    {
348
        return new PclZipInterface($this);
349
    }
350
351
    /**
352
     * @return string
353
     */
354
    public function getDriverType()
355
    {
356 2
        return get_class($this->archive);
357
    }
358 2
359
    /**
360 2
     * @return BasicDriver
361
     */
362
    public function getDriver()
363
    {
364 2
        return $this->archive;
365
    }
366
367
    /**
368 2
     * Returns size of archive file in bytes
369 2
     *
370
     * @return int
371 2
     */
372
    public function getSize()
373
    {
374
        return $this->archiveSize;
375
    }
376
377
    /**
378
     * Returns type of archive
379
     *
380
     * @return string One of Format class constants
381
     */
382
    public function getFormat()
383 2
    {
384
        return $this->format;
385 2
    }
386
387 2
    /**
388
     * Returns mime type of archive
389
     *
390 2
     * @return string|null Mime Type
391 2
     */
392 2
    public function getMimeType()
393
    {
394
        return Formats::getFormatMimeType($this->format);
395
    }
396
397
    /**
398
     * @return string|null
399
     * @throws UnsupportedOperationException
400
     */
401
    public function getComment()
402
    {
403
        return $this->archive->getComment();
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->archive->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...
404
    }
405 2
406
    /**
407 2
     * @param string|null $comment
408
     * @return string|null
409
     * @throws UnsupportedOperationException
410 2
     */
411 2
    public function setComment($comment)
412 2
    {
413
        return $this->archive->setComment($comment);
414
    }
415
416
    /**
417
     * Counts number of files
418
     *
419
     * @return int
420
     */
421
    public function countFiles()
422
    {
423
        return $this->filesQuantity;
424
    }
425
426
    /**
427
     * * Counts cumulative size of all uncompressed data (bytes)
428
     * @return int
429
     */
430
    public function getOriginalSize()
431
    {
432
        return $this->uncompressedFilesSize;
433
    }
434
435
    /**
436
     * Counts cumulative size of all compressed data (in bytes)
437
     * @return int
438
     */
439
    public function getCompressedSize()
440
    {
441
        return $this->compressedFilesSize;
442
    }
443
444
    /**
445
     * @param int $ability One of Abilities constant.
446
     * @return bool
447
     */
448
    public function can($ability)
449
    {
450
        return $this->driver->checkAbility($ability);
451
    }
452
453
    /**
454
     * Checks that file exists in archive
455
     *
456
     * @param string $fileName File name in archive
457
     * @return bool
458
     */
459
    public function hasFile($fileName)
460
    {
461
        return in_array($fileName, $this->files, true);
462 2
    }
463
464 2
    /**
465
     * Returns list of files, excluding folders.
466 2
     *
467
     * Paths is present in unix-style (with forward slash - /).
468
     *
469 2
     * @param string|null $filter
470
     * @return array List of files
471 2
     */
472
    public function getFiles($filter = null)
473
    {
474 2
        if ($filter === null)
475 2
            return $this->files;
476 2
477
        $result = [];
478
        foreach ($this->files as $file) {
479
            if (fnmatch($filter, $file))
480 2
                $result[] = $file;
481 2
        }
482 2
        return $result;
483 2
    }
484
485
    /**
486
     * Returns file metadata of file in archive
487
     *
488
     * @param string $fileName File name in archive
489
     * @return ArchiveEntry
490
     * @throws NonExistentArchiveFileException
491
     */
492
    public function getFileData($fileName)
493
    {
494
        if (!in_array($fileName, $this->files, true)) {
495
            throw new NonExistentArchiveFileException('File ' . $fileName . ' does not exist in archive');
496
        }
497
498
        return $this->archive->getFileData($fileName);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->archive->getFileData($fileName) could also return false which is incompatible with the documented return type wapmorgan\UnifiedArchive\ArchiveEntry. 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...
499
    }
500
501
    /**
502
     * Returns full file content as string
503
     *
504
     * @param string $fileName File name in archive
505
     * @return string
506
     * @throws NonExistentArchiveFileException
507
     */
508
    public function getFileContent($fileName)
509
    {
510
        if (!in_array($fileName, $this->files, true)) {
511
            throw new NonExistentArchiveFileException('File ' . $fileName . ' does not exist in archive');
512
        }
513
514
        return $this->archive->getFileContent($fileName);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->archive->getFileContent($fileName) could also return false which is incompatible with the documented return type 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...
515 2
    }
516
517 2
    /**
518
     * Returns a resource for reading file from archive
519
     *
520 2
     * @param string $fileName File name in archive
521
     * @return resource
522 2
     * @throws NonExistentArchiveFileException
523
     */
524
    public function getFileStream($fileName)
525 2
    {
526
        if (!in_array($fileName, $this->files, true)) {
527
            throw new NonExistentArchiveFileException('File ' . $fileName . ' does not exist in archive');
528
        }
529 2
530
        return $this->archive->getFileStream($fileName);
531 2
    }
532
533
    /**
534
     * Unpacks files to disk
535
     *
536
     * @param string $outputFolder Extraction output dir
537
     * @param string|array|null $files One file or files list or null to extract all content.
538
     * @param bool $expandFilesList Whether paths like 'src/' should be expanded to all files inside 'src/' dir or not.
539
     * @return int Number of extracted files
540
     * @throws EmptyFileListException
541
     * @throws ArchiveExtractionException
542
     */
543
    public function extract($outputFolder, &$files = null, $expandFilesList = false)
544
    {
545
        if ($files !== null) {
546
            if (is_string($files)) {
547
                $files = [$files];
548
            }
549
            if ($expandFilesList) {
550
                $files = static::expandFileList($this->files, $files);
551
            }
552
            if (empty($files)) {
553
                throw new EmptyFileListException('Files list is empty!');
554
            }
555
            return $this->archive->extractFiles($outputFolder, $files);
556
        }
557
        return $this->archive->extractArchive($outputFolder);
558
    }
559
560
    /**
561
     * Updates existing archive by removing files from it
562
     *
563
     * Only 7zip and zip types support deletion.
564
     * @param string|string[] $fileOrFiles
565
     * @param bool $expandFilesList
566
     *
567
     * @return bool|int
568
     * @throws EmptyFileListException
569
     * @throws UnsupportedOperationException
570
     * @throws ArchiveModificationException
571
     */
572
    public function delete($fileOrFiles, $expandFilesList = false)
573
    {
574
        $fileOrFiles = is_string($fileOrFiles) ? [$fileOrFiles] : $fileOrFiles;
575
576
        if ($expandFilesList && $fileOrFiles !== null) {
577
            $fileOrFiles = static::expandFileList($this->files, $fileOrFiles);
578
        }
579
580
        if (empty($fileOrFiles)) {
581
            throw new EmptyFileListException('Files list is empty!');
582
        }
583
584
        $result = $this->archive->deleteFiles($fileOrFiles);
585
        $this->scanArchive();
586
587
        return $result;
588
    }
589
590
    /**
591
     * Updates existing archive by adding new files
592
     *
593
     * @param string[] $fileOrFiles See [[archiveFiles]] method for file list format.
594
     * @return int Number of added files
595
     * @throws ArchiveModificationException
596 4
     * @throws EmptyFileListException
597
     * @throws UnsupportedOperationException
598 4
     */
599
    public function add($fileOrFiles)
600
    {
601 4
        $files_list = static::createFilesList($fileOrFiles);
602 2
603
        if (empty($files_list)) {
604 2
            throw new EmptyFileListException('Files list is empty!');
605
        }
606
607
        $result = $this->archive->addFiles($files_list);
0 ignored issues
show
Bug introduced by
It seems like $files_list can also be of type boolean; however, parameter $files of wapmorgan\UnifiedArchive...BasicDriver::addFiles() does only seem to accept array, 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

607
        $result = $this->archive->addFiles(/** @scrutinizer ignore-type */ $files_list);
Loading history...
608 2
        $this->scanArchive();
609
        return $result;
610
    }
611
612
    /**
613 2
     * @param string $inArchiveName
614
     * @param string $content
615
     * @return bool
616 2
     * @throws ArchiveModificationException
617
     * @throws UnsupportedOperationException
618
     */
619 2
    public function addFileFromString($inArchiveName, $content)
620 2
    {
621
        $result = $this->archive->addFileFromString($inArchiveName, $content);
622
        $this->scanArchive();
623 2
        return $result;
624
    }
625 2
626 2
    /**
627 2
     * @param string|null $filter
628
     * @return true|string[]
629
     * @throws NonExistentArchiveFileException
630
     */
631
    public function test($filter = null, &$hashes = [])
632 4
    {
633
        $hash_exists = function_exists('hash_update_stream') && in_array('crc32b', hash_algos(), true);
634
        $failed = [];
635
        foreach ($this->getFiles($filter) as $fileName) {
636
            if ($hash_exists) {
637
                $ctx = hash_init('crc32b');
638
                hash_update_stream($ctx, $this->getFileStream($fileName));
639
                $hashes[$fileName][0] = hash_final($ctx);
640
            } else {
641 2
                $hashes[$fileName][0] = dechex(crc32($this->getFileContent($fileName)));
642
            }
643
            $hashes[$fileName][1] = strtolower($this->getFileData($fileName)->crc32);
0 ignored issues
show
Bug introduced by
It seems like $this->getFileData($fileName)->crc32 can also be of type null; however, parameter $string of strtolower() 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

643
            $hashes[$fileName][1] = strtolower(/** @scrutinizer ignore-type */ $this->getFileData($fileName)->crc32);
Loading history...
644
            if ($hashes[$fileName][1] !== $hashes[$fileName][0]) {
645
                $failed[] = $fileName;
646 2
            }
647 2
        }
648
        return !empty($failed) ? $failed : true;
649 2
    }
650 2
651 2
    /**
652 2
     * Expands files list
653 2
     * @param $archiveFiles
654 2
     * @param $files
655
     * @return array
656
     */
657 2
    protected static function expandFileList($archiveFiles, $files)
658
    {
659
        $newFiles = [];
660
        foreach ($files as $file) {
661
            foreach ($archiveFiles as $archiveFile) {
662
                if (fnmatch($file . '*', $archiveFile)) {
663
                    $newFiles[] = $archiveFile;
664
                }
665
            }
666
        }
667
        return $newFiles;
668
    }
669
670
    /**
671
     * @param string|array $nodes
672
     * @return array|bool
673
     */
674
    protected static function createFilesList($nodes)
675
    {
676
        $files = [];
677
678
        // passed an extended list
679
        if (is_array($nodes)) {
680
            foreach ($nodes as $destination => $source) {
681
                // new format
682
                if (is_numeric($destination))
683
                    $destination = $source;
684
                else {
685
                    // old format
686
                    if (is_string($source) && !file_exists($source)) {
687
                        list($destination, $source) = [$source, $destination];
688
                    }
689
                }
690
                $destination = rtrim($destination, '/\\*');
691
                // few sources for directories
692
                if (is_array($source)) {
693
                    foreach ($source as $sourceItem) {
694
                        static::importFilesFromDir(
695
                            rtrim($sourceItem, '/\\*') . '/*',
696
                            !empty($destination) ? $destination . '/' : null,
697
                            true,
698
                            $files
699
                        );
700
                    }
701
                } else if (is_dir($source)) {
702
                    // one source for directories
703
                    static::importFilesFromDir(
704
                        rtrim($source, '/\\*') . '/*',
705
                        !empty($destination) ? $destination . '/' : null,
706
                        true,
707
                        $files
708
                    );
709
                } else if (is_file($source)) {
710
                    $files[$destination] = $source;
711
                }
712
            }
713
714
        } else if (is_string($nodes)) { // passed one file or directory
0 ignored issues
show
introduced by
The condition is_string($nodes) is always true.
Loading history...
715
            // if is directory
716
            if (is_dir($nodes)) {
717
                $nodes = rtrim($nodes, '/\\*') . '/';
718
                static::importFilesFromDir(
719
                    $nodes . '*',
720
                    $nodes,
721
                    true,
722
                    $files
723
                );
724
            } else if (is_file($nodes)) {
725
                $files[realpath($nodes)] = $nodes;
726
            }
727
        }
728
729
        return $files;
730
    }
731
732
    /**
733
     * @param string $source
734
     * @param string|null $destination
735
     * @param bool $recursive
736
     * @param array $map
737
     */
738
    protected static function importFilesFromDir($source, $destination, $recursive, &$map)
739
    {
740
        // $map[$destination] = rtrim($source, '/*');
741
        // do not map root archive folder
742
743
        if ($destination !== null)
744
            $map[$destination] = null;
745
746
        foreach (glob($source, GLOB_MARK) as $node) {
747
            if (in_array(substr($node, -1), ['/', '\\'], true) && $recursive) {
748
                static::importFilesFromDir(str_replace('\\', '/', $node) . '*',
749
                    $destination . basename($node) . '/', $recursive, $map);
750
            } elseif (is_file($node) && is_readable($node)) {
751
                $map[$destination . basename($node)] = $node;
752
            }
753
        }
754
    }
755
756
    /**
757
     * @param mixed $offset
758
     * @return bool
759
     */
760
    #[\ReturnTypeWillChange]
761
    public function offsetExists($offset)
762
    {
763
        return $this->hasFile($offset);
764
    }
765
766
    /**
767
     * @param mixed $offset
768
     * @return mixed|string
769
     * @throws NonExistentArchiveFileException
770
     */
771
    #[\ReturnTypeWillChange]
772
    public function offsetGet($offset)
773
    {
774
        return $this->getFileData($offset);
775
    }
776
777
    /**
778
     * @param mixed $offset
779
     * @param mixed $value
780
     * @return bool|void
781
     * @throws ArchiveModificationException
782
     * @throws UnsupportedOperationException
783
     */
784
    #[\ReturnTypeWillChange]
785
    public function offsetSet($offset, $value)
786
    {
787
        return $this->addFileFromString($offset, $value);
788
    }
789
790
    /**
791
     * @param mixed $offset
792
     * @return bool|int|void
793
     * @throws ArchiveModificationException
794
     * @throws UnsupportedOperationException
795
     */
796
    #[\ReturnTypeWillChange]
797
    public function offsetUnset($offset)
798
    {
799
        return $this->delete($offset);
800
    }
801
802
    #[\ReturnTypeWillChange]
803
    public function key()
804
    {
805
        return $this->files[$this->filesIterator];
806
    }
807
808
    /**
809
     * @throws NonExistentArchiveFileException
810
     */
811
    #[\ReturnTypeWillChange]
812
    public function current()
813
    {
814
        return $this->getFileData($this->files[$this->filesIterator]);
815
    }
816
817
    #[\ReturnTypeWillChange]
818
    public function next()
819
    {
820
        $this->filesIterator++;
821
    }
822
823
    #[\ReturnTypeWillChange]
824
    public function valid()
825
    {
826
        return $this->filesIterator < $this->filesQuantity;
827
    }
828
829
    #[\ReturnTypeWillChange]
830
    public function rewind()
831
    {
832
        $this->filesIterator = 0;
833
    }
834
835
    #[\ReturnTypeWillChange]
836
    public function count()
837
    {
838
        return $this->filesQuantity;
839
    }
840
841
    // deprecated methods
842
843
    /**
844
     * Checks whether archive can be opened with current system configuration
845
     *
846
     * @param string $fileName Archive filename
847
     * @return bool
848
     * @deprecated See {UnifiedArchive::canOpen()}
849
     */
850
    public static function canOpenArchive($fileName)
851
    {
852
        return static::canOpen($fileName);
853
    }
854
855
    /**
856
     * Checks whether specific archive type can be opened with current system configuration
857
     *
858
     * @param string $type One of predefined archive types (class constants)
859
     * @return bool
860
     * @deprecated Use {{Formats::canOpen()}}
861
     */
862
    public static function canOpenType($type)
863
    {
864
        return Formats::canOpen($type);
865
    }
866
867
    /**
868
     * Checks whether specified archive can be created
869
     *
870
     * @param string $type One of predefined archive types (class constants)
871
     * @return bool
872
     * @deprecated Use {{Formats::canCreate()}}
873
     */
874
    public static function canCreateType($type)
875
    {
876
        return Formats::canCreate($type);
877
    }
878
879
    /**
880
     * Detect archive type by its filename or content
881
     *
882
     * @param string $fileName Archive filename
883
     * @param bool $contentCheck Whether archive type can be detected by content
884
     * @return string|bool One of UnifiedArchive type constants OR false if type is not detected
885
     * @deprecated Use {{Formats::detectArchiveFormat()}}
886
     */
887
    public static function detectArchiveType($fileName, $contentCheck = true)
888
    {
889
        return Formats::detectArchiveFormat($fileName, $contentCheck);
890
    }
891
892
    /**
893
     * Creates an archive with passed files list
894
     *
895
     * @param string|string[]|array<string,string> $fileOrFiles List of files. Can be one of three formats:
896
     *                             1. A string containing path to file or directory.
897
     *                                  File will have it's basename.
898
     *                                  `UnifiedArchive::archiveFiles('/etc/php.ini', 'archive.zip)` will store
899
     * file with 'php.ini' name.
900
     *                                  Directory contents will be stored in archive root.
901
     *                                  `UnifiedArchive::archiveFiles('/var/log/', 'archive.zip')` will store all
902
     * directory contents in archive root.
903
     *                             2. An array with strings containing pats to files or directories.
904
     *                                  Files and directories will be stored with full paths.
905
     *                                  `UnifiedArchive::archiveFiles(['/etc/php.ini', '/var/log/'], 'archive.zip)`
906
     * will preserve full paths.
907
     *                             3. An array with strings where keys are strings.
908
     *                                  Files will have name from key.
909
     *                                  Directories contents will have prefix from key.
910
     *                                  `UnifiedArchive::archiveFiles(['doc.txt' => 'very_long_name_of_document.txt',
911
     *  'static' => '/var/www/html/static/'], 'archive.zip')`
912
     *
913
     * @param string $archiveName File name of archive. Type of archive will be determined by it's name.
914
     * @param int $compressionLevel Level of compression
915
     * @param string|null $password
916
     * @param callable|null $fileProgressCallable
917
     * @return int Count of stored files is returned.
918
     * @throws FileAlreadyExistsException
919
     * @throws UnsupportedOperationException
920
     * @deprecated Use {{UnifiedArchive::create}}
921
     */
922
    public static function archiveFiles(
923
        $fileOrFiles,
924
        $archiveName,
925
        $compressionLevel = BasicDriver::COMPRESSION_AVERAGE,
926
        $password = null,
927
        $fileProgressCallable = null
928
    )
929
    {
930
        return static::create($fileOrFiles, $archiveName, $compressionLevel, $password, $fileProgressCallable);
931
    }
932
933
    /**
934
     * Creates an archive with passed files list
935
     * @param string $archiveName File name of archive. Type of archive will be determined by it's name.
936
     * @param int $compressionLevel Level of compression
937
     * @param string|null $password
938
     * @param callable|null $fileProgressCallable
939
     * @return int Count of stored files is returned.
940
     * @throws FileAlreadyExistsException
941
     * @throws UnsupportedOperationException
942
     * @deprecated Use {{UnifiedArchive::create}}
943
     */
944
    public static function archive(
945
        $fileOrFiles,
946
        $archiveName,
947
        $compressionLevel = BasicDriver::COMPRESSION_AVERAGE,
948
        $password = null,
949
        $fileProgressCallable = null
950
    )
951
    {
952
        return static::create($fileOrFiles, $archiveName, $compressionLevel, $password, $fileProgressCallable);
953
    }
954
955
    /**
956
     * Creates an archive with one file
957
     *
958
     * @param string $file
959
     * @param string $archiveName
960
     * @param int $compressionLevel Level of compression
961
     * @param string|null $password
962
     * @return bool
963
     * @throws FileAlreadyExistsException
964
     * @throws UnsupportedOperationException
965
     * @deprecated Use {{UnifiedArchive::create}}
966
     */
967
    public static function archiveFile($file, $archiveName, $compressionLevel = BasicDriver::COMPRESSION_AVERAGE, $password = null)
968
    {
969
        if (!is_file($file)) {
970
            throw new InvalidArgumentException($file . ' is not a valid file to archive');
971
        }
972
973
        return static::create($file, $archiveName, $compressionLevel, $password) === 1;
974
    }
975
976
    /**
977
     * Creates an archive with full directory contents
978
     *
979
     * @param string $directory
980
     * @param string $archiveName
981
     * @param int $compressionLevel Level of compression
982
     * @param string|null $password
983
     * @return bool
984
     * @throws FileAlreadyExistsException
985
     * @throws UnsupportedOperationException
986
     * @deprecated Use {{UnifiedArchive::create}}
987
     */
988
    public static function archiveDirectory($directory, $archiveName, $compressionLevel = BasicDriver::COMPRESSION_AVERAGE, $password = null)
989
    {
990
        if (!is_dir($directory) || !is_readable($directory))
991
            throw new InvalidArgumentException($directory . ' is not a valid directory to archive');
992
993
        return static::create($directory, $archiveName, $compressionLevel, $password) > 0;
994
    }
995
996
    /**
997
     * Returns type of archive
998
     *
999
     * @return string One of class constants
1000
     * @deprecated Use {{UnifiedArchive::getArchiveFormat()}}
1001
     */
1002
    public function getArchiveType()
1003
    {
1004
        return $this->getFormat();
1005
    }
1006
1007
    /**
1008
     * Returns a resource for reading file from archive
1009
     *
1010
     * @param string $fileName File name in archive
1011
     * @return resource
1012
     * @throws NonExistentArchiveFileException
1013
     * @deprecated Use {{UnifiedArchive::getFileStream}}
1014
     */
1015
    public function getFileResource($fileName)
1016
    {
1017
        return $this->getFileStream($fileName);
1018
    }
1019
1020
    /**
1021
     * Returns type of archive
1022
     *
1023
     * @return string One of class constants
1024
     * @deprecated Use {{UnifiedArchive::getFormat}}
1025
     */
1026
    public function getArchiveFormat()
1027
    {
1028
        return $this->getFormat();
1029
    }
1030
1031
    /**
1032
     * Checks that file exists in archive
1033
     *
1034
     * @param string $fileName File name in archive
1035
     * @return bool
1036
     * @deprecated Use {{UnifiedArchive::hasFile}}
1037
     */
1038
    public function isFileExists($fileName)
1039
    {
1040
        return $this->hasFile($fileName);
1041
    }
1042
1043
    /**
1044
     * Returns size of archive file in bytes
1045
     *
1046
     * @return int
1047
     * @deprecated Use {{UnifiedArchive::getSize}}
1048
     */
1049
    public function getArchiveSize()
1050
    {
1051
        return $this->getSize();
1052
    }
1053
1054
    /**
1055
     * Counts cumulative size of all compressed data (in bytes)
1056
     *
1057
     * @return int
1058
     * @deprecated Use {{UnifiedArchive::getCompressedSize}}
1059
     */
1060
    public function countCompressedFilesSize()
1061
    {
1062
        return $this->getCompressedSize();
1063
    }
1064
1065
    /**
1066
     * Counts cumulative size of all uncompressed data (bytes)
1067
     *
1068
     * @return int
1069
     * @deprecated Use {{UnifiedArchive::getOriginalSize}}
1070
     */
1071
    public function countUncompressedFilesSize()
1072
    {
1073
        return $this->getOriginalSize();
1074
    }
1075
1076
    /**
1077
     * Returns list of files, excluding folders.
1078
     *
1079
     * Paths is present in unix-style (with forward slash - /).
1080
     *
1081
     * @param string|null $filter
1082
     * @return array List of files
1083
     * @deprecated Use {{UnifiedArchive::getFiles}}
1084
     */
1085
    public function getFileNames($filter = null)
1086
    {
1087
        return $this->getFiles($filter);
1088
    }
1089
1090
    /**
1091
     * Unpacks files to disk
1092
     *
1093
     * @param string $outputFolder Extraction output dir
1094
     * @param string|array|null $files One file or files list or null to extract all content.
1095
     * @param bool $expandFilesList Whether paths like 'src/' should be expanded to all files inside 'src/' dir or not.
1096
     * @return int Number of extracted files
1097
     * @throws EmptyFileListException
1098
     * @throws ArchiveExtractionException
1099
     * @deprecated Use {{UnifiedArchive::extract}}
1100
     */
1101
    public function extractFiles($outputFolder, &$files = null, $expandFilesList = false)
1102
    {
1103
        return $this->extract($outputFolder, $files, $expandFilesList);
1104
    }
1105
1106
    /**
1107
     * Updates existing archive by removing files from it
1108
     *
1109
     * Only 7zip and zip types support deletion.
1110
     * @param string|string[] $fileOrFiles
1111
     * @param bool $expandFilesList
1112
     *
1113
     * @return bool|int
1114
     * @throws EmptyFileListException
1115
     * @throws UnsupportedOperationException
1116
     * @throws ArchiveModificationException
1117
     * @deprecated Use {{UnifiedArchive::delete}}
1118
     */
1119
    public function deleteFiles($fileOrFiles, $expandFilesList = false)
1120
    {
1121
        return $this->delete($fileOrFiles, $expandFilesList);
1122
    }
1123
1124
    /**
1125
     * Updates existing archive by adding new files
1126
     *
1127
     * @param string[] $fileOrFiles See [[archiveFiles]] method for file list format.
1128
     * @return int|bool Number of added files
1129
     * @throws ArchiveModificationException
1130
     * @throws EmptyFileListException
1131
     * @throws UnsupportedOperationException
1132
     * @deprecated Use {{UnifiedArchive::add}}
1133
     */
1134
    public function addFiles($fileOrFiles)
1135
    {
1136
        return $this->add($fileOrFiles);
1137
    }
1138
1139
    /**
1140
     * Adds file into archive
1141
     *
1142
     * @param string $file File name to be added
1143
     * @param string|null $inArchiveName If not passed, full path will be preserved.
1144
     * @return bool
1145
     * @throws ArchiveModificationException
1146
     * @throws EmptyFileListException
1147
     * @throws UnsupportedOperationException
1148
     * @deprecated Use {{UnifiedArchive::add}}
1149
     */
1150
    public function addFile($file, $inArchiveName = null)
1151
    {
1152
        if (!is_file($file))
1153
            throw new InvalidArgumentException($file . ' is not a valid file to add in archive');
1154
1155
        return ($inArchiveName !== null
1156
                ? $this->add([$inArchiveName => $file])
1157
                : $this->add([$file])) === 1;
1158
    }
1159
1160
    /**
1161
     * Adds directory contents to archive
1162
     *
1163
     * @param string $directory
1164
     * @param string|null $inArchivePath If not passed, full paths will be preserved.
1165
     * @return bool
1166
     * @throws ArchiveModificationException
1167
     * @throws EmptyFileListException
1168
     * @throws UnsupportedOperationException
1169
     * @deprecated Use {{UnifiedArchive::add}}
1170
     */
1171
    public function addDirectory($directory, $inArchivePath = null)
1172
    {
1173
        if (!is_dir($directory) || !is_readable($directory))
1174
            throw new InvalidArgumentException($directory . ' is not a valid directory to add in archive');
1175
1176
        return ($inArchivePath !== null
1177
                ? $this->add([$inArchivePath => $directory])
1178
                : $this->add([$inArchivePath])) > 0;
1179
    }
1180
}
1181