Passed
Push — master ( b1d4f6...7824cd )
by f
10:25
created

UnifiedArchive::isFileExists()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 2
Bugs 0 Features 1
Metric Value
eloc 1
c 2
b 0
f 1
dl 0
loc 3
ccs 0
cts 0
cp 0
rs 10
cc 1
nc 1
nop 1
crap 2
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
    /** @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 string|null $password
61
     * @return UnifiedArchive|null Returns UnifiedArchive in case of successful reading of the file
62 23
     */
63
    public static function open($fileName, $password = null, $abilities = [])
64 23
    {
65
        if (!file_exists($fileName) || !is_readable($fileName)) {
66
            throw new InvalidArgumentException('Could not open file: ' . $fileName.' is not readable');
67
        }
68 23
69 23
        $format = Formats::detectArchiveFormat($fileName);
70
71
        if (empty($abilities)) {
72
            $abilities = [BasicDriver::OPEN];
73 23
            if (!empty($password)) {
74
                $abilities[] = [BasicDriver::OPEN_ENCRYPTED];
75
            }
76
        }
77
        $driver = Formats::getFormatDriver($format, $abilities);
78
        if ($driver === null) {
79
            throw new \RuntimeException('Driver for '.$format.' ('.$fileName.') is not found');
80
        }
81
82 21
        return new static($fileName, $format, $driver, $password);
83
    }
84 21
85 21
    /**
86
     * Checks whether archive can be opened with current system configuration
87
     *
88
     * @param string $fileName Archive filename
89
     * @return bool
90
     */
91
    public static function canOpen($fileName)
92
    {
93
        $format = Formats::detectArchiveFormat($fileName);
94
        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

94
        return $format !== false && Formats::canOpen(/** @scrutinizer ignore-type */ $format);
Loading history...
95 23
    }
96
97 23
    /**
98 23
     * Opens the file as one of supported formats
99
     *
100
     * @param string $fileName Archive filename
101
     * @param string $format Archive type
102 23
     * @param string|BasicDriver $driver
103 23
     * @param string|null $password
104 23
     */
105
    public function __construct($fileName, $format, $driver, $password = null)
106
    {
107 23
        $this->format = $format;
108 23
        $this->driver = $driver;
0 ignored issues
show
Documentation Bug introduced by
It seems like $driver can also be of type string. However, the property $driver is declared as type wapmorgan\UnifiedArchive\Drivers\BasicDriver. Maybe add an additional type check?

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

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

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

class Id
{
    public $id;

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

}

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

$account_id = false;

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

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

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

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

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

580
            /** @scrutinizer ignore-type */ $info['files'],
Loading history...
581
            $archiveName,
582
            $compressionLevel,
583
            $compressionLevel,
584
            $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

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