Passed
Push — master ( 0d8a9b...48a836 )
by f
12:20
created

UnifiedArchive::getCreationDriver()   A

Complexity

Conditions 6
Paths 11

Size

Total Lines 24
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 8.304

Importance

Changes 0
Metric Value
cc 6
eloc 13
c 0
b 0
f 0
nc 11
nop 3
dl 0
loc 24
rs 9.2222
ccs 3
cts 5
cp 0.6
crap 8.304
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.1.8';
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);
87
        if ($driver === null) {
88
            return null;
89
        }
90
91
        return new static($fileName, $format, $driver, $password);
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);
104 23
    }
105
106
    /**
107 23
     * Prepare files list for archiving
108 23
     *
109 23
     * @param string|array $fileOrFiles File of list of files. See [[archiveFiles]] for details.
110
     * @param string|null $archiveName File name of archive. See [[archiveFiles]] for details.
111
     * @return array An array containing entries:
112
     * - totalSize (int) - size in bytes for all files
113
     * - numberOfFiles (int) - quantity of files
114 23
     * - files (array) - list of files prepared for archiving
115
     * - type (string|null) - prepared format for archive. One of class constants
116 23
     * @throws EmptyFileListException
117 23
     * @throws UnsupportedArchiveException
118 23
     */
119 23
    public static function prepareForArchiving($fileOrFiles, $archiveName = null)
120 23
    {
121 23
        if ($archiveName !== null) {
122
            $archiveType = Formats::detectArchiveFormat($archiveName, false);
123
            if ($archiveType === false) {
124
                throw new UnsupportedArchiveException('Could not detect archive type for name "' . $archiveName . '"');
125
            }
126 23
        }
127
128 23
        $files_list = static::createFilesList($fileOrFiles);
129 23
130
        if (empty($files_list)) {
131
            throw new EmptyFileListException('Files list is empty!');
132
        }
133
134
        $totalSize = 0;
135
        foreach ($files_list as $fn) {
136
            if ($fn !== null) {
137
                $totalSize += filesize($fn);
138
            }
139
        }
140
141
        return [
142
            'totalSize' => $totalSize,
143
            '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

143
            'numberOfFiles' => count(/** @scrutinizer ignore-type */ $files_list),
Loading history...
144
            'files' => $files_list,
145 2
            '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...
introduced by
The condition $archiveName !== null is always false.
Loading history...
146
        ];
147 2
    }
148
149
    /**
150
     * Creates an archive with passed files list
151
     *
152
     * @param string|string[]|array<string,string>||array<string,string[]> $fileOrFiles List of files.
0 ignored issues
show
Documentation Bug introduced by
The doc comment string|string[]|array<st...|array<string,string[]> at position 13 could not be parsed: Unknown type name '|' at position 13 in string|string[]|array<string,string>||array<string,string[]>.
Loading history...
153
     *  Can be one of three formats:
154
     *  1. A string containing path to file or directory.
155
     *     File will have it's basename.
156
     *      `UnifiedArchive::create('/etc/php.ini', 'archive.zip)` will store file with 'php.ini' name.
157
     *     Directory contents will be populated in archive root.
158
     *      `UnifiedArchive::create('/var/log/', 'archive.zip')` will store all directory contents in archive root.
159
     *  2. An array with strings containing paths to files or directories.
160
     *     Files and directories will be stored with full paths (expect leading slash).
161
     *      `UnifiedArchive::create(['/etc/php.ini', '/var/log/'], 'archive.zip)` will preserve full paths.
162
     *  3. An array with strings where keys are strings.
163
     *     Files will be named from key.
164
     *     Directory contents will be prefixed from key. If prefix is empty string, contents will be populated into
165
     *      archive root. If value is an array, all folder contents will have the same prefix.
166
     *      `UnifiedArchive::create([
167
     *          'doc.txt' => '/home/user/very_long_name_of_document.txt',
168
     *          'static' => '/var/www/html/static/',
169
     *          'collection' => ['/var/www/html/collection1/', '/var/www/html/collection2/'],
170
     *          '' => ['/var/www/html/readme/', '/var/www/html/docs/'], // root contents
171
     *      ], 'archive.zip')`
172
     *
173
     * @param string $archiveName File name of archive. Type of archive will be determined by its name.
174
     * @param int $compressionLevel Level of compression
175
     * @param string|null $password
176
     * @param callable|null $fileProgressCallable
177
     * @return int Count of stored files is returned.
178
     * @throws FileAlreadyExistsException
179
     * @throws UnsupportedOperationException
180
     */
181
    public static function create(
182
        $fileOrFiles,
183
        $archiveName,
184
        $compressionLevel = BasicDriver::COMPRESSION_AVERAGE,
185
        $password = null,
186
        $fileProgressCallable = null
187
    )
188
    {
189
        if (file_exists($archiveName)) {
190
            throw new FileAlreadyExistsException('Archive ' . $archiveName . ' already exists!');
191
        }
192
193
        $info = static::prepareForArchiving($fileOrFiles, $archiveName);
194
        $driver = static::getCreationDriver($info['type'], false, $password !== null);
195
196
        return $driver::createArchive(
197
            $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

197
            /** @scrutinizer ignore-type */ $info['files'],
Loading history...
198
            $archiveName,
199
            $info['type'],
200
            $compressionLevel,
201
            $password,
202
            $fileProgressCallable
203
        );
204
    }
205
206
    /**
207
     * Creates an archive with passed files list
208
     *
209
     * @param string|string[]|array<string,string> $fileOrFiles List of files. Can be one of three formats:
210 7
     *                             1. A string containing path to file or directory.
211
     *                                  File will have it's basename.
212 7
     *                                  `UnifiedArchive::archiveFiles('/etc/php.ini', 'archive.zip)` will store
213
     * file with 'php.ini' name.
214
     *                                  Directory contents will be stored in archive root.
215
     *                                  `UnifiedArchive::archiveFiles('/var/log/', 'archive.zip')` will store all
216
     * directory contents in archive root.
217
     *                             2. An array with strings containing pats to files or directories.
218
     *                                  Files and directories will be stored with full paths.
219 7
     *                                  `UnifiedArchive::archiveFiles(['/etc/php.ini', '/var/log/'], 'archive.zip)`
220
     * will preserve full paths.
221 7
     *                             3. An array with strings where keys are strings.
222
     *                                  Files will have name from key.
223
     *                                  Directories contents will have prefix from key.
224
     *                                  `UnifiedArchive::archiveFiles(['doc.txt' => 'very_long_name_of_document.txt',
225
     *  'static' => '/var/www/html/static/'], 'archive.zip')`
226
     * @param string $archiveFormat
227
     * @param int $compressionLevel Level of compression
228
     * @param string|null $password
229
     * @param callable|null $fileProgressCallable
230
     * @return int Count of stored files is returned.
231
     * @throws UnsupportedOperationException
232
     */
233
    public static function createInString(
234
        $fileOrFiles,
235
        $archiveFormat,
236
        $compressionLevel = BasicDriver::COMPRESSION_AVERAGE,
237
        $password = null,
238
        $fileProgressCallable = null
239 8
    )
240
    {
241 8
        $info = static::prepareForArchiving($fileOrFiles);
242
        try {
243
            $driver = static::getCreationDriver($archiveFormat, true, $password !== null);
244
        } catch (UnsupportedArchiveException $e) {
245
            // 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
246
            $driver = static::getCreationDriver($archiveFormat, false, $password !== null);
247
        }
248
249
        return $driver::createArchiveInString(
250
            $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

250
            /** @scrutinizer ignore-type */ $info['files'],
Loading history...
251
            $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

251
            /** @scrutinizer ignore-type */ $info['format'],
Loading history...
252 8
            $compressionLevel,
253
            $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

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

586
        $result = $this->archive->addFiles(/** @scrutinizer ignore-type */ $files_list);
Loading history...
587
        $this->scanArchive();
588
        return $result;
589
    }
590
591
    /**
592
     * Adds file into archive
593
     *
594
     * @param string $file File name to be added
595
     * @param string|null $inArchiveName If not passed, full path will be preserved.
596 4
     * @return bool
597
     * @throws ArchiveModificationException
598 4
     * @throws EmptyFileListException
599
     * @throws UnsupportedOperationException
600
     */
601 4
    public function addFile($file, $inArchiveName = null)
602 2
    {
603
        if (!is_file($file))
604 2
            throw new InvalidArgumentException($file . ' is not a valid file to add in archive');
605
606
        return ($inArchiveName !== null
607
                ? $this->add([$inArchiveName => $file])
608 2
                : $this->add([$file])) === 1;
609
    }
610
611
    /**
612
     * @param string $inArchiveName
613 2
     * @param string $content
614
     * @return bool
615
     * @throws ArchiveModificationException
616 2
     * @throws UnsupportedOperationException
617
     */
618
    public function addFileFromString($inArchiveName, $content)
619 2
    {
620 2
        $result = $this->archive->addFileFromString($inArchiveName, $content);
621
        $this->scanArchive();
622
        return $result;
623 2
    }
624
625 2
    /**
626 2
     * Adds directory contents to archive
627 2
     *
628
     * @param string $directory
629
     * @param string|null $inArchivePath If not passed, full paths will be preserved.
630
     * @return bool
631
     * @throws ArchiveModificationException
632 4
     * @throws EmptyFileListException
633
     * @throws UnsupportedOperationException
634
     */
635
    public function addDirectory($directory, $inArchivePath = null)
636
    {
637
        if (!is_dir($directory) || !is_readable($directory))
638
            throw new InvalidArgumentException($directory . ' is not a valid directory to add in archive');
639
640
        return ($inArchivePath !== null
641 2
                ? $this->add([$inArchivePath => $directory])
642
                : $this->add([$inArchivePath])) > 0;
643
    }
644
645
    /**
646 2
     * @param string|null $filter
647 2
     * @return true|string[]
648
     * @throws NonExistentArchiveFileException
649 2
     */
650 2
    public function test($filter = null)
651 2
    {
652 2
        $hash_exists = function_exists('hash_update_stream') && in_array('crc32b', hash_algos(), true);
653 2
        $failed = [];
654 2
        foreach ($this->getFiles($filter) as $fileName) {
655
            if ($hash_exists) {
656
                $ctx = hash_init('crc32b');
657 2
                hash_update_stream($ctx, $this->getFileStream($fileName));
658
                $actual_hash = hash_final($ctx);
659
            } else {
660
                $actual_hash = dechex(crc32($this->getFileContent($fileName)));
661
            }
662
            $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

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