Passed
Push — master ( afad4e...ae0184 )
by f
40:32 queued 25:34
created

UnifiedArchive::prepareForArchiving()   B

Complexity

Conditions 7
Paths 15

Size

Total Lines 26
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 8.8142

Importance

Changes 4
Bugs 0 Features 1
Metric Value
cc 7
eloc 16
nc 15
nop 2
dl 0
loc 26
rs 8.8333
c 4
b 0
f 1
ccs 4
cts 6
cp 0.6667
crap 8.8142
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.7';
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 === false) {
72
            return null;
73 23
        }
74
75
        if (!empty($abilities) && is_string($abilities)) {
76
            $password = $abilities;
77
            $abilities = [];
78
        }
79
80
        if (empty($abilities)) {
81
            $abilities = [BasicDriver::OPEN];
82 21
            if (!empty($password)) {
83
                $abilities[] = [BasicDriver::OPEN_ENCRYPTED];
84 21
            }
85 21
        }
86
        $driver = Formats::getFormatDriver($format, $abilities);
0 ignored issues
show
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

86
        $driver = Formats::getFormatDriver(/** @scrutinizer ignore-type */ $format, $abilities);
Loading history...
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

86
        $driver = Formats::getFormatDriver($format, /** @scrutinizer ignore-type */ $abilities);
Loading history...
87
        if ($driver === null) {
88
            return null;
89
        }
90
91
        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

91
        return new static($fileName, /** @scrutinizer ignore-type */ $format, $driver, $password);
Loading history...
92
    }
93
94
    /**
95 23
     * Checks whether archive can be opened with current system configuration
96
     *
97 23
     * @param string $fileName Archive filename
98 23
     * @return bool
99
     */
100
    public static function canOpen($fileName)
101
    {
102 23
        $format = Formats::detectArchiveFormat($fileName);
103 23
        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

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

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

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

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

587
            /** @scrutinizer ignore-type */ $info['files'],
Loading history...
588
            $archiveName,
589
            $info['type'],
590
            $compressionLevel,
591
            $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

591
            /** @scrutinizer ignore-type */ $password,
Loading history...
592
            $fileProgressCallable
593
        );
594
    }
595
596 4
    /**
597
     * Creates an archive with passed files list
598 4
     *
599
     * @param string|string[]|array<string,string> $fileOrFiles List of files. Can be one of three formats:
600
     *                             1. A string containing path to file or directory.
601 4
     *                                  File will have it's basename.
602 2
     *                                  `UnifiedArchive::archiveFiles('/etc/php.ini', 'archive.zip)` will store
603
     * file with 'php.ini' name.
604 2
     *                                  Directory contents will be stored in archive root.
605
     *                                  `UnifiedArchive::archiveFiles('/var/log/', 'archive.zip')` will store all
606
     * directory contents in archive root.
607
     *                             2. An array with strings containing pats to files or directories.
608 2
     *                                  Files and directories will be stored with full paths.
609
     *                                  `UnifiedArchive::archiveFiles(['/etc/php.ini', '/var/log/'], 'archive.zip)`
610
     * will preserve full paths.
611
     *                             3. An array with strings where keys are strings.
612
     *                                  Files will have name from key.
613 2
     *                                  Directories contents will have prefix from key.
614
     *                                  `UnifiedArchive::archiveFiles(['doc.txt' => 'very_long_name_of_document.txt',
615
     *  'static' => '/var/www/html/static/'], 'archive.zip')`
616 2
     *
617
     * @param string $archiveName File name of archive. Type of archive will be determined by it's name.
618
     * @param int $compressionLevel Level of compression
619 2
     * @param string|null $password
620 2
     * @param callable|null $fileProgressCallable
621
     * @return int Count of stored files is returned.
622
     * @throws FileAlreadyExistsException
623 2
     * @throws UnsupportedOperationException
624
     */
625 2
    public static function archiveInString(
626 2
        $fileOrFiles,
627 2
        $compressionLevel = BasicDriver::COMPRESSION_AVERAGE,
628
        $password = null,
629
        $fileProgressCallable = null
630
    )
631
    {
632 4
        $info = static::prepareForArchiving($fileOrFiles);
633
        $abilities = [BasicDriver::CREATE, BasicDriver::CREATE_IN_STRING];
634
        if (!Formats::canCreate($info['type'])) {
635
            throw new UnsupportedArchiveException('Unsupported archive type: ' . $info['type'] . ' of archive ' . $archiveName);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $archiveName seems to be never defined.
Loading history...
636
        }
637
638
        if ($password !== null && !Formats::canEncrypt($info['type'])) {
639
            throw new UnsupportedOperationException('Archive type ' . $info['type'] . ' can not be encrypted');
640
        }
641 2
        if ($password !== null) {
642
            $abilities[] = BasicDriver::CREATE_ENCRYPTED;
643
        }
644
645
        /** @var BasicDriver $driver */
646 2
        $driver = Formats::getFormatDriver($info['type'], $abilities);
647 2
        if ($driver === null) {
648
            throw new UnsupportedArchiveException('Unsupported archive type: ' . $info['type'] . ' of archive ');
649 2
        }
650 2
651 2
        return $driver::createArchiveInString(
652 2
            $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

652
            /** @scrutinizer ignore-type */ $info['files'],
Loading history...
653 2
            $info['format'],
0 ignored issues
show
Bug introduced by
It seems like $info['format'] can also be of type array; however, parameter $archiveFormat of wapmorgan\UnifiedArchive...createArchiveInString() 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

653
            /** @scrutinizer ignore-type */ $info['format'],
Loading history...
654 2
            $compressionLevel,
655
            $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

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