Passed
Push — master ( a7d2a0...df2343 )
by f
29:27 queued 14:29
created

UnifiedArchive::rewind()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 2
Bugs 0 Features 1
Metric Value
cc 1
eloc 2
c 2
b 0
f 1
nc 1
nop 0
dl 0
loc 4
ccs 0
cts 2
cp 0
crap 2
rs 10
1
<?php
2
namespace wapmorgan\UnifiedArchive;
3
4
use ArrayAccess;
5
use Countable;
6
use InvalidArgumentException;
7
use Iterator;
8
use wapmorgan\UnifiedArchive\Drivers\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.1.6';
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 Returns UnifiedArchive in case of successful reading of the file
63
     * @throws UnsupportedArchiveException
64 23
     * @throws UnsupportedOperationException
65
     */
66
    public static function open($fileName, $abilities = [], $password = null)
67
    {
68 23
        if (!file_exists($fileName) || !is_readable($fileName)) {
69 23
            throw new InvalidArgumentException('Could not open file: ' . $fileName.' is not readable');
70
        }
71
72
        $format = Formats::detectArchiveFormat($fileName);
73 23
        if ($format === false) {
74
            throw new UnsupportedArchiveException('Can not recognize archive format for ' . $fileName);
75
        }
76
77
        if (!empty($abilities) && is_string($abilities)) {
78
            $password = $abilities;
79
            $abilities = [];
80
        }
81
82 21
        if (empty($abilities)) {
83
            $abilities = [BasicDriver::OPEN];
84 21
            if (!empty($password)) {
85 21
                $abilities[] = [BasicDriver::OPEN_ENCRYPTED];
86
            }
87
        }
88
        $driver = Formats::getFormatDriver($format, $abilities);
0 ignored issues
show
Bug introduced by
It seems like $abilities can also be of type null; however, parameter $abilities of wapmorgan\UnifiedArchive...mats::getFormatDriver() 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

88
        $driver = Formats::getFormatDriver($format, /** @scrutinizer ignore-type */ $abilities);
Loading history...
Bug introduced by
It seems like $format can also be of type true; however, parameter $format of wapmorgan\UnifiedArchive...mats::getFormatDriver() 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

88
        $driver = Formats::getFormatDriver(/** @scrutinizer ignore-type */ $format, $abilities);
Loading history...
89
        if ($driver === null) {
90
            throw new UnsupportedOperationException('Driver for ' . $format . ' (' . $fileName . ') is not found');
0 ignored issues
show
Bug introduced by
Are you sure $format of type string|true can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

90
            throw new UnsupportedOperationException('Driver for ' . /** @scrutinizer ignore-type */ $format . ' (' . $fileName . ') is not found');
Loading history...
91
        }
92
93
        return new static($fileName, $format, $driver, $password);
0 ignored issues
show
Bug introduced by
It seems like $format can also be of type true; however, parameter $format of wapmorgan\UnifiedArchive...dArchive::__construct() 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

93
        return new static($fileName, /** @scrutinizer ignore-type */ $format, $driver, $password);
Loading history...
94
    }
95 23
96
    /**
97 23
     * Checks whether archive can be opened with current system configuration
98 23
     *
99
     * @param string $fileName Archive filename
100
     * @return bool
101
     */
102 23
    public static function canOpen($fileName)
103 23
    {
104 23
        $format = Formats::detectArchiveFormat($fileName);
105
        return $format !== false && Formats::canOpen($format);
0 ignored issues
show
Bug introduced by
It seems like $format can also be of type true; however, parameter $format of wapmorgan\UnifiedArchive\Formats::canOpen() 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

105
        return $format !== false && Formats::canOpen(/** @scrutinizer ignore-type */ $format);
Loading history...
106
    }
107 23
108 23
    /**
109 23
     * Opens the file as one of supported formats
110
     *
111
     * @param string $fileName Archive filename
112
     * @param string $format Archive type
113
     * @param string|BasicDriver $driver
114 23
     * @param string|null $password
115
     */
116 23
    public function __construct($fileName, $format, $driver, $password = null)
117 23
    {
118 23
        $this->format = $format;
119 23
        $this->driver = $driver;
0 ignored issues
show
Documentation Bug introduced by
It seems like $driver can also be of type string. However, the property $driver is declared as type wapmorgan\UnifiedArchive\Drivers\BasicDriver. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
120 23
        $this->password = $password;
121 23
        $this->archiveSize = filesize($fileName);
122
123
        /** @var BasicDriver */
124
        $this->archive = new $driver($fileName, $format, $password);
125
        $this->scanArchive();
126 23
    }
127
128 23
    /**
129 23
     * Rescans array after modification
130
     */
131
    protected function scanArchive()
132
    {
133
        $information = $this->archive->getArchiveInformation();
134
        $this->files = $information->files;
135
        $this->compressedFilesSize = $information->compressedFilesSize;
136
        $this->uncompressedFilesSize = $information->uncompressedFilesSize;
137
        $this->filesQuantity = count($information->files);
138
    }
139
140
    /**
141
     * Closes archive
142
     */
143
    public function __destruct()
144
    {
145 2
        unset($this->archive);
146
    }
147 2
148
    /**
149
     * Returns an instance of class implementing PclZipOriginalInterface
150
     * interface.
151
     *
152
     * @return PclZipInterface Returns an instance of a class implementing PclZip-like interface
153
     */
154
    public function getPclZipInterface()
155
    {
156
        return new PclZipInterface($this);
157
    }
158
159
    /**
160
     * @return string
161
     */
162
    public function getDriverType()
163
    {
164
        return get_class($this->archive);
165
    }
166
167
    /**
168
     * @return BasicDriver
169
     */
170
    public function getDriver()
171
    {
172
        return $this->archive;
173
    }
174
175
    /**
176
     * Returns size of archive file in bytes
177
     *
178
     * @return int
179
     */
180
    public function getSize()
181
    {
182
        return $this->archiveSize;
183
    }
184
185
    /**
186
     * Returns type of archive
187
     *
188
     * @return string One of Format class constants
189
     */
190
    public function getFormat()
191
    {
192
        return $this->format;
193
    }
194
195
    /**
196
     * Returns mime type of archive
197
     *
198
     * @return string|false Mime Type
199
     */
200
    public function getMimeType()
201
    {
202
        return Formats::getFormatMimeType($this->format);
203
    }
204
205
    /**
206
     * @return string|null
207
     */
208
    public function getComment()
209
    {
210 7
        return $this->archive->getComment();
211
    }
212 7
213
    /**
214
     * @param string|null $comment
215
     * @return string|null
216
     */
217
    public function setComment($comment)
218
    {
219 7
        return $this->archive->setComment($comment);
220
    }
221 7
222
    /**
223
     * Counts number of files
224
     *
225
     * @return int
226
     */
227
    public function countFiles()
228
    {
229
        return $this->filesQuantity;
230
    }
231
232
    /**
233
     * * Counts cumulative size of all uncompressed data (bytes)
234
     * @return int
235
     */
236
    public function getOriginalSize()
237
    {
238
        return $this->uncompressedFilesSize;
239 8
    }
240
241 8
    /**
242
     * Counts cumulative size of all compressed data (in bytes)
243
     * @return int
244
     */
245
    public function getCompressedSize()
246
    {
247
        return $this->compressedFilesSize;
248
    }
249
250
    /**
251
     * Checks that file exists in archive
252 8
     *
253
     * @param string $fileName File name in archive
254 8
     * @return bool
255 8
     */
256
    public function hasFile($fileName)
257
    {
258
        return in_array($fileName, $this->files, true);
259
    }
260
261
    /**
262
     * Returns list of files, excluding folders.
263
     *
264
     * Paths is present in unix-style (with forward slash - /).
265
     *
266
     * @param string|null $filter
267
     * @return array List of files
268
     */
269
    public function getFileNames($filter = null)
270
    {
271
        if ($filter === null)
272 6
            return $this->files;
273
274 6
        $result = [];
275
        foreach ($this->files as $file) {
276
            if (fnmatch($filter, $file))
277
                $result[] = $file;
278 6
        }
279
        return $result;
280
    }
281
282
    /**
283
     * Returns file metadata of file in archive
284
     *
285
     * @param string $fileName File name in archive
286
     * @return ArchiveEntry
287
     * @throws NonExistentArchiveFileException
288 8
     */
289
    public function getFileData($fileName)
290 8
    {
291
        if (!in_array($fileName, $this->files, true)) {
292
            throw new NonExistentArchiveFileException('File ' . $fileName . ' does not exist in archive');
293
        }
294 8
295
        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...
296
    }
297
298
    /**
299
     * Returns full file content as string
300
     *
301
     * @param string $fileName File name in archive
302
     * @return string
303
     * @throws NonExistentArchiveFileException
304 6
     */
305
    public function getFileContent($fileName)
306 6
    {
307
        if (!in_array($fileName, $this->files, true)) {
308
            throw new NonExistentArchiveFileException('File ' . $fileName . ' does not exist in archive');
309
        }
310 6
311
        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...
312
    }
313
314
    /**
315
     * Returns a resource for reading file from archive
316
     *
317
     * @param string $fileName File name in archive
318
     * @return resource
319
     * @throws NonExistentArchiveFileException
320
     */
321
    public function getFileStream($fileName)
322
    {
323
        if (!in_array($fileName, $this->files, true)) {
324
            throw new NonExistentArchiveFileException('File ' . $fileName . ' does not exist in archive');
325
        }
326
327
        return $this->archive->getFileStream($fileName);
328
    }
329
330
    /**
331
     * Unpacks files to disk
332
     *
333
     * @param string $outputFolder Extraction output dir
334
     * @param string|array|null $files One file or files list or null to extract all content.
335
     * @param bool $expandFilesList Whether paths like 'src/' should be expanded to all files inside 'src/' dir or not.
336
     * @return int Number of extracted files
337
     * @throws EmptyFileListException
338
     * @throws ArchiveExtractionException
339
     */
340
    public function extractFiles($outputFolder, &$files = null, $expandFilesList = false)
341
    {
342
        if ($files !== null) {
343
            if (is_string($files)) {
344
                $files = [$files];
345
            }
346
347
            if ($expandFilesList) {
348
                $files = static::expandFileList($this->files, $files);
349
            }
350
351
            if (empty($files)) {
352
                throw new EmptyFileListException('Files list is empty!');
353
            }
354
355
            return $this->archive->extractFiles($outputFolder, $files);
356 2
        }
357
358 2
        return $this->archive->extractArchive($outputFolder);
359
    }
360 2
361
    /**
362
     * Updates existing archive by removing files from it
363
     *
364 2
     * Only 7zip and zip types support deletion.
365
     * @param string|string[] $fileOrFiles
366
     * @param bool $expandFilesList
367
     *
368 2
     * @return bool|int
369 2
     * @throws EmptyFileListException
370
     * @throws UnsupportedOperationException
371 2
     * @throws ArchiveModificationException
372
     */
373
    public function deleteFiles($fileOrFiles, $expandFilesList = false)
374
    {
375
        $fileOrFiles = is_string($fileOrFiles) ? [$fileOrFiles] : $fileOrFiles;
376
377
        if ($expandFilesList && $fileOrFiles !== null) {
378
            $fileOrFiles = static::expandFileList($this->files, $fileOrFiles);
379
        }
380
381
        if (empty($fileOrFiles)) {
382
            throw new EmptyFileListException('Files list is empty!');
383 2
        }
384
385 2
        $result = $this->archive->deleteFiles($fileOrFiles);
386
        $this->scanArchive();
387 2
388
        return $result;
389
    }
390 2
391 2
    /**
392 2
     * Updates existing archive by adding new files
393
     *
394
     * @param string[] $fileOrFiles See [[archiveFiles]] method for file list format.
395
     * @return int|bool Number of added files
396
     * @throws ArchiveModificationException
397
     * @throws EmptyFileListException
398
     * @throws UnsupportedOperationException
399
     */
400
    public function addFiles($fileOrFiles)
401
    {
402
        $files_list = static::createFilesList($fileOrFiles);
403
404
        if (empty($files_list))
405 2
            throw new EmptyFileListException('Files list is empty!');
406
407 2
        $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

407
        $result = $this->archive->addFiles(/** @scrutinizer ignore-type */ $files_list);
Loading history...
408
        $this->scanArchive();
409
        return $result;
410 2
    }
411 2
412 2
    /**
413
     * Adds file into archive
414
     *
415
     * @param string $file File name to be added
416
     * @param string|null $inArchiveName If not passed, full path will be preserved.
417
     * @return bool
418
     * @throws ArchiveModificationException
419
     * @throws EmptyFileListException
420
     * @throws UnsupportedOperationException
421
     */
422
    public function addFile($file, $inArchiveName = null)
423
    {
424
        if (!is_file($file))
425
            throw new InvalidArgumentException($file.' is not a valid file to add in archive');
426
427
        return ($inArchiveName !== null
428
                ? $this->addFiles([$inArchiveName => $file])
429
                : $this->addFiles([$file])) === 1;
430
    }
431
432
    /**
433
     * @param string $inArchiveName
434
     * @param string $content
435
     * @return bool
436
     * @throws ArchiveModificationException
437
     * @throws UnsupportedOperationException
438
     */
439
    public function addFileFromString($inArchiveName, $content)
440
    {
441
        $result = $this->archive->addFileFromString($inArchiveName, $content);
442
        $this->scanArchive();
443
        return $result;
444
    }
445
446
    /**
447
     * Adds directory contents to archive
448
     *
449
     * @param string $directory
450
     * @param string|null $inArchivePath If not passed, full paths will be preserved.
451
     * @return bool
452
     * @throws ArchiveModificationException
453
     * @throws EmptyFileListException
454
     * @throws UnsupportedOperationException
455
     */
456
    public function addDirectory($directory, $inArchivePath = null)
457
    {
458
        if (!is_dir($directory) || !is_readable($directory))
459
            throw new InvalidArgumentException($directory.' is not a valid directory to add in archive');
460
461
        return ($inArchivePath !== null
462 2
                ? $this->addFiles([$inArchivePath => $directory])
463
                : $this->addFiles([$inArchivePath])) > 0;
464 2
    }
465
466 2
    /**
467
     * @param string|null $filter
468
     * @return true|string[]
469 2
     * @throws NonExistentArchiveFileException
470
     */
471 2
    public function test($filter = null)
472
    {
473
        $hash_exists = function_exists('hash_update_stream') && in_array('crc32b', hash_algos(), true);
474 2
        $failed = [];
475 2
        foreach ($this->getFileNames($filter) as $fileName) {
476 2
            if ($hash_exists) {
477
                $ctx = hash_init('crc32b');
478
                hash_update_stream($ctx, $this->getFileStream($fileName));
479
                $actual_hash = hash_final($ctx);
480 2
            } else {
481 2
                $actual_hash = dechex(crc32($this->getFileContent($fileName)));
482 2
            }
483 2
            $expected_hash = 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

483
            $expected_hash = strtolower(/** @scrutinizer ignore-type */ $this->getFileData($fileName)->crc32);
Loading history...
484
            if ($expected_hash !== $actual_hash) {
485
                $failed[] = $fileName;
486
            }
487
        }
488
        return !empty($failed) ? $failed : true;
489
    }
490
491
    /**
492
     * Prepare files list for archiving
493
     *
494
     * @param string|array $fileOrFiles File of list of files. See [[archiveFiles]] for details.
495
     * @param string $archiveName File name of archive. See [[archiveFiles]] for details.
496
     * @return array An array containing entries:
497
     * - totalSize (int) - size in bytes for all files
498
     * - numberOfFiles (int) - quantity of files
499
     * - files (array) - list of files prepared for archiving
500
     * - type (string) - prepared format for archive. One of class constants
501
     * @throws EmptyFileListException
502
     * @throws UnsupportedArchiveException
503
     */
504
    public static function prepareForArchiving($fileOrFiles, $archiveName)
505
    {
506
        $archiveType = Formats::detectArchiveFormat($archiveName, false);
507
508
        if ($archiveType === false)
509
            throw new UnsupportedArchiveException('Could not detect archive type for name "'.$archiveName.'"');
510
511
        $files_list = static::createFilesList($fileOrFiles);
512
513
        if (empty($files_list))
514
            throw new EmptyFileListException('Files list is empty!');
515 2
516
        $totalSize = 0;
517 2
        foreach ($files_list as $fn) {
518
            if ($fn !== null) {
519
                $totalSize += filesize($fn);
520 2
            }
521
        }
522 2
523
        return [
524
            'totalSize' => $totalSize,
525 2
            '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

525
            'numberOfFiles' => count(/** @scrutinizer ignore-type */ $files_list),
Loading history...
526
            'files' => $files_list,
527
            'type' => $archiveType,
528
        ];
529 2
    }
530
531 2
    /**
532
     * Creates an archive with passed files list
533
     *
534
     * @param string|string[]|array<string,string> $fileOrFiles List of files. Can be one of three formats:
535
     *                             1. A string containing path to file or directory.
536
     *                                  File will have it's basename.
537
     *                                  `UnifiedArchive::archiveFiles('/etc/php.ini', 'archive.zip)` will store
538
     * file with 'php.ini' name.
539
     *                                  Directory contents will be stored in archive root.
540
     *                                  `UnifiedArchive::archiveFiles('/var/log/', 'archive.zip')` will store all
541
     * directory contents in archive root.
542
     *                             2. An array with strings containing pats to files or directories.
543
     *                                  Files and directories will be stored with full paths.
544
     *                                  `UnifiedArchive::archiveFiles(['/etc/php.ini', '/var/log/'], 'archive.zip)`
545
     * will preserve full paths.
546
     *                             3. An array with strings where keys are strings.
547
     *                                  Files will have name from key.
548
     *                                  Directories contents will have prefix from key.
549
     *                                  `UnifiedArchive::archiveFiles(['doc.txt' => 'very_long_name_of_document.txt',
550
     *  'static' => '/var/www/html/static/'], 'archive.zip')`
551
     *
552
     * @param string $archiveName File name of archive. Type of archive will be determined by it's name.
553
     * @param int $compressionLevel Level of compression
554
     * @param string|null $password
555
     * @param callable|null $fileProgressCallable
556
     * @return int Count of stored files is returned.
557
     * @throws FileAlreadyExistsException
558
     * @throws UnsupportedOperationException
559
     */
560
    public static function archiveFiles(
561
        $fileOrFiles,
562
        $archiveName,
563
        $compressionLevel = BasicDriver::COMPRESSION_AVERAGE,
564
        $password = null,
565
        $fileProgressCallable = null
566
 )
567
    {
568
        if (file_exists($archiveName))
569
            throw new FileAlreadyExistsException('Archive '.$archiveName.' already exists!');
570
571
        $info = static::prepareForArchiving($fileOrFiles, $archiveName);
572
573
        $abilities = [BasicDriver::CREATE];
574
575
        if (!Formats::canCreate($info['type']))
576
            throw new UnsupportedArchiveException('Unsupported archive type: '.$info['type'].' of archive '.$archiveName);
577
578
        if ($password !== null && !Formats::canEncrypt($info['type']))
579
            throw new UnsupportedOperationException('Archive type '.$info['type'].' can not be encrypted');
580
        if ($password !== null) {
581
            $abilities[] = BasicDriver::CREATE_ENCRYPTED;
582
        }
583
584
        /** @var BasicDriver $driver */
585
        $driver = Formats::getFormatDriver($info['type'], $abilities);
586
        if ($driver === null) {
587
            throw new UnsupportedArchiveException('Unsupported archive type: ' . $info['type'] . ' of archive ');
588
        }
589
590
        return $driver::createArchive(
591
            $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

591
            /** @scrutinizer ignore-type */ $info['files'],
Loading history...
592
            $archiveName,
593
            $compressionLevel,
594
            $compressionLevel,
595
            $password,
0 ignored issues
show
Bug introduced by
It seems like $password can also be of type string; however, parameter $password of wapmorgan\UnifiedArchive...Driver::createArchive() 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

595
            /** @scrutinizer ignore-type */ $password,
Loading history...
596 4
            $fileProgressCallable
597
        );
598 4
    }
599
600
    /**
601 4
     * Creates an archive with one file
602 2
     *
603
     * @param string $file
604 2
     * @param string $archiveName
605
     * @param int $compressionLevel Level of compression
606
     * @param string|null $password
607
     * @return bool
608 2
     * @throws FileAlreadyExistsException
609
     * @throws UnsupportedOperationException
610
     */
611
    public static function archiveFile($file, $archiveName, $compressionLevel = BasicDriver::COMPRESSION_AVERAGE, $password = null)
612
    {
613 2
        if (!is_file($file)) {
614
            throw new InvalidArgumentException($file . ' is not a valid file to archive');
615
        }
616 2
617
        return static::archiveFiles($file, $archiveName, $compressionLevel, $password) === 1;
618
    }
619 2
620 2
    /**
621
     * Creates an archive with full directory contents
622
     *
623 2
     * @param string $directory
624
     * @param string $archiveName
625 2
     * @param int $compressionLevel Level of compression
626 2
     * @param string|null $password
627 2
     * @return bool
628
     * @throws FileAlreadyExistsException
629
     * @throws UnsupportedOperationException
630
     */
631
    public static function archiveDirectory($directory, $archiveName, $compressionLevel = BasicDriver::COMPRESSION_AVERAGE, $password = null)
632 4
    {
633
        if (!is_dir($directory) || !is_readable($directory))
634
            throw new InvalidArgumentException($directory.' is not a valid directory to archive');
635
636
        return static::archiveFiles($directory, $archiveName, $compressionLevel, $password) > 0;
637
    }
638
639
    /**
640
     * Expands files list
641 2
     * @param $archiveFiles
642
     * @param $files
643
     * @return array
644
     */
645
    protected static function expandFileList($archiveFiles, $files)
646 2
    {
647 2
        $newFiles = [];
648
        foreach ($files as $file) {
649 2
            foreach ($archiveFiles as $archiveFile) {
650 2
                if (fnmatch($file.'*', $archiveFile)) {
651 2
                    $newFiles[] = $archiveFile;
652 2
                }
653 2
            }
654 2
        }
655
        return $newFiles;
656
    }
657 2
658
    /**
659
     * @param string|array $nodes
660
     * @return array|bool
661
     */
662
    protected static function createFilesList($nodes)
663
    {
664
        $files = [];
665
666
        // passed an extended list
667
        if (is_array($nodes)) {
668
            foreach ($nodes as $destination => $source) {
669
                // new format
670
                if (is_numeric($destination))
671
                    $destination = $source;
672
                else {
673
                    // old format
674
                    if (is_string($source) && !file_exists($source)) {
675
                        list($destination, $source) = [$source, $destination];
676
                    }
677
                }
678
679
                $destination = rtrim($destination, '/\\*');
680
681
                // few sources for directories
682
                if (is_array($source)) {
683
                    foreach ($source as $sourceItem) {
684
                        static::importFilesFromDir(
685
                            rtrim($sourceItem, '/\\*') . '/*',
686
                            !empty($destination) ? $destination . '/' : null,
687
                            true,
688
                            $files
689
                        );
690
                    }
691
                } else if (is_dir($source)) {
692
                    // one source for directories
693
                    static::importFilesFromDir(
694
                        rtrim($source, '/\\*') . '/*',
695
                        !empty($destination) ? $destination . '/' : null,
696
                        true,
697
                        $files
698
                    );
699
                } else if (is_file($source)) {
700
                    $files[$destination] = $source;
701
                }
702
            }
703
704
        } 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...
705
            // if is directory
706
            if (is_dir($nodes))
707
                static::importFilesFromDir(rtrim($nodes, '/\\*').'/*', null, true,
708
                    $files);
709
            else if (is_file($nodes))
710
                $files[basename($nodes)] = $nodes;
711
        }
712
713
        return $files;
714
    }
715
716
    /**
717
     * @param string $source
718
     * @param string|null $destination
719
     * @param bool $recursive
720
     * @param array $map
721
     */
722
    protected static function importFilesFromDir($source, $destination, $recursive, &$map)
723
    {
724
        // $map[$destination] = rtrim($source, '/*');
725
        // do not map root archive folder
726
727
        if ($destination !== null)
728
            $map[$destination] = null;
729
730
        foreach (glob($source, GLOB_MARK) as $node) {
731
            if (in_array(substr($node, -1), ['/', '\\'], true) && $recursive) {
732
                static::importFilesFromDir(str_replace('\\', '/', $node).'*',
733
                    $destination.basename($node).'/', $recursive, $map);
734
            } elseif (is_file($node) && is_readable($node)) {
735
                $map[$destination.basename($node)] = $node;
736
            }
737
        }
738
    }
739
740
    /**
741
     * @param mixed $offset
742
     * @return bool
743
     */
744
    #[\ReturnTypeWillChange]
745
    public function offsetExists($offset)
746
    {
747
        return $this->hasFile($offset);
748
    }
749
750
    /**
751
     * @param mixed $offset
752
     * @return mixed|string
753
     * @throws NonExistentArchiveFileException
754
     */
755
    #[\ReturnTypeWillChange]
756
    public function offsetGet($offset)
757
    {
758
        return $this->getFileData($offset);
759
    }
760
761
    /**
762
     * @param mixed $offset
763
     * @param mixed $value
764
     * @return bool|void
765
     * @throws ArchiveModificationException
766
     * @throws UnsupportedOperationException
767
     */
768
    #[\ReturnTypeWillChange]
769
    public function offsetSet($offset, $value)
770
    {
771
        return $this->addFileFromString($offset, $value);
772
    }
773
774
    /**
775
     * @param mixed $offset
776
     * @return bool|int|void
777
     * @throws ArchiveModificationException
778
     * @throws UnsupportedOperationException
779
     */
780
    #[\ReturnTypeWillChange]
781
    public function offsetUnset($offset)
782
    {
783
        return $this->deleteFiles($offset);
784
    }
785
786
    #[\ReturnTypeWillChange]
787
    public function key()
788
    {
789
        return $this->files[$this->filesIterator];
790
    }
791
792
    /**
793
     * @throws NonExistentArchiveFileException
794
     */
795
    #[\ReturnTypeWillChange]
796
    public function current()
797
    {
798
        return $this->getFileData($this->files[$this->filesIterator]);
799
    }
800
801
    #[\ReturnTypeWillChange]
802
    public function next()
803
    {
804
        $this->filesIterator++;
805
    }
806
807
    #[\ReturnTypeWillChange]
808
    public function valid()
809
    {
810
        return $this->filesIterator < $this->filesQuantity;
811
    }
812
813
    #[\ReturnTypeWillChange]
814
    public function rewind()
815
    {
816
        $this->filesIterator = 0;
817
    }
818
819
    #[\ReturnTypeWillChange]
820
    public function count()
821
    {
822
        return $this->filesQuantity;
823
    }
824
}
825