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

Zip::createArchive()   C

Complexity

Conditions 14
Paths 16

Size

Total Lines 57
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 14.1691

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 19
cts 21
cp 0.9048
crap 14.1691

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\Drivers\Basic\BasicDriver;
8
use wapmorgan\UnifiedArchive\Drivers\Basic\BasicExtensionDriver;
9
use wapmorgan\UnifiedArchive\Exceptions\ArchiveCreationException;
10
use wapmorgan\UnifiedArchive\Exceptions\ArchiveExtractionException;
11
use wapmorgan\UnifiedArchive\Exceptions\ArchiveModificationException;
12
use wapmorgan\UnifiedArchive\Exceptions\UnsupportedOperationException;
13
use wapmorgan\UnifiedArchive\Formats;
14
use ZipArchive;
15
16
/**
17
 * Class Zip
18
 *
19
 * @package wapmorgan\UnifiedArchive\Formats
20
 * @requires ext-zip
21
 */
22
class Zip extends BasicExtensionDriver
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
    /**
35
     * @return array
36
     */
37
    public static function getSupportedFormats()
38
    {
39
        return [
40
            Formats::ZIP
41 1
        ];
42
    }
43
44 1
    /**
45 1
     * @param $format
46
     * @return array
47
     */
48
    public static function checkFormatSupport($format)
49
    {
50
        if (!static::isInstalled()) {
51
            return [];
52
        }
53
        $abilities = [
54
            BasicDriver::OPEN,
55
            BasicDriver::OPEN_ENCRYPTED,
56
            BasicDriver::GET_COMMENT,
57
            BasicDriver::EXTRACT_CONTENT,
58
            BasicDriver::STREAM_CONTENT,
59
            BasicDriver::APPEND,
60
            BasicDriver::DELETE,
61
            BasicDriver::SET_COMMENT,
62
            BasicDriver::CREATE,
63
        ];
64 4
65
        if (static::canEncrypt($format)) {
66 4
            $abilities[] = BasicDriver::CREATE_ENCRYPTED;
67 4
        }
68
69 4
        return $abilities;
70
    }
71
72
    /**
73
     * @inheritDoc
74
     */
75 4
    public function __construct($archiveFileName, $format, $password = null)
76
    {
77 4
        parent::__construct($archiveFileName, $format);
78 4
        $this->open($archiveFileName);
79 4
        if ($password !== null)
80
            $this->zip->setPassword($password);
81
    }
82 4
83
    /**
84
     * @param string $archiveFileName
85
     * @throws UnsupportedOperationException
86
     */
87 4
    protected function open($archiveFileName)
88
    {
89 4
        $this->zip = new ZipArchive();
90 4
        $open_result = $this->zip->open($archiveFileName);
91
        if ($open_result !== true) {
92
            throw new UnsupportedOperationException('Could not open Zip archive: '.$open_result);
93
        }
94
    }
95 4
96
    /**
97 4
     * Zip format destructor
98 4
     */
99 4
    public function __destruct()
100
    {
101 4
        unset($this->zip);
102 4
    }
103 4
104 4
    /**
105 4
     * @return ArchiveInformation
106
     */
107 4
    public function getArchiveInformation()
108
    {
109
        $information = new ArchiveInformation();
110
        $this->pureFilesNumber = 0;
111
        for ($i = 0; $i < $this->zip->numFiles; $i++) {
112
            $file = $this->zip->statIndex($i);
113
            // skip directories
114
            if (in_array(substr($file['name'], -1), ['/', '\\'], true))
115
                continue;
116
            $this->pureFilesNumber++;
117
            $information->files[$i] = $file['name'];
118
            $information->compressedFilesSize += $file['comp_size'];
119
            $information->uncompressedFilesSize += $file['size'];
120
        }
121
        return $information;
122
    }
123
124
    /**
125
     * @return false|string|null
126
     */
127
    public function getComment()
128
    {
129
        return $this->zip->getArchiveComment();
130
    }
131
132
    /**
133
     * @param string|null $comment
134
     * @return bool|null
135
     */
136
    public function setComment($comment)
137
    {
138
        return $this->zip->setArchiveComment($comment);
139
    }
140
141
    /**
142
     * @return array
143
     */
144
    public function getFileNames()
145
    {
146
        $files = [];
147
        for ($i = 0; $i < $this->zip->numFiles; $i++) {
148
            $file_name = $this->zip->getNameIndex($i);
149
            // skip directories
150
            if (in_array(substr($file_name, -1), ['/', '\\'], true))
151
                continue;
152
            $files[] = $file_name;
153
        }
154
        return $files;
155
    }
156
157
    /**
158 1
     * @param string $fileName
159
     *
160 1
     * @return bool
161 1
     */
162 1
    public function isFileExists($fileName)
163
    {
164
        return $this->zip->statName($fileName) !== false;
165
    }
166
167
    /**
168
     * @param string $fileName
169
     *
170
     * @return ArchiveEntry
171 2
     */
172
    public function getFileData($fileName)
173 2
    {
174 2
        $stat = $this->zip->statName($fileName);
175
176 2
        return new ArchiveEntry(
177
            $fileName,
178
            $stat['comp_size'],
179
            $stat['size'],
180
            $stat['mtime'],
181
            $stat['comp_method'] != 0,
182
            $this->zip->getCommentName($fileName),
183 1
            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

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

371
    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...
372
    {
373
        return method_exists('\ZipArchive', 'setEncryptionName');
374
    }
375
}
376