Completed
Push — master ( 81e1ab...9d0e46 )
by Marco
15:06
created

Zip::addDirectoryItem()   A

Complexity

Conditions 6
Paths 9

Size

Total Lines 33
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 18
nc 9
nop 5
dl 0
loc 33
rs 9.0444
c 0
b 0
f 0
1
<?php namespace Comodojo\Zip;
2
3
use \Comodojo\Zip\Interfaces\ZipInterface;
4
use \Comodojo\Zip\Base\StatusCodes;
5
use \Comodojo\Zip\Traits\{
6
    SkipTrait,
7
    MaskTrait,
8
    PasswordTrait,
9
    PathTrait,
10
    ArchiveTrait
11
};
12
use \Comodojo\Foundation\Validation\DataFilter;
13
use \Comodojo\Exception\ZipException;
14
use \ZipArchive;
15
use \DirectoryIterator;
16
use \Countable;
17
18
/**
19
 * comodojo/zip - ZipArchive toolbox
20
 *
21
 * @package     Comodojo Spare Parts
22
 * @author      Marco Giovinazzi <[email protected]>
23
 * @license     MIT
24
 *
25
 * LICENSE:
26
 *
27
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
28
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
29
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
30
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
31
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
32
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
33
 * THE SOFTWARE.
34
 */
35
36
class Zip implements ZipInterface, Countable {
37
38
    use SkipTrait;
39
    use MaskTrait;
40
    use PasswordTrait;
41
    use PathTrait;
42
    use ArchiveTrait;
43
44
    /**
45
     * zip file name
46
     *
47
     * @var string
48
     */
49
    private $zip_file;
50
51
    /**
52
     * Class constructor
53
     *
54
     * @param string $zip_file ZIP file name
55
     *
56
     * @throws ZipException
57
     */
58
    public function __construct(string $zip_file) {
59
60
        if ( empty($zip_file) ) {
61
            throw new ZipException(StatusCodes::get(ZipArchive::ER_NOENT));
62
        }
63
64
        $this->zip_file = $zip_file;
65
66
    }
67
68
    /**
69
     * {@inheritdoc}
70
     */
71
    public static function open(string $zip_file): ZipInterface {
72
73
        try {
74
75
            $zip = new Zip($zip_file);
76
            $zip->setArchive(self::openZipFile($zip_file));
77
78
        } catch (ZipException $ze) {
79
            throw $ze;
80
        }
81
82
        return $zip;
83
84
    }
85
86
    /**
87
     * {@inheritdoc}
88
     */
89
    public static function check(string $zip_file): bool {
90
91
        try {
92
93
            $zip = self::openZipFile($zip_file, ZipArchive::CHECKCONS);
94
            $zip->close();
95
96
        } catch (ZipException $ze) {
97
            throw $ze;
98
        }
99
100
        return true;
101
102
    }
103
104
    /**
105
     * {@inheritdoc}
106
     */
107
    public static function create(string $zip_file, bool $overwrite = false): ZipInterface {
108
109
        $overwrite = DataFilter::filterBoolean($overwrite);
110
111
        try {
112
113
            $zip = new Zip($zip_file);
114
115
            if ( $overwrite ) {
116
                $zip->setArchive(
117
                    self::openZipFile(
118
                        $zip_file,
119
                        ZipArchive::CREATE | ZipArchive::OVERWRITE
120
                    )
121
                );
122
            } else {
123
                $zip->setArchive(
124
                    self::openZipFile($zip_file, ZipArchive::CREATE)
125
                );
126
            }
127
128
        } catch (ZipException $ze) {
129
            throw $ze;
130
        }
131
132
        return $zip;
133
134
    }
135
136
    /**
137
     * Count the number of files in the archive
138
     *
139
     * @return int
140
     */
141
    public function count(): int {
142
        return count($this->getArchive());
0 ignored issues
show
Bug introduced by
$this->getArchive() of type ZipArchive is incompatible with the type Countable|array expected by parameter $var of count(). ( Ignorable by Annotation )

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

142
        return count(/** @scrutinizer ignore-type */ $this->getArchive());
Loading history...
143
    }
144
145
    /**
146
     * Get current zip file
147
     *
148
     * @return string
149
     */
150
    public function getZipFile(): string {
151
152
        return $this->zip_file;
153
154
    }
155
156
    /**
157
     * {@inheritdoc}
158
     */
159
    public function listFiles(): array {
160
161
        $list = [];
162
163
        for ( $i = 0; $i < $this->getArchive()->numFiles; $i++ ) {
164
165
            $name = $this->getArchive()->getNameIndex($i);
166
            if ( $name === false ) {
167
                throw new ZipException(StatusCodes::get($this->getArchive()->status));
168
            }
169
            $list[] = $name;
170
171
        }
172
173
        return $list;
174
175
    }
176
177
    /**
178
     * {@inheritdoc}
179
     */
180
    public function extract(string $destination, $files = null): bool {
181
182
        if ( empty($destination) ) {
183
            throw new ZipException("Invalid destination path: $destination");
184
        }
185
186
        if ( !file_exists($destination) ) {
187
188
            $omask = umask(0);
189
            $action = mkdir($destination, $this->getMask(), true);
190
            umask($omask);
191
192
            if ( $action === false ) {
193
                throw new ZipException("Error creating folder: $destination");
194
            }
195
196
        }
197
198
        if ( !is_writable($destination) ) {
199
            throw new ZipException("Destination path $destination not writable");
200
        }
201
202
        if ( is_array($files) && @sizeof($files) != 0 ) {
203
            $file_matrix = $files;
204
        } else {
205
            $file_matrix = $this->getArchiveFiles();
206
        }
207
208
        if ( !empty($this->getPassword()) ) {
209
            $this->getArchive()->setPassword($this->getPassword());
210
        }
211
212
        if ( $this->getArchive()->extractTo($destination, $file_matrix) === false ) {
213
            throw new ZipException(StatusCodes::get($this->getArchive()->status));
214
        }
215
216
        return true;
217
218
    }
219
220
    /**
221
     * {@inheritdoc}
222
     */
223
    public function add(
224
        $file_name_or_array,
225
        bool $flatten_root_folder = false,
226
        int $compression = self::CM_DEFAULT
227
    ): ZipInterface {
228
229
        if ( empty($file_name_or_array) ) {
230
            throw new ZipException(StatusCodes::get(ZipArchive::ER_NOENT));
231
        }
232
233
        $flatten_root_folder = DataFilter::filterBoolean($flatten_root_folder);
234
235
        try {
236
237
            if ( is_array($file_name_or_array) ) {
238
                foreach ( $file_name_or_array as $file_name ) {
239
                    $this->addItem($file_name, $flatten_root_folder, $compression);
240
                }
241
            } else {
242
                $this->addItem($file_name_or_array, $flatten_root_folder, $compression);
243
            }
244
245
        } catch (ZipException $ze) {
246
            throw $ze;
247
        }
248
249
        return $this;
250
251
    }
252
253
    /**
254
     * {@inheritdoc}
255
     */
256
    public function delete($file_name_or_array): ZipInterface {
257
258
        if ( empty($file_name_or_array) ) {
259
            throw new ZipException(StatusCodes::get(ZipArchive::ER_NOENT));
260
        }
261
262
        try {
263
264
            if ( is_array($file_name_or_array) ) {
265
                foreach ( $file_name_or_array as $file_name ) {
266
                    $this->deleteItem($file_name);
267
                }
268
            } else {
269
                $this->deleteItem($file_name_or_array);
270
            }
271
272
        } catch (ZipException $ze) {
273
            throw $ze;
274
        }
275
276
        return $this;
277
278
    }
279
280
    /**
281
     * {@inheritdoc}
282
     */
283
    public function close(): bool {
284
285
        if ( $this->getArchive()->close() === false ) {
286
            throw new ZipException(StatusCodes::get($this->getArchive()->status));
287
        }
288
289
        return true;
290
291
    }
292
293
    /**
294
     * Get a list of file contained in zip archive before extraction
295
     *
296
     * @return array
297
     */
298
    private function getArchiveFiles(): array {
299
300
        $list = [];
301
302
        for ( $i = 0; $i < $this->getArchive()->numFiles; $i++ ) {
303
304
            $file = $this->getArchive()->statIndex($i);
305
            if ( $file === false ) {
306
                continue;
307
            }
308
309
            $name = str_replace('\\', '/', $file['name']);
310
            if (
311
                (
312
                    $name[0] == "." &&
313
                    in_array($this->getSkipMode(), ["HIDDEN", "ALL"])
314
                ) ||
315
                (
316
                    $name[0] == "." &&
317
                    @$name[1] == "_" &&
318
                    in_array($this->getSkipMode(), ["COMODOJO", "ALL"])
319
                )
320
            ) {
321
                continue;
322
            }
323
324
            $list[] = $name;
325
326
        }
327
328
        return $list;
329
330
    }
331
332
    /**
333
     * Add item to zip archive
334
     *
335
     * @param string $file File to add (realpath)
336
     * @param bool $flatroot (optional) If true, source directory will be not included
337
     * @param string $base (optional) Base to record in zip file
338
     * @return void
339
     * @throws ZipException
340
     */
341
    private function addItem(
342
        string $file,
343
        bool $flatroot = false,
344
        int $compression = self::CM_DEFAULT,
345
        ?string $base = null
346
    ): void {
347
348
        $file = is_null($this->getPath()) ? $file : $this->getPath()."/$file";
0 ignored issues
show
introduced by
The condition is_null($this->getPath()) is always false.
Loading history...
349
        $real_file = str_replace('\\', '/', realpath($file));
350
        $real_name = basename($real_file);
351
352
        if (
353
            $base !== null &&
354
            (
355
                (
356
                    $real_name[0] == "." &&
357
                    in_array($this->getSkipMode(), ["HIDDEN", "ALL"])
358
                ) ||
359
                (
360
                    $real_name[0] == "." &&
361
                    @$real_name[1] == "_" &&
362
                    in_array($this->getSkipMode(), ["COMODOJO", "ALL"])
363
                )
364
            )
365
        ) {
366
            return;
367
        }
368
369
        if ( is_dir($real_file) ) {
370
            $this->addDirectoryItem($real_file, $real_name, $compression, $base, $flatroot);
371
        } else {
372
            $this->addFileItem($real_file, $real_name, $compression, $base);
373
        }
374
375
    }
376
377
    private function addFileItem(
378
        string $real_file,
379
        string $real_name,
380
        int $compression = self::CM_DEFAULT,
381
        ?string $base = null
382
    ): void {
383
384
        $file_target = is_null($base) ? $real_name : $base.$real_name;
385
        if (
386
            $this->getArchive()->addFile($real_file, $file_target) === false ||
387
            $this->getArchive()->setCompressionName($file_target, $compression) === false
388
        ) {
389
            throw new ZipException(StatusCodes::get($this->getArchive()->status));
390
        }
391
392
    }
393
394
    private function addDirectoryItem(
395
        string $real_file,
396
        string $real_name,
397
        int $compression = self::CM_DEFAULT,
398
        ?string $base = null,
399
        bool $flatroot = false
400
    ): void {
401
402
        if ( !$flatroot ) {
403
            $folder_target = $base.$real_name;
404
            $new_base = "$folder_target/";
405
            if ( $this->getArchive()->addEmptyDir($folder_target) === false ) {
406
                throw new ZipException(StatusCodes::get($this->getArchive()->status));
407
            }
408
        } else {
409
            $new_base = null;
410
        }
411
412
        foreach ( new DirectoryIterator($real_file) as $path ) {
413
414
            if ( $path->isDot() ) {
415
                continue;
416
            }
417
418
            try {
419
                $this->addItem(
420
                    $path->getPathname(),
421
                    false,
422
                    $compression,
423
                    $new_base
424
                );
425
            } catch (ZipException $ze) {
426
                throw $ze;
427
            }
428
429
        }
430
431
    }
432
433
    /**
434
     * Delete item from zip archive
435
     *
436
     * @param string $file File to delete (zippath)
437
     * @return void
438
     * @throws ZipException
439
     */
440
    private function deleteItem(string $file): void {
441
442
        if ( $this->getArchive()->deleteName($file) === false ) {
443
            throw new ZipException(StatusCodes::get($this->getArchive()->status));
444
        }
445
446
    }
447
448
    /**
449
     * Open a zip file
450
     *
451
     * @param string $zip_file ZIP file name
452
     * @param int $flags ZipArchive::open flags
453
     *
454
     * @return  ZipArchive
455
     * @throws  ZipException
456
     */
457
    private static function openZipFile(string $zip_file, int $flags = null): ZipArchive {
458
459
        $zip = new ZipArchive();
460
461
        $open = $zip->open($zip_file, $flags);
462
        if ( $open !== true ) {
463
            throw new ZipException(StatusCodes::get($open));
464
        }
465
466
        return $zip;
467
468
    }
469
470
}
471