Zip::createArchive()   C
last analyzed

Complexity

Conditions 14
Paths 16

Size

Total Lines 57
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 14.6615

Importance

Changes 0
Metric Value
cc 14
eloc 33
c 0
b 0
f 0
nc 16
nop 6
dl 0
loc 57
ccs 17
cts 20
cp 0.85
crap 14.6615
rs 6.2666

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

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

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