Passed
Push — master ( e9611b...a3681d )
by f
13:07
created

Zip::createArchive()   C

Complexity

Conditions 14
Paths 16

Size

Total Lines 57
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 14.5719

Importance

Changes 0
Metric Value
cc 14
eloc 33
c 0
b 0
f 0
nc 16
nop 6
dl 0
loc 57
rs 6.2666
ccs 12
cts 14
cp 0.8571
crap 14.5719

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
namespace wapmorgan\UnifiedArchive\Drivers;
3
4
use Exception;
5
use wapmorgan\UnifiedArchive\ArchiveEntry;
6
use wapmorgan\UnifiedArchive\ArchiveInformation;
7
use wapmorgan\UnifiedArchive\Exceptions\ArchiveCreationException;
8
use wapmorgan\UnifiedArchive\Exceptions\ArchiveExtractionException;
9
use wapmorgan\UnifiedArchive\Exceptions\ArchiveModificationException;
10
use wapmorgan\UnifiedArchive\Exceptions\UnsupportedOperationException;
11
use wapmorgan\UnifiedArchive\Formats;
12
use ZipArchive;
13
14
/**
15
 * Class Zip
16
 *
17
 * @package wapmorgan\UnifiedArchive\Formats
18
 * @requires ext-zip
19
 */
20
class Zip extends BasicDriver
21
{
22
    const TYPE = self::TYPE_EXTENSION;
23
24
    /** @var ZipArchive */
25
    protected $zip;
26
27
    protected $pureFilesNumber;
28
29
    public static function getDescription()
30 1
    {
31
        return 'adapter for ext-zip'.(extension_loaded('zip') && defined('\ZipArchive::LIBZIP_VERSION') ? ' ('. ZipArchive::LIBZIP_VERSION.')' : null);
32
    }
33 1
34
    public static function isInstalled()
35
    {
36
        return extension_loaded('zip');
37
    }
38
39
    public static function getInstallationInstruction()
40
    {
41 1
        return 'install [zip] php extension';
42
    }
43
44 1
    /**
45 1
     * @return array
46
     */
47
    public static function getSupportedFormats()
48
    {
49
        return [
50
            Formats::ZIP
51
        ];
52
    }
53
54
    /**
55
     * @param $format
56
     * @return array
57
     */
58
    public static function checkFormatSupport($format)
59
    {
60
        if (!static::isInstalled()) {
61
            return [];
62
        }
63
        $abilities = [
64 4
            BasicDriver::OPEN,
65
            BasicDriver::OPEN_ENCRYPTED,
66 4
            BasicDriver::GET_COMMENT,
67 4
            BasicDriver::EXTRACT_CONTENT,
68
            BasicDriver::STREAM_CONTENT,
69 4
            BasicDriver::APPEND,
70
            BasicDriver::DELETE,
71
            BasicDriver::SET_COMMENT,
72
            BasicDriver::CREATE,
73
        ];
74
75 4
        if (static::canEncrypt($format)) {
76
            $abilities[] = BasicDriver::CREATE_ENCRYPTED;
77 4
        }
78 4
79 4
        return $abilities;
80
    }
81
82 4
    /**
83
     * @inheritDoc
84
     */
85
    public function __construct($archiveFileName, $format, $password = null)
86
    {
87 4
        $this->open($archiveFileName);
88
        if ($password !== null)
89 4
            $this->zip->setPassword($password);
90 4
    }
91
92
    /**
93
     * @param string $archiveFileName
94
     * @throws UnsupportedOperationException
95 4
     */
96
    protected function open($archiveFileName)
97 4
    {
98 4
        $this->zip = new ZipArchive();
99 4
        $open_result = $this->zip->open($archiveFileName);
100
        if ($open_result !== true) {
101 4
            throw new UnsupportedOperationException('Could not open Zip archive: '.$open_result);
102 4
        }
103 4
    }
104 4
105 4
    /**
106
     * Zip format destructor
107 4
     */
108
    public function __destruct()
109
    {
110
        unset($this->zip);
111
    }
112
113
    /**
114
     * @return ArchiveInformation
115
     */
116
    public function getArchiveInformation()
117
    {
118
        $information = new ArchiveInformation();
119
        $this->pureFilesNumber = 0;
120
        for ($i = 0; $i < $this->zip->numFiles; $i++) {
121
            $file = $this->zip->statIndex($i);
122
            // skip directories
123
            if (in_array(substr($file['name'], -1), ['/', '\\'], true))
124
                continue;
125
            $this->pureFilesNumber++;
126
            $information->files[$i] = $file['name'];
127
            $information->compressedFilesSize += $file['comp_size'];
128
            $information->uncompressedFilesSize += $file['size'];
129
        }
130
        return $information;
131
    }
132
133
    /**
134
     * @return false|string|null
135
     */
136
    public function getComment()
137
    {
138
        return $this->zip->getArchiveComment();
139
    }
140
141
    /**
142
     * @param string|null $comment
143
     * @return bool|null
144
     */
145
    public function setComment($comment)
146
    {
147
        return $this->zip->setArchiveComment($comment);
148
    }
149
150
    /**
151
     * @return array
152
     */
153
    public function getFileNames()
154
    {
155
        $files = [];
156
        for ($i = 0; $i < $this->zip->numFiles; $i++) {
157
            $file_name = $this->zip->getNameIndex($i);
158 1
            // skip directories
159
            if (in_array(substr($file_name, -1), ['/', '\\'], true))
160 1
                continue;
161 1
            $files[] = $file_name;
162 1
        }
163
        return $files;
164
    }
165
166
    /**
167
     * @param string $fileName
168
     *
169
     * @return bool
170
     */
171 2
    public function isFileExists($fileName)
172
    {
173 2
        return $this->zip->statName($fileName) !== false;
174 2
    }
175
176 2
    /**
177
     * @param string $fileName
178
     *
179
     * @return ArchiveEntry
180
     */
181
    public function getFileData($fileName)
182
    {
183 1
        $stat = $this->zip->statName($fileName);
184
185 1
        return new ArchiveEntry(
186
            $fileName,
187
            $stat['comp_size'],
188
            $stat['size'],
189
            $stat['mtime'],
190
            $stat['comp_method'] != 0,
191
            $this->zip->getCommentName($fileName),
192
            strtoupper(dechex($stat['crc'] < 0 ? sprintf('%u', $stat['crc']) : $stat['crc']))
0 ignored issues
show
Bug introduced by
It seems like $stat['crc'] < 0 ? sprin...['crc']) : $stat['crc'] can also be of type string; however, parameter $num of dechex() does only seem to accept integer, 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

192
            strtoupper(dechex(/** @scrutinizer ignore-type */ $stat['crc'] < 0 ? sprintf('%u', $stat['crc']) : $stat['crc']))
Loading history...
193
        );
194
    }
195
196
    /**
197
     * @param string $fileName
198
     *
199
     * @return string|false
200
     * @throws \Exception
201
     */
202
    public function getFileContent($fileName)
203
    {
204
        $result = $this->zip->getFromName($fileName);
205
        if ($result === false)
206
            throw new Exception('Could not get file information: '.$result.'. May use password?');
207
        return $result;
208
    }
209
210
    /**
211
     * @param $fileName
212
     * @return false|resource
213
     */
214
    public function getFileStream($fileName)
215
    {
216
        return $this->zip->getStream($fileName);
217
    }
218
219
    /**
220
     * @param string $outputFolder
221 1
     * @param array $files
222
     * @return int Number of extracted files
223 1
     * @throws ArchiveExtractionException
224 1
     */
225 1
    public function extractFiles($outputFolder, array $files)
226
    {
227 1
        if ($this->zip->extractTo($outputFolder, $files) === false)
228
            throw new ArchiveExtractionException($this->zip->getStatusString(), $this->zip->status);
229
230
        return count($files);
231 1
    }
232 1
233 1
    /**
234
     * @param string $outputFolder
235 1
     * @return int Number of extracted files
236
     *@throws ArchiveExtractionException
237
     */
238
    public function extractArchive($outputFolder)
239
    {
240
        if ($this->zip->extractTo($outputFolder) === false)
241
            throw new ArchiveExtractionException($this->zip->getStatusString(), $this->zip->status);
242
243
        return $this->pureFilesNumber;
244 1
    }
245
246 1
    /**
247 1
     * @param array $files
248 1
     * @return int
249
     * @throws ArchiveModificationException
250
     * @throws UnsupportedOperationException
251
     */
252 1
    public function deleteFiles(array $files)
253
    {
254 1
        $count = 0;
255
        foreach ($files as $file) {
256
            if ($this->zip->deleteName($file) === false)
257
                throw new ArchiveModificationException($this->zip->getStatusString(), $this->zip->status);
258
            $count++;
259 1
        }
260 1
261 1
        // reopen archive to save changes
262
        $archive_filename = $this->zip->filename;
263 1
        $this->zip->close();
264
        $this->open($archive_filename);
265
266
        return $count;
267
    }
268
269
    /**
270
     * @param array $files
271
     * @return int
272
     * @throws ArchiveModificationException
273
     * @throws UnsupportedOperationException
274
     */
275
    public function addFiles(array $files)
276
    {
277
        $added_files = 0;
278
        foreach ($files as $localName => $fileName) {
279
            if (is_null($fileName)) {
280
                if ($this->zip->addEmptyDir($localName) === false)
281
                    throw new ArchiveModificationException($this->zip->getStatusString(), $this->zip->status);
282
            } else {
283
                if ($this->zip->addFile($fileName, $localName) === false)
284
                    throw new ArchiveModificationException($this->zip->getStatusString(), $this->zip->status);
285
                $added_files++;
286 1
            }
287
        }
288 1
289
        // reopen archive to save changes
290
        $archive_filename = $this->zip->filename;
291
        $this->zip->close();
292
        $this->open($archive_filename);
293
294
        return $added_files;
295
    }
296 1
297 1
    /**
298
     * @param string $inArchiveName
299 1
     * @param string $content
300
     * @return bool
301
     */
302 1
    public function addFileFromString($inArchiveName, $content)
303 1
    {
304
        return $this->zip->addFromString($inArchiveName, $content);
305 1
    }
306
307
    /**
308
     * @param array $files
309 1
     * @param string $archiveFileName
310 1
     * @param int $archiveFormat
311 1
     * @param int $compressionLevel
312 1
     * @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...
313
     * @param $fileProgressCallable
314 1
     * @return int
315
     * @throws ArchiveCreationException
316 1
     * @throws UnsupportedOperationException
317 1
     */
318
    public static function createArchive(
319 1
        array $files,
320 1
        $archiveFileName,
321
        $archiveFormat,
322
        $compressionLevel = self::COMPRESSION_AVERAGE,
323
        $password = null,
324 1
        $fileProgressCallable = null
325
    ) {
326 1
        static $compressionLevelMap = [
327
            self::COMPRESSION_NONE => ZipArchive::CM_STORE,
328
            self::COMPRESSION_WEAK => ZipArchive::CM_DEFLATE,
329
            self::COMPRESSION_AVERAGE => ZipArchive::CM_DEFLATE,
330
            self::COMPRESSION_STRONG => ZipArchive::CM_DEFLATE,
331
            self::COMPRESSION_MAXIMUM => ZipArchive::CM_DEFLATE64,
332 1
        ];
333
334 1
        $zip = new ZipArchive();
335
        $result = $zip->open($archiveFileName, ZipArchive::CREATE);
336
337
        if ($result !== true)
338
            throw new ArchiveCreationException('ZipArchive error: '.$result);
339
340 1
        $can_set_compression_level = method_exists($zip, 'setCompressionName');
341
        $can_encrypt = static::canEncrypt(Formats::ZIP);
342 1
343
        if ($password !== null && !$can_encrypt) {
0 ignored issues
show
introduced by
The condition $password !== null is always false.
Loading history...
344
            throw new ArchiveCreationException('Encryption is not supported on current platform');
345
        }
346
347
        if ($fileProgressCallable !== null && !is_callable($fileProgressCallable)) {
348 1
            throw new ArchiveCreationException('File progress callable is not callable');
349
        }
350 1
351
        $current_file = 0;
352
        $total_files = count($files);
353
354
        foreach ($files as $localName => $fileName) {
355
            if ($fileName === null) {
356 1
                if ($zip->addEmptyDir($localName) === false)
357
                    throw new ArchiveCreationException('Could not archive directory "'.$localName.'": '.$zip->getStatusString(), $zip->status);
358 1
            } else {
359
                if ($zip->addFile($fileName, $localName) === false)
360
                    throw new ArchiveCreationException('Could not archive file "'.$fileName.'": '.$zip->getStatusString(), $zip->status);
361
                if ($can_set_compression_level) {
362
                    $zip->setCompressionName($localName, $compressionLevelMap[$compressionLevel]);
363
                }
364
                if ($password !== null && $can_encrypt) {
365
                    $zip->setEncryptionName($localName, ZipArchive::EM_AES_256, $password);
366
                }
367
            }
368
            if ($fileProgressCallable !== null) {
369
                call_user_func_array($fileProgressCallable, [$current_file++, $total_files, $fileName, $localName]);
370
            }
371
        }
372
        $zip->close();
373
374
        return count($files);
375
    }
376
377
    /**
378
     * @inheritDoc
379
     */
380
    public static function canEncrypt($format)
0 ignored issues
show
Unused Code introduced by
The parameter $format is not used and could be removed. ( Ignorable by Annotation )

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

380
    public static function canEncrypt(/** @scrutinizer ignore-unused */ $format)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
381
    {
382
        return method_exists('\ZipArchive', 'setEncryptionName');
383
    }
384
}
385