Passed
Push — master ( 5292c8...f9e47f )
by f
15:16
created

UnifiedArchive::open()   A

Complexity

Conditions 6
Paths 4

Size

Total Lines 17
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 7.3329

Importance

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

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

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

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

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

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

241
            /** @scrutinizer ignore-type */ $info['files'],
Loading history...
242
            $info['type'],
243
            $compressionLevel,
244
            $password,
0 ignored issues
show
Bug introduced by
It seems like $password can also be of type string; however, parameter $password of wapmorgan\UnifiedArchive...createArchiveInString() does only seem to accept null, maybe add an additional type check? ( Ignorable by Annotation )

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

244
            /** @scrutinizer ignore-type */ $password,
Loading history...
245
            $fileProgressCallable
246
        );
247
    }
248
249
    /**
250
     * @throws UnsupportedOperationException
251
     * @return BasicDriver
252 8
     */
253
    protected static function getCreationDriver($archiveFormat, $inString, $encrypted)
254 8
    {
255 8
        if (!Formats::canCreate($archiveFormat)) {
256
            throw new UnsupportedArchiveException('Unsupported archive type: ' . $archiveFormat);
257
        }
258
259
        $abilities = [Abilities::CREATE];
260
        if ($inString) {
261
            $abilities[] = Abilities::CREATE_IN_STRING;
262
        }
263
264
        if ($encrypted) {
265
            if (!Formats::canEncrypt($archiveFormat)) {
266
                throw new UnsupportedOperationException('Archive type ' . $archiveFormat . ' can not be encrypted');
267
            }
268
            $abilities[] = Abilities::CREATE_ENCRYPTED;
269
        }
270
271
        /** @var BasicDriver $driver */
272 6
        $driver = Formats::getFormatDriver($archiveFormat, $abilities);
273
        if ($driver === null) {
274 6
            throw new UnsupportedArchiveException('Unsupported archive type: ' . $archiveFormat . ' of archive ');
275
        }
276
        return $driver;
277
    }
278 6
279
    /**
280
     * Opens the file as one of supported formats
281
     *
282
     * @param string $fileName Archive filename
283
     * @param string $format Archive type
284
     * @param string|null $password
285
     */
286
    public function __construct($fileName, $format, $abilities = [], $password = null)
287
    {
288 8
        if (empty($abilities)) {
289
            $abilities = [Abilities::OPEN];
290 8
            if (!empty($password)) {
291
                $abilities[] = Abilities::OPEN_ENCRYPTED;
292
            }
293
        }
294 8
        $driver = Formats::getFormatDriver($format, $abilities);
295
        if ($driver === null) {
296
            throw new UnsupportedArchiveException(
297
                'Format ' . $format . ' driver with abilities ('
298
                . implode(
299
                    ', ',
300
                    array_map(function ($ability) {
301
                        return array_search($ability, Abilities::$abilitiesLabels);
302
                    }, $abilities)
303
                )
304 6
                . ') is not found');
305
        }
306 6
307
        $this->format = $format;
308
        $this->driver = $driver;
0 ignored issues
show
Documentation Bug introduced by
It seems like $driver of type string is incompatible with the declared type wapmorgan\UnifiedArchive\Drivers\Basic\BasicDriver of property $driver.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

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

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

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

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

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