Passed
Push — master ( 96860e...392c1d )
by f
11:16
created

UnifiedArchive::test()   A

Complexity

Conditions 6
Paths 20

Size

Total Lines 18
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 6.1666

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 13
c 1
b 0
f 0
nc 20
nop 1
dl 0
loc 18
ccs 5
cts 6
cp 0.8333
crap 6.1666
rs 9.2222
1
<?php
2
namespace wapmorgan\UnifiedArchive;
3
4
use ArrayAccess;
5
use Countable;
6
use InvalidArgumentException;
7
use Iterator;
8
use wapmorgan\UnifiedArchive\Drivers\BasicDriver;
9
use wapmorgan\UnifiedArchive\Exceptions\ArchiveExtractionException;
10
use wapmorgan\UnifiedArchive\Exceptions\ArchiveModificationException;
11
use wapmorgan\UnifiedArchive\Exceptions\EmptyFileListException;
12
use wapmorgan\UnifiedArchive\Exceptions\FileAlreadyExistsException;
13
use wapmorgan\UnifiedArchive\Exceptions\NonExistentArchiveFileException;
14
use wapmorgan\UnifiedArchive\Exceptions\UnsupportedArchiveException;
15
use wapmorgan\UnifiedArchive\Exceptions\UnsupportedOperationException;
16
17
/**
18
 * Class which represents archive in one of supported formats.
19
 */
20
class UnifiedArchive implements ArrayAccess, Iterator, Countable
21
{
22
    const VERSION = '1.1.5';
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
    /**
51
     * @var null
52
     */
53
    protected $password;
54
55
    /**
56
     * Creates a UnifiedArchive instance for passed archive
57
     *
58
     * @param string $fileName Archive filename
59
     * @param null $password
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $password is correct as it would always require null to be passed?
Loading history...
60
     * @return UnifiedArchive|null Returns UnifiedArchive in case of successful reading of the file
61
     */
62 23
    public static function open($fileName, $password = null)
63
    {
64 23
        if (!file_exists($fileName) || !is_readable($fileName)) {
65
            throw new InvalidArgumentException('Could not open file: ' . $fileName.' is not readable');
66
        }
67
68 23
        $format = Formats::detectArchiveFormat($fileName);
69 23
        if (!Formats::canOpen($format)) {
70
            return null;
71
        }
72
73 23
        return new static($fileName, $format, $password);
74
    }
75
76
    /**
77
     * Checks whether archive can be opened with current system configuration
78
     *
79
     * @param string $fileName Archive filename
80
     * @return bool
81
     */
82 21
    public static function canOpen($fileName)
83
    {
84 21
        $format = Formats::detectArchiveFormat($fileName);
85 21
        return $format !== false && Formats::canOpen($format);
0 ignored issues
show
Bug introduced by
It seems like $format can also be of type true; however, parameter $format of wapmorgan\UnifiedArchive\Formats::canOpen() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

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

390
        $result = $this->archive->addFiles(/** @scrutinizer ignore-type */ $files_list);
Loading history...
391 2
        $this->scanArchive();
392 2
        return $result;
393
    }
394
395
    /**
396
     * Adds file into archive
397
     *
398
     * @param string $file File name to be added
399
     * @param string|null $inArchiveName If not passed, full path will be preserved.
400
     * @return bool
401
     * @throws ArchiveModificationException
402
     * @throws EmptyFileListException
403
     * @throws UnsupportedOperationException
404
     */
405 2
    public function addFile($file, $inArchiveName = null)
406
    {
407 2
        if (!is_file($file))
408
            throw new InvalidArgumentException($file.' is not a valid file to add in archive');
409
410 2
        return ($inArchiveName !== null
411 2
                ? $this->addFiles([$inArchiveName => $file])
412 2
                : $this->addFiles([$file])) === 1;
413
    }
414
415
    /**
416
     * @param string $inArchiveName
417
     * @param string $content
418
     * @return bool
419
     * @throws ArchiveModificationException
420
     * @throws UnsupportedOperationException
421
     */
422
    public function addFileFromString($inArchiveName, $content)
423
    {
424
        $result = $this->archive->addFileFromString($inArchiveName, $content);
425
        $this->scanArchive();
426
        return $result;
427
    }
428
429
    /**
430
     * Adds directory contents to archive
431
     *
432
     * @param string $directory
433
     * @param string|null $inArchivePath If not passed, full paths will be preserved.
434
     * @return bool
435
     * @throws ArchiveModificationException
436
     * @throws EmptyFileListException
437
     * @throws UnsupportedOperationException
438
     */
439
    public function addDirectory($directory, $inArchivePath = null)
440
    {
441
        if (!is_dir($directory) || !is_readable($directory))
442
            throw new InvalidArgumentException($directory.' is not a valid directory to add in archive');
443
444
        return ($inArchivePath !== null
445
                ? $this->addFiles([$inArchivePath => $directory])
446
                : $this->addFiles([$inArchivePath])) > 0;
447
    }
448
449
    /**
450
     * @param string|null $filter
451
     * @return true|string[]
452
     * @throws NonExistentArchiveFileException
453
     */
454
    public function test($filter = null)
455
    {
456
        $hash_exists = function_exists('hash_update_stream') && in_array('crc32b', hash_algos(), true);
457
        $failed = [];
458
        foreach ($this->getFileNames($filter) as $fileName) {
459
            if ($hash_exists) {
460
                $ctx = hash_init('crc32b');
461
                hash_update_stream($ctx, $this->getFileStream($fileName));
462 2
                $actual_hash = hash_final($ctx);
463
            } else {
464 2
                $actual_hash = dechex(crc32($this->getFileContent($fileName)));
465
            }
466 2
            $expected_hash = strtolower($this->getFileData($fileName)->crc32);
0 ignored issues
show
Bug introduced by
It seems like $this->getFileData($fileName)->crc32 can also be of type null; however, parameter $string of strtolower() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

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

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

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

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

570
            /** @scrutinizer ignore-type */ $password,
Loading history...
571
            $fileProgressCallable
572
        );
573
    }
574
575
    /**
576
     * Creates an archive with one file
577
     *
578
     * @param string $file
579
     * @param string $archiveName
580
     * @param int $compressionLevel Level of compression
581
     * @param null $password
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $password is correct as it would always require null to be passed?
Loading history...
582
     * @return bool
583
     * @throws FileAlreadyExistsException
584
     * @throws UnsupportedOperationException
585
     */
586
    public static function archiveFile($file, $archiveName, $compressionLevel = BasicDriver::COMPRESSION_AVERAGE, $password = null)
587
    {
588
        if (!is_file($file)) {
589
            throw new InvalidArgumentException($file . ' is not a valid file to archive');
590
        }
591
592
        return static::archiveFiles($file, $archiveName, $compressionLevel, $password) === 1;
593
    }
594
595
    /**
596 4
     * Creates an archive with full directory contents
597
     *
598 4
     * @param string $directory
599
     * @param string $archiveName
600
     * @param int $compressionLevel Level of compression
601 4
     * @param null $password
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $password is correct as it would always require null to be passed?
Loading history...
602 2
     * @return bool
603
     * @throws FileAlreadyExistsException
604 2
     * @throws UnsupportedOperationException
605
     */
606
    public static function archiveDirectory($directory, $archiveName, $compressionLevel = BasicDriver::COMPRESSION_AVERAGE, $password = null)
607
    {
608 2
        if (!is_dir($directory) || !is_readable($directory))
609
            throw new InvalidArgumentException($directory.' is not a valid directory to archive');
610
611
        return static::archiveFiles($directory, $archiveName, $compressionLevel, $password) > 0;
612
    }
613 2
614
    /**
615
     * Expands files list
616 2
     * @param $archiveFiles
617
     * @param $files
618
     * @return array
619 2
     */
620 2
    protected static function expandFileList($archiveFiles, $files)
621
    {
622
        $newFiles = [];
623 2
        foreach ($files as $file) {
624
            foreach ($archiveFiles as $archiveFile) {
625 2
                if (fnmatch($file.'*', $archiveFile)) {
626 2
                    $newFiles[] = $archiveFile;
627 2
                }
628
            }
629
        }
630
        return $newFiles;
631
    }
632 4
633
    /**
634
     * @param string|array $nodes
635
     * @return array|bool
636
     */
637
    protected static function createFilesList($nodes)
638
    {
639
        $files = [];
640
641 2
        // passed an extended list
642
        if (is_array($nodes)) {
643
            foreach ($nodes as $destination => $source) {
644
                // new format
645
                if (is_numeric($destination))
646 2
                    $destination = $source;
647 2
                else {
648
                    // old format
649 2
                    if (is_string($source) && !file_exists($source)) {
650 2
                        list($destination, $source) = [$source, $destination];
651 2
                    }
652 2
                }
653 2
654 2
                $destination = rtrim($destination, '/\\*');
655
656
                // few sources for directories
657 2
                if (is_array($source)) {
658
                    foreach ($source as $sourceItem) {
659
                        static::importFilesFromDir(
660
                            rtrim($sourceItem, '/\\*') . '/*',
661
                            !empty($destination) ? $destination . '/' : null,
662
                            true,
663
                            $files
664
                        );
665
                    }
666
                } else if (is_dir($source)) {
667
                    // one source for directories
668
                    static::importFilesFromDir(
669
                        rtrim($source, '/\\*') . '/*',
670
                        !empty($destination) ? $destination . '/' : null,
671
                        true,
672
                        $files
673
                    );
674
                } else if (is_file($source)) {
675
                    $files[$destination] = $source;
676
                }
677
            }
678
679
        } 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...
680
            // if is directory
681
            if (is_dir($nodes))
682
                static::importFilesFromDir(rtrim($nodes, '/\\*').'/*', null, true,
683
                    $files);
684
            else if (is_file($nodes))
685
                $files[basename($nodes)] = $nodes;
686
        }
687
688
        return $files;
689
    }
690
691
    /**
692
     * @param string $source
693
     * @param string|null $destination
694
     * @param bool $recursive
695
     * @param array $map
696
     */
697
    protected static function importFilesFromDir($source, $destination, $recursive, &$map)
698
    {
699
        // $map[$destination] = rtrim($source, '/*');
700
        // do not map root archive folder
701
702
        if ($destination !== null)
703
            $map[$destination] = null;
704
705
        foreach (glob($source, GLOB_MARK) as $node) {
706
            if (in_array(substr($node, -1), ['/', '\\'], true) && $recursive) {
707
                static::importFilesFromDir(str_replace('\\', '/', $node).'*',
708
                    $destination.basename($node).'/', $recursive, $map);
709
            } elseif (is_file($node) && is_readable($node)) {
710
                $map[$destination.basename($node)] = $node;
711
            }
712
        }
713
    }
714
715
    /**
716
     * Checks whether archive can be opened with current system configuration
717
     *
718
     * @param string $fileName Archive filename
719
     * @deprecated See {UnifiedArchive::canOpen()}
720
     * @return bool
721
     */
722
    public static function canOpenArchive($fileName)
723
    {
724
        return static::canOpen($fileName);
725
    }
726
727
    /**
728
     * Checks whether specific archive type can be opened with current system configuration
729
     *
730
     * @deprecated See {{Formats::canOpen()}}
731
     * @param string $type One of predefined archive types (class constants)
732
     * @return bool
733
     */
734
    public static function canOpenType($type)
735
    {
736
        return Formats::canOpen($type);
737
    }
738
739
    /**
740
     * Checks whether specified archive can be created
741
     *
742
     * @deprecated See {{Formats::canCreate()}}
743
     * @param string $type One of predefined archive types (class constants)
744
     * @return bool
745
     */
746
    public static function canCreateType($type)
747
    {
748
        return Formats::canCreate($type);
749
    }
750
751
    /**
752
     * Returns type of archive
753
     *
754
     * @deprecated See {{UnifiedArchive::getArchiveFormat()}}
755
     * @return string One of class constants
756
     */
757
    public function getArchiveType()
758
    {
759
        return $this->getFormat();
760
    }
761
762
    /**
763
     * Detect archive type by its filename or content
764
     *
765
     * @deprecated See {{Formats::detectArchiveFormat()}}
766
     * @param string $fileName Archive filename
767
     * @param bool $contentCheck Whether archive type can be detected by content
768
     * @return string|bool One of UnifiedArchive type constants OR false if type is not detected
769
     */
770
    public static function detectArchiveType($fileName, $contentCheck = true)
771
    {
772
        return Formats::detectArchiveFormat($fileName, $contentCheck);
773
    }
774
775
    /**
776
     * Returns a resource for reading file from archive
777
     *
778
     * @deprecated See {{UnifiedArchive::getFileStream}}
779
     * @param string $fileName File name in archive
780
     * @return resource
781
     * @throws NonExistentArchiveFileException
782
     */
783
    public function getFileResource($fileName)
784
    {
785
        return $this->getFileStream($fileName);
786
    }
787
788
    /**
789
     * Returns type of archive
790
     *
791
     * @deprecated See {{UnifiedArchive::getFormat}}
792
     * @return string One of class constants
793
     */
794
    public function getArchiveFormat()
795
    {
796
        return $this->getFormat();
797
    }
798
799
    /**
800
     * Checks that file exists in archive
801
     *
802
     * @deprecated See {{UnifiedArchive::hasFile}}
803
     * @param string $fileName File name in archive
804
     * @return bool
805
     */
806
    public function isFileExists($fileName)
807
    {
808
        return $this->hasFile($fileName);
809
    }
810
811
    /**
812
     * Returns size of archive file in bytes
813
     *
814
     * @deprecated See {{UnifiedArchive::getSize}}
815
     * @return int
816
     */
817
    public function getArchiveSize()
818
    {
819
        return $this->getSize();
820
    }
821
822
    /**
823
     * Counts cumulative size of all compressed data (in bytes)
824
     *
825
     * @deprecated See {{UnifiedArchive::getCompressedSize}}
826
     * @return int
827
     */
828
    public function countCompressedFilesSize()
829
    {
830
        return $this->getCompressedSize();
831
    }
832
833
    /**
834
     * Counts cumulative size of all uncompressed data (bytes)
835
     *
836
     * @deprecated See {{UnifiedArchive::getOriginalSize}}
837
     * @return int
838
     */
839
    public function countUncompressedFilesSize()
840
    {
841
        return $this->getOriginalSize();
842
    }
843
844
    /**
845
     * @param mixed $offset
846
     * @return bool
847
     */
848
    #[\ReturnTypeWillChange]
849
    public function offsetExists($offset)
850
    {
851
        return $this->hasFile($offset);
852
    }
853
854
    /**
855
     * @param mixed $offset
856
     * @return mixed|string
857
     * @throws NonExistentArchiveFileException
858
     */
859
    #[\ReturnTypeWillChange]
860
    public function offsetGet($offset)
861
    {
862
        return $this->getFileData($offset);
863
    }
864
865
    /**
866
     * @param mixed $offset
867
     * @param mixed $value
868
     * @return bool|void
869
     * @throws ArchiveModificationException
870
     * @throws UnsupportedOperationException
871
     */
872
    #[\ReturnTypeWillChange]
873
    public function offsetSet($offset, $value)
874
    {
875
        return $this->addFileFromString($offset, $value);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->addFileFromString($offset, $value) returns the type boolean which is incompatible with the return type mandated by ArrayAccess::offsetSet() of void.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
876
    }
877
878
    /**
879
     * @param mixed $offset
880
     * @return bool|int|void
881
     * @throws ArchiveModificationException
882
     * @throws UnsupportedOperationException
883
     */
884
    #[\ReturnTypeWillChange]
885
    public function offsetUnset($offset)
886
    {
887
        return $this->deleteFiles($offset);
888
    }
889
890
    #[\ReturnTypeWillChange]
891
    public function key()
892
    {
893
        return $this->files[$this->filesIterator];
894
    }
895
896
    /**
897
     * @throws NonExistentArchiveFileException
898
     */
899
    #[\ReturnTypeWillChange]
900
    public function current()
901
    {
902
        return $this->getFileData($this->files[$this->filesIterator]);
903
    }
904
905
    #[\ReturnTypeWillChange]
906
    public function next()
907
    {
908
        $this->filesIterator++;
909
    }
910
911
    #[\ReturnTypeWillChange]
912
    public function valid()
913
    {
914
        return $this->filesIterator < $this->filesQuantity;
915
    }
916
917
    #[\ReturnTypeWillChange]
918
    public function rewind()
919
    {
920
        $this->filesIterator = 0;
921
    }
922
923
    #[\ReturnTypeWillChange]
924
    public function count()
925
    {
926
        return $this->filesQuantity;
927
    }
928
}
929