Completed
Push — master ( 07d0c9...44d352 )
by Marco
01:39
created

ZipBase::setPassword()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 1
1
<?php namespace Comodojo\Zip;
2
3
use \Comodojo\Foundation\Validation\DataFilter;
4
use \Comodojo\Exception\ZipException;
5
use \ZipArchive;
6
use \DirectoryIterator;
7
8
/**
9
 * comodojo/zip - ZipArchive toolbox
10
 *
11
 * @package     Comodojo Spare Parts
12
 * @author      Marco Giovinazzi <[email protected]>
13
 * @license     MIT
14
 *
15
 * LICENSE:
16
 *
17
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
 * THE SOFTWARE.
24
 */
25
26
class ZipBase {
27
28
    const SKIP_NONE = 'NONE';
29
30
    const SKIP_HIDDEN = 'HIDDEN';
31
32
    const SKIP_ALL = 'ALL';
33
34
    const SKIP_COMODOJO = 'COMODOJO';
35
36
    const DEFAULT_MASK = 0777;
37
38
    /**
39
     * Select files to skip
40
     *
41
     * @var string
42
     */
43
    private $skip_mode = self::SKIP_NONE;
44
45
    /**
46
     * Supported skip modes
47
     *
48
     * @var bool
49
     */
50
    private $supported_skip_modes = ['NONE', 'HIDDEN', 'ALL', 'COMODOJO'];
51
52
    /**
53
     * Mask for the extraction folder (if it should be created)
54
     *
55
     * @var int
56
     */
57
    private $mask = self::DEFAULT_MASK;
58
59
    /**
60
     * ZipArchive internal pointer
61
     *
62
     * @var object
63
     */
64
    private $zip_archive;
65
66
    /**
67
     * zip file name
68
     *
69
     * @var string
70
     */
71
    private $zip_file;
72
73
    /**
74
     * zip file password (only for extract)
75
     *
76
     * @var string
77
     */
78
    private $password;
79
80
    /**
81
     * Current base path
82
     *
83
     * @var string
84
     */
85
    private $path;
86
87
    /**
88
     * Class constructor
89
     *
90
     * @param   string  $zip_file   ZIP file name
91
     *
92
     * @throws  ZipException
93
     */
94
    public function __construct(string $zip_file) {
95
96
        if ( empty($zip_file) ) throw new ZipException(StatusCodes::get(ZipArchive::ER_NOENT));
97
98
        $this->zip_file = $zip_file;
99
100
    }
101
102
    /**
103
     * Set files to skip
104
     *
105
     * @param   string  $mode   [HIDDEN, COMODOJO, ALL, NONE]
106
     *
107
     * @return  Zip
108
     * @throws  ZipException
109
     */
110
    public function setSkipped(string $mode): self {
111
112
        $mode = strtoupper($mode);
113
114
        if ( !in_array($mode, $this->supported_skip_modes) ) throw new ZipException("Unsupported skip mode");
115
116
        $this->skip_mode = $mode;
117
118
        return $this;
119
120
    }
121
122
    /**
123
     * Get current skip mode (HIDDEN, COMODOJO, ALL, NONE)
124
     *
125
     * @return  string
126
     */
127
    public function getSkipped(): string {
128
129
        return $this->skip_mode;
130
131
    }
132
133
    /**
134
     * Set extraction password
135
     *
136
     * @param   string  $password
137
     *
138
     * @return  Zip
139
     */
140
    public function setPassword(string $password): self {
141
142
        $this->password = $password;
143
144
        return $this;
145
146
    }
147
148
    /**
149
     * Get current extraction password
150
     *
151
     * @return  string
152
     */
153
    public function getPassword(): ?string {
154
155
        return $this->password;
156
157
    }
158
159
    /**
160
     * Set current base path (just to add relative files to zip archive)
161
     *
162
     * @param   string|null  $path
163
     *
164
     * @return  Zip
165
     * @throws  ZipException
166
     */
167
    public function setPath(?string $path): self {
168
169
        if ( $path === null ) {
170
            $this->path = null;
171
        } else if ( !file_exists($path) ) {
172
            throw new ZipException("Not existent path");
173
        } else {
174
            $this->path = $path;
175
        }
176
177
        return $this;
178
179
    }
180
181
    /**
182
     * Get current base path
183
     *
184
     * @return  string
185
     */
186
    public function getPath(): ?string {
187
188
        return $this->path;
189
190
    }
191
192
    /**
193
     * Set extraction folder mask
194
     *
195
     * @param   int     $mask
196
     *
197
     * @return  Zip
198
     */
199
    public function setMask(int $mask): self {
200
201
        $mask = filter_var($mask, FILTER_VALIDATE_INT, array(
202
            "options" => array(
203
                "max_range" => self::DEFAULT_MASK,
204
                "default" => self::DEFAULT_MASK
205
            ), 'flags' => FILTER_FLAG_ALLOW_OCTAL
206
        ));
207
208
        $this->mask = $mask;
209
210
        return $this;
211
212
    }
213
214
    /**
215
     * Get current extraction folder mask
216
     *
217
     * @return  int
218
     */
219
     public function getMask(): int {
220
221
        return $this->mask;
222
223
    }
224
225
    /**
226
     * Set the current ZipArchive object
227
     *
228
     * @param   ZipArchive     $zip
229
     *
230
     * @return  Zip
231
     */
232
    public function setArchive(ZipArchive $zip): self {
233
234
        $this->zip_archive = $zip;
235
236
        return $this;
237
238
    }
239
240
    /**
241
     * Get current ZipArchive object
242
     *
243
     * @return  ZipArchive|null
244
     */
245
    public function getArchive(): ?ZipArchive {
246
247
        return $this->zip_archive;
248
249
    }
250
251
    /**
252
     * Get current zip file
253
     *
254
     * @return  string
255
     */
256
    public function getZipFile(): string {
257
258
        return $this->zip_file;
259
260
    }
261
262
    /**
263
     * Get a list of files in archive (array)
264
     *
265
     * @return  array
266
     * @throws  ZipException
267
     */
268
    public function listFiles(): array {
269
270
        $list = [];
271
272
        for ( $i = 0; $i < $this->zip_archive->numFiles; $i++ ) {
273
274
            $name = $this->zip_archive->getNameIndex($i);
275
276
            if ( $name === false ) throw new ZipException(StatusCodes::get($this->zip_archive->status));
277
278
            array_push($list, $name);
279
280
        }
281
282
        return $list;
283
284
    }
285
286
    /**
287
     * Extract files from zip archive
288
     *
289
     * @param   string  $destination    Destination path
290
     * @param   mixed   $files          (optional) a filename or an array of filenames
291
     *
292
     * @return  bool
293
     * @throws  ZipException
294
     */
295
    public function extract(string $destination, $files = null): bool {
296
297
        if ( empty($destination) ) throw new ZipException('Invalid destination path');
298
299
        if ( !file_exists($destination) ) {
300
301
            $omask = umask(0);
302
303
            $action = mkdir($destination, $this->mask, true);
304
305
            umask($omask);
306
307
            if ( $action === false ) throw new ZipException("Error creating folder $destination");
308
309
        }
310
311
        if ( !is_writable($destination) ) throw new ZipException('Destination path $destination not writable');
312
313
        if ( is_array($files) && @sizeof($files) != 0 ) {
314
315
            $file_matrix = $files;
316
317
        } else {
318
319
            $file_matrix = $this->getArchiveFiles();
320
321
        }
322
323
        if ( !empty($this->password) ) $this->zip_archive->setPassword($this->password);
324
325
        $extract = $this->zip_archive->extractTo($destination, $file_matrix);
326
327
        if ( $extract === false ) throw new ZipException(StatusCodes::get($this->zip_archive->status));
328
329
        return true;
330
331
    }
332
333
    /**
334
     * Add files to zip archive
335
     *
336
     * @param   mixed   $file_name_or_array     filename to add or an array of filenames
337
     * @param   bool    $flatten_root_folder    in case of directory, specify if root folder should be flatten or not
338
     *
339
     * @return  Zip
340
     * @throws  ZipException
341
     */
342
    public function add($file_name_or_array, bool $flatten_root_folder = false): self {
343
344
        if ( empty($file_name_or_array) ) throw new ZipException(StatusCodes::get(ZipArchive::ER_NOENT));
345
346
        $flatten_root_folder = DataFilter::filterBoolean($flatten_root_folder);
347
348
        try {
349
350
            if ( is_array($file_name_or_array) ) {
351
352
                foreach ( $file_name_or_array as $file_name ) {
353
                    $this->addItem($file_name, $flatten_root_folder);
354
                }
355
356
            } else {
357
                $this->addItem($file_name_or_array, $flatten_root_folder);
358
            }
359
360
        } catch (ZipException $ze) {
361
362
            throw $ze;
363
364
        }
365
366
        return $this;
367
368
    }
369
370
    /**
371
     * Delete files from zip archive
372
     *
373
     * @param   mixed   $file_name_or_array     filename to delete or an array of filenames
374
     *
375
     * @return  Zip
376
     * @throws  ZipException
377
     */
378
    public function delete($file_name_or_array): self {
379
380
        if ( empty($file_name_or_array) ) throw new ZipException(StatusCodes::get(ZipArchive::ER_NOENT));
381
382
        try {
383
384
            if ( is_array($file_name_or_array) ) {
385
386
                foreach ( $file_name_or_array as $file_name ) {
387
                    $this->deleteItem($file_name);
388
                }
389
390
            } else {
391
                $this->deleteItem($file_name_or_array);
392
            }
393
394
        } catch (ZipException $ze) {
395
396
            throw $ze;
397
398
        }
399
400
        return $this;
401
402
    }
403
404
    /**
405
     * Close the zip archive
406
     *
407
     * @return  bool
408
     * @throws  ZipException
409
     */
410
    public function close(): bool {
411
412
        if ( $this->zip_archive->close() === false ) throw new ZipException(StatusCodes::get($this->zip_archive->status));
413
414
        return true;
415
416
    }
417
418
    /**
419
     * Get a list of file contained in zip archive before extraction
420
     *
421
     * @return  array
422
     */
423
    private function getArchiveFiles(): array {
424
425
        $list = [];
426
427
        for ( $i = 0; $i < $this->zip_archive->numFiles; $i++ ) {
428
429
            $file = $this->zip_archive->statIndex($i);
430
431
            if ( $file === false ) continue;
432
433
            $name = str_replace('\\', '/', $file['name']);
434
435
            if ( $name[0] == "." AND in_array($this->skip_mode, ["HIDDEN", "ALL"]) ) continue;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
436
437
            if ( $name[0] == "." AND @$name[1] == "_" AND in_array($this->skip_mode, ["COMODOJO", "ALL"]) ) continue;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
438
439
            array_push($list, $name);
440
441
        }
442
443
        return $list;
444
445
    }
446
447
    /**
448
     * Add item to zip archive
449
     *
450
     * @param   string $file       File to add (realpath)
451
     * @param   bool   $flatroot   (optional) If true, source directory will be not included
452
     * @param   string $base       (optional) Base to record in zip file
453
     *
454
     * @throws  ZipException
455
     */
456
    private function addItem(string $file, bool $flatroot = false, ?string $base = null): void {
457
458
        $file = is_null($this->path) ? $file : "$this->path/$file";
459
460
        $real_file = str_replace('\\', '/', realpath($file));
461
462
        $real_name = basename($real_file);
463
464
        if ( !is_null($base) ) {
465
466
            if ( $real_name[0] == "." AND in_array($this->skip_mode, ["HIDDEN", "ALL"]) ) return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
467
468
            if ( $real_name[0] == "." AND @$real_name[1] == "_" AND in_array($this->skip_mode, ["COMODOJO", "ALL"]) ) return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
469
470
        }
471
472
        if ( is_dir($real_file) ) {
473
474
            if ( !$flatroot ) {
475
476
                $folder_target = is_null($base) ? $real_name : $base.$real_name;
477
478
                $new_folder = $this->zip_archive->addEmptyDir($folder_target);
479
480
                if ( $new_folder === false ) throw new ZipException(StatusCodes::get($this->zip_archive->status));
481
482
            } else {
483
484
                $folder_target = null;
485
486
            }
487
488
            foreach ( new DirectoryIterator($real_file) as $path ) {
489
490
                if ( $path->isDot() ) continue;
491
492
                $file_real = $path->getPathname();
493
494
                $base = is_null($folder_target) ? null : ($folder_target."/");
495
496
                try {
497
498
                    $this->addItem($file_real, false, $base);
499
500
                } catch (ZipException $ze) {
501
502
                    throw $ze;
503
504
                }
505
506
            }
507
508
        } else if ( is_file($real_file) ) {
509
510
            $file_target = is_null($base) ? $real_name : $base.$real_name;
511
512
            $add_file = $this->zip_archive->addFile($real_file, $file_target);
513
514
            if ( $add_file === false ) throw new ZipException(StatusCodes::get($this->zip_archive->status));
515
516
        } else {
517
            return;
518
        }
519
520
    }
521
522
    /**
523
     * Delete item from zip archive
524
     *
525
     * @param   string $file   File to delete (zippath)
526
     *
527
     * @throws  ZipException
528
     */
529
    private function deleteItem(string $file): void {
530
531
        $deleted = $this->zip_archive->deleteName($file);
532
533
        if ( $deleted === false ) throw new ZipException(StatusCodes::get($this->zip_archive->status));
534
535
    }
536
537
}
538