ZipEntry::create()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 37
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 1
eloc 18
nc 1
nop 16
dl 0
loc 37
ccs 19
cts 19
cp 1
crap 1
rs 9.6666
c 1
b 0
f 1

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
/** @noinspection PhpUsageOfSilenceOperatorInspection */
4
5
namespace PhpZip\Model;
6
7
use PhpZip\Constants\DosAttrs;
8
use PhpZip\Constants\DosCodePage;
9
use PhpZip\Constants\GeneralPurposeBitFlag;
10
use PhpZip\Constants\UnixStat;
11
use PhpZip\Constants\ZipCompressionLevel;
12
use PhpZip\Constants\ZipCompressionMethod;
13
use PhpZip\Constants\ZipConstants;
14
use PhpZip\Constants\ZipEncryptionMethod;
15
use PhpZip\Constants\ZipPlatform;
16
use PhpZip\Constants\ZipVersion;
17
use PhpZip\Exception\InvalidArgumentException;
18
use PhpZip\Exception\RuntimeException;
19
use PhpZip\Exception\ZipUnsupportMethodException;
20
use PhpZip\Model\Extra\ExtraFieldsCollection;
21
use PhpZip\Model\Extra\Fields\AsiExtraField;
22
use PhpZip\Model\Extra\Fields\ExtendedTimestampExtraField;
23
use PhpZip\Model\Extra\Fields\NtfsExtraField;
24
use PhpZip\Model\Extra\Fields\OldUnixExtraField;
25
use PhpZip\Model\Extra\Fields\UnicodePathExtraField;
26
use PhpZip\Model\Extra\Fields\WinZipAesExtraField;
27
use PhpZip\Model\Extra\ZipExtraField;
28
use PhpZip\Util\DateTimeConverter;
29
use PhpZip\Util\StringUtil;
30
31
/**
32
 * ZIP file entry.
33
 *
34
 * @see     https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
35
 *
36
 * @author  Ne-Lexa [email protected]
37
 * @license MIT
38
 */
39
class ZipEntry
40
{
41
    /** @var int the unknown value for numeric properties */
42
    const UNKNOWN = -1;
43
44
    /**
45
     * @var int DOS platform
46
     *
47
     * @deprecated Use {@see ZipPlatform::OS_DOS}
48
     */
49
    const PLATFORM_FAT = ZipPlatform::OS_DOS;
50
51
    /**
52
     * @var int Unix platform
53
     *
54
     * @deprecated Use {@see ZipPlatform::OS_UNIX}
55
     */
56
    const PLATFORM_UNIX = ZipPlatform::OS_UNIX;
57
58
    /**
59
     * @var int MacOS platform
60
     *
61
     * @deprecated Use {@see ZipPlatform::OS_MAC_OSX}
62
     */
63
    const PLATFORM_OS_X = ZipPlatform::OS_MAC_OSX;
64
65
    /**
66
     * Pseudo compression method for WinZip AES encrypted entries.
67
     * Require php extension openssl or mcrypt.
68
     *
69
     * @deprecated Use {@see ZipCompressionMethod::WINZIP_AES}
70
     */
71
    const METHOD_WINZIP_AES = ZipCompressionMethod::WINZIP_AES;
72
73
    /** @var string Entry name (filename in archive) */
74
    private $name;
75
76
    /** @var bool Is directory */
77
    private $isDirectory;
78
79
    /** @var ZipData|null Zip entry contents */
80
    private $data;
81
82
    /** @var int Made by platform */
83
    private $createdOS = self::UNKNOWN;
84
85
    /** @var int Extracted by platform */
86
    private $extractedOS = self::UNKNOWN;
87
88
    /** @var int Software version */
89
    private $softwareVersion = self::UNKNOWN;
90
91
    /** @var int Version needed to extract */
92
    private $extractVersion = self::UNKNOWN;
93
94
    /** @var int Compression method */
95
    private $compressionMethod = self::UNKNOWN;
96
97
    /** @var int General purpose bit flags */
98
    private $generalPurposeBitFlags = 0;
99
100
    /** @var int Dos time */
101
    private $dosTime = self::UNKNOWN;
102
103
    /** @var int Crc32 */
104
    private $crc = self::UNKNOWN;
105
106
    /** @var int Compressed size */
107
    private $compressedSize = self::UNKNOWN;
108
109
    /** @var int Uncompressed size */
110
    private $uncompressedSize = self::UNKNOWN;
111
112
    /** @var int Internal attributes */
113
    private $internalAttributes = 0;
114
115
    /** @var int External attributes */
116
    private $externalAttributes = 0;
117
118
    /** @var int relative Offset Of Local File Header */
119
    private $localHeaderOffset = 0;
120
121
    /**
122
     * Collections of Extra Fields in Central Directory.
123
     * Keys from Header ID [int] and value Extra Field [ExtraField].
124
     *
125
     * @var ExtraFieldsCollection
126
     */
127
    protected $cdExtraFields;
128
129
    /**
130
     * Collections of Extra Fields int local header.
131
     * Keys from Header ID [int] and value Extra Field [ExtraField].
132
     *
133
     * @var ExtraFieldsCollection
134
     */
135
    protected $localExtraFields;
136
137
    /** @var string|null comment field */
138
    private $comment;
139
140
    /** @var string|null entry password for read or write encryption data */
141
    private $password;
142
143
    /** @var int encryption method */
144
    private $encryptionMethod = ZipEncryptionMethod::NONE;
145
146
    /** @var int */
147
    private $compressionLevel = ZipCompressionLevel::NORMAL;
148
149
    /** @var string|null */
150
    private $charset;
151
152
    /**
153
     * ZipEntry constructor.
154
     *
155
     * @param string      $name    Entry name
156
     * @param string|null $charset DOS charset
157
     */
158 351
    public function __construct($name, $charset = null)
159
    {
160 351
        $this->setName($name, $charset);
161
162 348
        $this->cdExtraFields = new ExtraFieldsCollection();
163 348
        $this->localExtraFields = new ExtraFieldsCollection();
164 348
    }
165
166
    /**
167
     * This method only internal use.
168
     *
169
     * @param string      $name
170
     * @param int         $createdOS
171
     * @param int         $extractedOS
172
     * @param int         $softwareVersion
173
     * @param int         $extractVersion
174
     * @param int         $compressionMethod
175
     * @param int         $gpbf
176
     * @param int         $dosTime
177
     * @param int         $crc
178
     * @param int         $compressedSize
179
     * @param int         $uncompressedSize
180
     * @param int         $internalAttributes
181
     * @param int         $externalAttributes
182
     * @param int         $offsetLocalHeader
183
     * @param string|null $comment
184
     * @param string|null $charset
185
     *
186
     * @return ZipEntry
187
     *
188
     * @internal
189
     *
190
     * @noinspection PhpTooManyParametersInspection
191
     */
192 134
    public static function create(
193
        $name,
194
        $createdOS,
195
        $extractedOS,
196
        $softwareVersion,
197
        $extractVersion,
198
        $compressionMethod,
199
        $gpbf,
200
        $dosTime,
201
        $crc,
202
        $compressedSize,
203
        $uncompressedSize,
204
        $internalAttributes,
205
        $externalAttributes,
206
        $offsetLocalHeader,
207
        $comment,
208
        $charset
209
    ) {
210 134
        $entry = new self($name);
211 134
        $entry->createdOS = (int) $createdOS;
212 134
        $entry->extractedOS = (int) $extractedOS;
213 134
        $entry->softwareVersion = (int) $softwareVersion;
214 134
        $entry->extractVersion = (int) $extractVersion;
215 134
        $entry->compressionMethod = (int) $compressionMethod;
216 134
        $entry->generalPurposeBitFlags = (int) $gpbf;
217 134
        $entry->dosTime = (int) $dosTime;
218 134
        $entry->crc = (int) $crc;
219 134
        $entry->compressedSize = (int) $compressedSize;
220 134
        $entry->uncompressedSize = (int) $uncompressedSize;
221 134
        $entry->internalAttributes = (int) $internalAttributes;
222 134
        $entry->externalAttributes = (int) $externalAttributes;
223 134
        $entry->localHeaderOffset = (int) $offsetLocalHeader;
224 134
        $entry->setComment($comment);
225 134
        $entry->setCharset($charset);
226 134
        $entry->updateCompressionLevel();
227
228 134
        return $entry;
229
    }
230
231
    /**
232
     * Set entry name.
233
     *
234
     * @param string      $name    New entry name
235
     * @param string|null $charset
236
     *
237
     * @return ZipEntry
238
     */
239 351
    private function setName($name, $charset = null)
240
    {
241 351
        if ($name === null) {
0 ignored issues
show
introduced by
The condition $name === null is always false.
Loading history...
242 1
            throw new InvalidArgumentException('zip entry name is null');
243
        }
244
245 350
        $name = ltrim((string) $name, '\\/');
246
247 350
        if ($name === '') {
248 2
            throw new InvalidArgumentException('Empty zip entry name');
249
        }
250
251 348
        $name = (string) $name;
252 348
        $length = \strlen($name);
253
254 348
        if ($length > 0xffff) {
255
            throw new InvalidArgumentException('Illegal zip entry name parameter');
256
        }
257
258 348
        $this->setCharset($charset);
259
260 348
        if ($this->charset === null && !StringUtil::isASCII($name)) {
261 24
            $this->enableUtf8Name(true);
262
        }
263 348
        $this->name = $name;
264 348
        $this->isDirectory = ($length = \strlen($name)) >= 1 && $name[$length - 1] === '/';
265 348
        $this->externalAttributes = $this->isDirectory ? DosAttrs::DOS_DIRECTORY : DosAttrs::DOS_ARCHIVE;
266
267 348
        if ($this->extractVersion !== self::UNKNOWN) {
268 5
            $this->extractVersion = max(
269 5
                $this->extractVersion,
270 5
                $this->isDirectory ?
271 1
                    ZipVersion::v20_DEFLATED_FOLDER_ZIPCRYPTO :
272 5
                    ZipVersion::v10_DEFAULT_MIN
273
            );
274
        }
275
276 348
        return $this;
277
    }
278
279
    /**
280
     * @param string|null $charset
281
     *
282
     * @return ZipEntry
283
     *
284
     * @see DosCodePage::getCodePages()
285
     */
286 348
    public function setCharset($charset = null)
287
    {
288 348
        if ($charset !== null && $charset === '') {
289 1
            throw new InvalidArgumentException('Empty charset');
290
        }
291 348
        $this->charset = $charset;
292
293 348
        return $this;
294
    }
295
296
    /**
297
     * @return string|null
298
     */
299 137
    public function getCharset()
300
    {
301 137
        return $this->charset;
302
    }
303
304
    /**
305
     * @param string $newName New entry name
306
     *
307
     * @return ZipEntry new {@see ZipEntry} object with new name
308
     *
309
     * @internal
310
     */
311 8
    public function rename($newName)
312
    {
313 8
        $newEntry = clone $this;
314 8
        $newEntry->setName($newName);
315
316 8
        $newEntry->removeExtraField(UnicodePathExtraField::HEADER_ID);
317
318 8
        return $newEntry;
319
    }
320
321
    /**
322
     * Returns the ZIP entry name.
323
     *
324
     * @return string
325
     */
326 182
    public function getName()
327
    {
328 182
        return $this->name;
329
    }
330
331
    /**
332
     * @return ZipData|null
333
     *
334
     * @internal
335
     */
336 151
    public function getData()
337
    {
338 151
        return $this->data;
339
    }
340
341
    /**
342
     * @param ZipData|null $data
343
     *
344
     * @internal
345
     */
346 173
    public function setData($data)
347
    {
348 173
        $this->data = $data;
349 173
    }
350
351
    /**
352
     * @return int Get platform
353
     *
354
     * @deprecated Use {@see ZipEntry::getCreatedOS()}
355
     */
356
    public function getPlatform()
357
    {
358
        @trigger_error(__METHOD__ . ' is deprecated. Use ' . __CLASS__ . '::getCreatedOS()', \E_USER_DEPRECATED);
359
360
        return $this->getCreatedOS();
361
    }
362
363
    /**
364
     * @param int $platform
365
     *
366
     * @return ZipEntry
367
     *
368
     * @deprecated Use {@see ZipEntry::setCreatedOS()}
369
     */
370
    public function setPlatform($platform)
371
    {
372
        @trigger_error(__METHOD__ . ' is deprecated. Use ' . __CLASS__ . '::setCreatedOS()', \E_USER_DEPRECATED);
373
374
        return $this->setCreatedOS($platform);
375
    }
376
377
    /**
378
     * @return int platform
379
     */
380 154
    public function getCreatedOS()
381
    {
382 154
        return $this->createdOS;
383
    }
384
385
    /**
386
     * Set platform.
387
     *
388
     * @param int $platform
389
     *
390
     * @return ZipEntry
391
     */
392 178
    public function setCreatedOS($platform)
393
    {
394 178
        $platform = (int) $platform;
395
396 178
        if ($platform < 0x00 || $platform > 0xff) {
397 2
            throw new InvalidArgumentException('Platform out of range');
398
        }
399 176
        $this->createdOS = $platform;
400
401 176
        return $this;
402
    }
403
404
    /**
405
     * @return int
406
     */
407 139
    public function getExtractedOS()
408
    {
409 139
        return $this->extractedOS;
410
    }
411
412
    /**
413
     * Set extracted OS.
414
     *
415
     * @param int $platform
416
     *
417
     * @return ZipEntry
418
     */
419 178
    public function setExtractedOS($platform)
420
    {
421 178
        $platform = (int) $platform;
422
423 178
        if ($platform < 0x00 || $platform > 0xff) {
424 2
            throw new InvalidArgumentException('Platform out of range');
425
        }
426 176
        $this->extractedOS = $platform;
427
428 176
        return $this;
429
    }
430
431
    /**
432
     * @return int
433
     */
434 136
    public function getSoftwareVersion()
435
    {
436 136
        if ($this->softwareVersion === self::UNKNOWN) {
437 134
            return $this->getExtractVersion();
438
        }
439
440 37
        return $this->softwareVersion;
441
    }
442
443
    /**
444
     * @param int $softwareVersion
445
     *
446
     * @return ZipEntry
447
     */
448 1
    public function setSoftwareVersion($softwareVersion)
449
    {
450 1
        $this->softwareVersion = (int) $softwareVersion;
451
452 1
        return $this;
453
    }
454
455
    /**
456
     * Version needed to extract.
457
     *
458
     * @return int
459
     *
460
     * @deprecated Use {@see ZipEntry::getExtractVersion()}
461
     */
462
    public function getVersionNeededToExtract()
463
    {
464
        @trigger_error(__METHOD__ . ' is deprecated. Use ' . __CLASS__ . '::getExtractVersion()', \E_USER_DEPRECATED);
465
466
        return $this->getExtractVersion();
467
    }
468
469
    /**
470
     * Version needed to extract.
471
     *
472
     * @return int
473
     */
474 145
    public function getExtractVersion()
475
    {
476 145
        if ($this->extractVersion === self::UNKNOWN) {
477 143
            if (ZipEncryptionMethod::isWinZipAesMethod($this->encryptionMethod)) {
478 14
                return ZipVersion::v51_ENCR_AES_RC2_CORRECT;
479
            }
480
481 138
            if ($this->compressionMethod === ZipCompressionMethod::BZIP2) {
482 6
                return ZipVersion::v46_BZIP2;
483
            }
484
485 138
            if ($this->isZip64ExtensionsRequired()) {
486
                return ZipVersion::v45_ZIP64_EXT;
487
            }
488
489
            if (
490 138
                $this->compressionMethod === ZipCompressionMethod::DEFLATED ||
491 121
                $this->isDirectory ||
492 138
                $this->encryptionMethod === ZipEncryptionMethod::PKWARE
493
            ) {
494 69
                return ZipVersion::v20_DEFLATED_FOLDER_ZIPCRYPTO;
495
            }
496
497 113
            return ZipVersion::v10_DEFAULT_MIN;
498
        }
499
500 32
        return $this->extractVersion;
501
    }
502
503
    /**
504
     * Set version needed to extract.
505
     *
506
     * @param int $version
507
     *
508
     * @return ZipEntry
509
     *
510
     * @deprecated Use {@see ZipEntry::setExtractVersion()}
511
     */
512
    public function setVersionNeededToExtract($version)
513
    {
514
        @trigger_error(__METHOD__ . ' is deprecated. Use ' . __CLASS__ . '::setExtractVersion()', \E_USER_DEPRECATED);
515
516
        return $this->setExtractVersion($version);
517
    }
518
519
    /**
520
     * Set version needed to extract.
521
     *
522
     * @param int $version
523
     *
524
     * @return ZipEntry
525
     */
526 2
    public function setExtractVersion($version)
527
    {
528 2
        $this->extractVersion = max(ZipVersion::v10_DEFAULT_MIN, (int) $version);
529
530 2
        return $this;
531
    }
532
533
    /**
534
     * Returns the compressed size of this entry.
535
     *
536
     * @return int
537
     */
538 154
    public function getCompressedSize()
539
    {
540 154
        return $this->compressedSize;
541
    }
542
543
    /**
544
     * Sets the compressed size of this entry.
545
     *
546
     * @param int $compressedSize the Compressed Size
547
     *
548
     * @return ZipEntry
549
     *
550
     * @internal
551
     */
552 154
    public function setCompressedSize($compressedSize)
553
    {
554 154
        $compressedSize = (int) $compressedSize;
555
556 154
        if ($compressedSize < self::UNKNOWN) {
557 1
            throw new InvalidArgumentException('Compressed size < ' . self::UNKNOWN);
558
        }
559 153
        $this->compressedSize = $compressedSize;
560
561 153
        return $this;
562
    }
563
564
    /**
565
     * Returns the uncompressed size of this entry.
566
     *
567
     * @return int
568
     *
569
     * @deprecated Use {@see ZipEntry::getUncompressedSize()}
570
     */
571
    public function getSize()
572
    {
573
        @trigger_error(__METHOD__ . ' is deprecated. Use ' . __CLASS__ . '::getUncompressedSize()', \E_USER_DEPRECATED);
574
575
        return $this->getUncompressedSize();
576
    }
577
578
    /**
579
     * Sets the uncompressed size of this entry.
580
     *
581
     * @param int $size the (Uncompressed) Size
582
     *
583
     * @return ZipEntry
584
     *
585
     * @deprecated Use {@see ZipEntry::setUncompressedSize()}
586
     *
587
     * @internal
588
     */
589
    public function setSize($size)
590
    {
591
        @trigger_error(__METHOD__ . ' is deprecated. Use ' . __CLASS__ . '::setUncompressedSize()', \E_USER_DEPRECATED);
592
593
        return $this->setUncompressedSize($size);
594
    }
595
596
    /**
597
     * Returns the uncompressed size of this entry.
598
     *
599
     * @return int
600
     */
601 154
    public function getUncompressedSize()
602
    {
603 154
        return $this->uncompressedSize;
604
    }
605
606
    /**
607
     * Sets the uncompressed size of this entry.
608
     *
609
     * @param int $uncompressedSize the (Uncompressed) Size
610
     *
611
     * @return ZipEntry
612
     *
613
     * @internal
614
     */
615 183
    public function setUncompressedSize($uncompressedSize)
616
    {
617 183
        $uncompressedSize = (int) $uncompressedSize;
618
619 183
        if ($uncompressedSize < self::UNKNOWN) {
620 1
            throw new InvalidArgumentException('Uncompressed size < ' . self::UNKNOWN);
621
        }
622 182
        $this->uncompressedSize = $uncompressedSize;
623
624 182
        return $this;
625
    }
626
627
    /**
628
     * Return relative Offset Of Local File Header.
629
     *
630
     * @return int
631
     */
632 146
    public function getLocalHeaderOffset()
633
    {
634 146
        return $this->localHeaderOffset;
635
    }
636
637
    /**
638
     * @param int $localHeaderOffset
639
     *
640
     * @return ZipEntry
641
     *
642
     * @internal
643
     */
644 138
    public function setLocalHeaderOffset($localHeaderOffset)
645
    {
646 138
        $localHeaderOffset = (int) $localHeaderOffset;
647
648 138
        if ($localHeaderOffset < 0) {
649 1
            throw new InvalidArgumentException('Negative $localHeaderOffset');
650
        }
651 138
        $this->localHeaderOffset = $localHeaderOffset;
652
653 138
        return $this;
654
    }
655
656
    /**
657
     * Return relative Offset Of Local File Header.
658
     *
659
     * @return int
660
     *
661
     * @deprecated Use {@see ZipEntry::getLocalHeaderOffset()}
662
     */
663
    public function getOffset()
664
    {
665
        @trigger_error(
666
            __METHOD__ . ' is deprecated. Use ' . __CLASS__ . '::getLocalHeaderOffset()',
667
            \E_USER_DEPRECATED
668
        );
669
670
        return $this->getLocalHeaderOffset();
671
    }
672
673
    /**
674
     * @param int $offset
675
     *
676
     * @return ZipEntry
677
     *
678
     * @deprecated Use {@see ZipEntry::setLocalHeaderOffset()}
679
     *
680
     * @internal
681
     */
682
    public function setOffset($offset)
683
    {
684
        @trigger_error(
685
            __METHOD__ . ' is deprecated. Use ' . __CLASS__ . '::setLocalHeaderOffset()',
686
            \E_USER_DEPRECATED
687
        );
688
689
        return $this->setLocalHeaderOffset($offset);
690
    }
691
692
    /**
693
     * Returns the General Purpose Bit Flags.
694
     *
695
     * @return int
696
     */
697 150
    public function getGeneralPurposeBitFlags()
698
    {
699 150
        return $this->generalPurposeBitFlags;
700
    }
701
702
    /**
703
     * Sets the General Purpose Bit Flags.
704
     *
705
     * @param int $gpbf general purpose bit flags
706
     *
707
     * @return ZipEntry
708
     *
709
     * @internal
710
     */
711 8
    public function setGeneralPurposeBitFlags($gpbf)
712
    {
713 8
        $gpbf = (int) $gpbf;
714
715 8
        if ($gpbf < 0x0000 || $gpbf > 0xffff) {
716 2
            throw new InvalidArgumentException('general purpose bit flags out of range');
717
        }
718 6
        $this->generalPurposeBitFlags = $gpbf;
719 6
        $this->updateCompressionLevel();
720
721 6
        return $this;
722
    }
723
724 194
    private function updateCompressionLevel()
725
    {
726 194
        if ($this->compressionMethod === ZipCompressionMethod::DEFLATED) {
727 81
            $bit1 = $this->isSetGeneralBitFlag(GeneralPurposeBitFlag::COMPRESSION_FLAG1);
728 81
            $bit2 = $this->isSetGeneralBitFlag(GeneralPurposeBitFlag::COMPRESSION_FLAG2);
729
730 81
            if ($bit1 && !$bit2) {
731 4
                $this->compressionLevel = ZipCompressionLevel::MAXIMUM;
732 80
            } elseif (!$bit1 && $bit2) {
733 3
                $this->compressionLevel = ZipCompressionLevel::FAST;
734 80
            } elseif ($bit1 && $bit2) {
735 7
                $this->compressionLevel = ZipCompressionLevel::SUPER_FAST;
736
            } else {
737 80
                $this->compressionLevel = ZipCompressionLevel::NORMAL;
738
            }
739
        }
740 194
    }
741
742
    /**
743
     * @param int  $mask
744
     * @param bool $enable
745
     *
746
     * @return ZipEntry
747
     */
748 52
    private function setGeneralBitFlag($mask, $enable)
749
    {
750 52
        if ($enable) {
751 47
            $this->generalPurposeBitFlags |= $mask;
752
        } else {
753 8
            $this->generalPurposeBitFlags &= ~$mask;
754
        }
755
756 52
        return $this;
757
    }
758
759
    /**
760
     * @param int $mask
761
     *
762
     * @return bool
763
     */
764 185
    private function isSetGeneralBitFlag($mask)
765
    {
766 185
        return ($this->generalPurposeBitFlags & $mask) === $mask;
767
    }
768
769
    /**
770
     * @return bool
771
     */
772 136
    public function isDataDescriptorEnabled()
773
    {
774 136
        return $this->isSetGeneralBitFlag(GeneralPurposeBitFlag::DATA_DESCRIPTOR);
775
    }
776
777
    /**
778
     * Enabling or disabling the use of the Data Descriptor block.
779
     *
780
     * @param bool $enabled
781
     */
782 5
    public function enableDataDescriptor($enabled = true)
783
    {
784 5
        $this->setGeneralBitFlag(GeneralPurposeBitFlag::DATA_DESCRIPTOR, (bool) $enabled);
785 5
    }
786
787
    /**
788
     * @param bool $enabled
789
     */
790 27
    public function enableUtf8Name($enabled)
791
    {
792 27
        $this->setGeneralBitFlag(GeneralPurposeBitFlag::UTF8, (bool) $enabled);
793 27
    }
794
795
    /**
796
     * @return bool
797
     */
798 3
    public function isUtf8Flag()
799
    {
800 3
        return $this->isSetGeneralBitFlag(GeneralPurposeBitFlag::UTF8);
801
    }
802
803
    /**
804
     * Returns true if and only if this ZIP entry is encrypted.
805
     *
806
     * @return bool
807
     */
808 160
    public function isEncrypted()
809
    {
810 160
        return $this->isSetGeneralBitFlag(GeneralPurposeBitFlag::ENCRYPTION);
811
    }
812
813
    /**
814
     * @return bool
815
     */
816 81
    public function isStrongEncryption()
817
    {
818 81
        return $this->isSetGeneralBitFlag(GeneralPurposeBitFlag::STRONG_ENCRYPTION);
819
    }
820
821
    /**
822
     * Sets the encryption property to false and removes any other
823
     * encryption artifacts.
824
     *
825
     * @return ZipEntry
826
     */
827 5
    public function disableEncryption()
828
    {
829 5
        $this->setEncrypted(false);
830 5
        $this->removeExtraField(WinZipAesExtraField::HEADER_ID);
831 5
        $this->encryptionMethod = ZipEncryptionMethod::NONE;
832 5
        $this->password = null;
833 5
        $this->extractVersion = self::UNKNOWN;
834
835 5
        return $this;
836
    }
837
838
    /**
839
     * Sets the encryption flag for this ZIP entry.
840
     *
841
     * @param bool $encrypted
842
     *
843
     * @return ZipEntry
844
     */
845 32
    private function setEncrypted($encrypted)
846
    {
847 32
        $encrypted = (bool) $encrypted;
848 32
        $this->setGeneralBitFlag(GeneralPurposeBitFlag::ENCRYPTION, $encrypted);
849
850 32
        return $this;
851
    }
852
853
    /**
854
     * Returns the compression method for this entry.
855
     *
856
     * @return int
857
     *
858
     * @deprecated Use {@see ZipEntry::getCompressionMethod()}
859
     */
860
    public function getMethod()
861
    {
862
        @trigger_error(
863
            __METHOD__ . ' is deprecated. Use ' . __CLASS__ . '::getCompressionMethod()',
864
            \E_USER_DEPRECATED
865
        );
866
867
        return $this->getCompressionMethod();
868
    }
869
870
    /**
871
     * Returns the compression method for this entry.
872
     *
873
     * @return int
874
     */
875 142
    public function getCompressionMethod()
876
    {
877 142
        return $this->compressionMethod;
878
    }
879
880
    /**
881
     * Sets the compression method for this entry.
882
     *
883
     * @param int $method
884
     *
885
     * @throws ZipUnsupportMethodException
886
     *
887
     * @return ZipEntry
888
     *
889
     * @deprecated Use {@see ZipEntry::setCompressionMethod()}
890
     */
891
    public function setMethod($method)
892
    {
893
        @trigger_error(
894
            __METHOD__ . ' is deprecated. Use ' . __CLASS__ . '::setCompressionMethod()',
895
            \E_USER_DEPRECATED
896
        );
897
898
        return $this->setCompressionMethod($method);
899
    }
900
901
    /**
902
     * Sets the compression method for this entry.
903
     *
904
     * @param int $compressionMethod
905
     *
906
     * @throws ZipUnsupportMethodException
907
     *
908
     * @return ZipEntry
909
     *
910
     * @see ZipCompressionMethod::STORED
911
     * @see ZipCompressionMethod::DEFLATED
912
     * @see ZipCompressionMethod::BZIP2
913
     */
914 213
    public function setCompressionMethod($compressionMethod)
915
    {
916 213
        $compressionMethod = (int) $compressionMethod;
917
918 213
        if ($compressionMethod < 0x0000 || $compressionMethod > 0xffff) {
919 2
            throw new InvalidArgumentException('method out of range: ' . $compressionMethod);
920
        }
921
922 211
        ZipCompressionMethod::checkSupport($compressionMethod);
923
924 183
        $this->compressionMethod = $compressionMethod;
925 183
        $this->updateCompressionLevel();
926 183
        $this->extractVersion = self::UNKNOWN;
927
928 183
        return $this;
929
    }
930
931
    /**
932
     * Get Unix Timestamp.
933
     *
934
     * @return int
935
     */
936 15
    public function getTime()
937
    {
938 15
        if ($this->getDosTime() === self::UNKNOWN) {
939 1
            return self::UNKNOWN;
940
        }
941
942 14
        return DateTimeConverter::msDosToUnix($this->getDosTime());
943
    }
944
945
    /**
946
     * Get Dos Time.
947
     *
948
     * @return int
949
     */
950 147
    public function getDosTime()
951
    {
952 147
        return $this->dosTime;
953
    }
954
955
    /**
956
     * Set Dos Time.
957
     *
958
     * @param int $dosTime
959
     *
960
     * @return ZipEntry
961
     */
962 170
    public function setDosTime($dosTime)
963
    {
964 170
        $dosTime = (int) $dosTime;
965
966 170
        if (\PHP_INT_SIZE === 8) {
967 170
            if ($dosTime < 0x00000000 || $dosTime > 0xffffffff) {
968 2
                throw new InvalidArgumentException('DosTime out of range');
969
            }
970
        }
971
972 168
        $this->dosTime = $dosTime;
973
974 168
        return $this;
975
    }
976
977
    /**
978
     * Set time from unix timestamp.
979
     *
980
     * @param int $unixTimestamp
981
     *
982
     * @return ZipEntry
983
     */
984 164
    public function setTime($unixTimestamp)
985
    {
986 164
        if ($unixTimestamp !== self::UNKNOWN) {
987 164
            $this->setDosTime(DateTimeConverter::unixToMsDos($unixTimestamp));
988
        } else {
989 1
            $this->dosTime = 0;
990
        }
991
992 164
        return $this;
993
    }
994
995
    /**
996
     * Returns the external file attributes.
997
     *
998
     * @return int the external file attributes
999
     */
1000 145
    public function getExternalAttributes()
1001
    {
1002 145
        return $this->externalAttributes;
1003
    }
1004
1005
    /**
1006
     * Sets the external file attributes.
1007
     *
1008
     * @param int $externalAttributes the external file attributes
1009
     *
1010
     * @return ZipEntry
1011
     */
1012 196
    public function setExternalAttributes($externalAttributes)
1013
    {
1014 196
        $this->externalAttributes = (int) $externalAttributes;
1015
1016 196
        if (\PHP_INT_SIZE === 8) {
1017 196
            if ($externalAttributes < 0x00000000 || $externalAttributes > 0xffffffff) {
1018 2
                throw new InvalidArgumentException('external attributes out of range: ' . $externalAttributes);
1019
            }
1020
        }
1021
1022 194
        $this->externalAttributes = $externalAttributes;
1023
1024 194
        return $this;
1025
    }
1026
1027
    /**
1028
     * Returns the internal file attributes.
1029
     *
1030
     * @return int the internal file attributes
1031
     */
1032 136
    public function getInternalAttributes()
1033
    {
1034 136
        return $this->internalAttributes;
1035
    }
1036
1037
    /**
1038
     * Sets the internal file attributes.
1039
     *
1040
     * @param int $internalAttributes the internal file attributes
1041
     *
1042
     * @return ZipEntry
1043
     */
1044 5
    public function setInternalAttributes($internalAttributes)
1045
    {
1046 5
        $internalAttributes = (int) $internalAttributes;
1047
1048 5
        if ($internalAttributes < 0x0000 || $internalAttributes > 0xffff) {
1049 2
            throw new InvalidArgumentException('internal attributes out of range');
1050
        }
1051 3
        $this->internalAttributes = $internalAttributes;
1052
1053 3
        return $this;
1054
    }
1055
1056
    /**
1057
     * Returns true if and only if this ZIP entry represents a directory entry
1058
     * (i.e. end with '/').
1059
     *
1060
     * @return bool
1061
     */
1062 202
    final public function isDirectory()
1063
    {
1064 202
        return $this->isDirectory;
1065
    }
1066
1067
    /**
1068
     * @return ExtraFieldsCollection
1069
     */
1070 149
    public function getCdExtraFields()
1071
    {
1072 149
        return $this->cdExtraFields;
1073
    }
1074
1075
    /**
1076
     * @param int $headerId
1077
     *
1078
     * @return ZipExtraField|null
1079
     */
1080 145
    public function getCdExtraField($headerId)
1081
    {
1082 145
        return $this->cdExtraFields->get((int) $headerId);
1083
    }
1084
1085
    /**
1086
     * @param ExtraFieldsCollection $cdExtraFields
1087
     *
1088
     * @return ZipEntry
1089
     */
1090 1
    public function setCdExtraFields(ExtraFieldsCollection $cdExtraFields)
1091
    {
1092 1
        $this->cdExtraFields = $cdExtraFields;
1093
1094 1
        return $this;
1095
    }
1096
1097
    /**
1098
     * @return ExtraFieldsCollection
1099
     */
1100 148
    public function getLocalExtraFields()
1101
    {
1102 148
        return $this->localExtraFields;
1103
    }
1104
1105
    /**
1106
     * @param int $headerId
1107
     *
1108
     * @return ZipExtraField|null
1109
     */
1110 154
    public function getLocalExtraField($headerId)
1111
    {
1112 154
        return $this->localExtraFields[(int) $headerId];
1113
    }
1114
1115
    /**
1116
     * @param ExtraFieldsCollection $localExtraFields
1117
     *
1118
     * @return ZipEntry
1119
     */
1120 1
    public function setLocalExtraFields(ExtraFieldsCollection $localExtraFields)
1121
    {
1122 1
        $this->localExtraFields = $localExtraFields;
1123
1124 1
        return $this;
1125
    }
1126
1127
    /**
1128
     * @param int $headerId
1129
     *
1130
     * @return ZipExtraField|null
1131
     */
1132 142
    public function getExtraField($headerId)
1133
    {
1134 142
        $headerId = (int) $headerId;
1135 142
        $local = $this->getLocalExtraField($headerId);
1136
1137 142
        if ($local === null) {
1138 141
            return $this->getCdExtraField($headerId);
1139
        }
1140
1141 16
        return $local;
1142
    }
1143
1144
    /**
1145
     * @param int $headerId
1146
     *
1147
     * @return bool
1148
     */
1149 17
    public function hasExtraField($headerId)
1150
    {
1151 17
        $headerId = (int) $headerId;
1152
1153
        return
1154 17
            isset($this->localExtraFields[$headerId]) ||
1155 17
            isset($this->cdExtraFields[$headerId]);
1156
    }
1157
1158
    /**
1159
     * @param int $headerId
1160
     */
1161 18
    public function removeExtraField($headerId)
1162
    {
1163 18
        $headerId = (int) $headerId;
1164
1165 18
        $this->cdExtraFields->remove($headerId);
1166 18
        $this->localExtraFields->remove($headerId);
1167 18
    }
1168
1169
    /**
1170
     * @param ZipExtraField $zipExtraField
1171
     */
1172 6
    public function addExtraField(ZipExtraField $zipExtraField)
1173
    {
1174 6
        $this->addLocalExtraField($zipExtraField);
1175 6
        $this->addCdExtraField($zipExtraField);
1176 6
    }
1177
1178
    /**
1179
     * @param ZipExtraField $zipExtraField
1180
     */
1181 6
    public function addLocalExtraField(ZipExtraField $zipExtraField)
1182
    {
1183 6
        $this->localExtraFields->add($zipExtraField);
1184 6
    }
1185
1186
    /**
1187
     * @param ZipExtraField $zipExtraField
1188
     */
1189 6
    public function addCdExtraField(ZipExtraField $zipExtraField)
1190
    {
1191 6
        $this->cdExtraFields->add($zipExtraField);
1192 6
    }
1193
1194
    /**
1195
     * Returns comment entry.
1196
     *
1197
     * @return string
1198
     */
1199 137
    public function getComment()
1200
    {
1201 137
        return $this->comment !== null ? $this->comment : '';
1202
    }
1203
1204
    /**
1205
     * Set entry comment.
1206
     *
1207
     * @param string|null $comment
1208
     *
1209
     * @return ZipEntry
1210
     */
1211 138
    public function setComment($comment)
1212
    {
1213 138
        if ($comment !== null) {
1214 7
            $commentLength = \strlen($comment);
1215
1216 7
            if ($commentLength > 0xffff) {
1217 3
                throw new InvalidArgumentException('Comment too long');
1218
            }
1219
1220 4
            if ($this->charset === null && !StringUtil::isASCII($comment)) {
1221 4
                $this->enableUtf8Name(true);
1222
            }
1223
        }
1224 135
        $this->comment = $comment;
1225
1226 135
        return $this;
1227
    }
1228
1229
    /**
1230
     * @return bool
1231
     */
1232 142
    public function isDataDescriptorRequired()
1233
    {
1234 142
        return ($this->getCrc() | $this->getCompressedSize() | $this->getUncompressedSize()) === self::UNKNOWN;
1235
    }
1236
1237
    /**
1238
     * Return crc32 content or 0 for WinZip AES v2.
1239
     *
1240
     * @return int
1241
     */
1242 147
    public function getCrc()
1243
    {
1244 147
        return $this->crc;
1245
    }
1246
1247
    /**
1248
     * Set crc32 content.
1249
     *
1250
     * @param int $crc
1251
     *
1252
     * @return ZipEntry
1253
     *
1254
     * @internal
1255
     */
1256 141
    public function setCrc($crc)
1257
    {
1258 141
        $this->crc = (int) $crc;
1259
1260 141
        return $this;
1261
    }
1262
1263
    /**
1264
     * @return string|null
1265
     */
1266 47
    public function getPassword()
1267
    {
1268 47
        return $this->password;
1269
    }
1270
1271
    /**
1272
     * Set password and encryption method from entry.
1273
     *
1274
     * @param string|null $password
1275
     * @param int|null    $encryptionMethod
1276
     *
1277
     * @return ZipEntry
1278
     */
1279 27
    public function setPassword($password, $encryptionMethod = null)
1280
    {
1281 27
        if (!$this->isDirectory) {
1282 26
            if ($password === null || $password === '') {
1283 4
                $this->password = null;
1284 4
                $this->disableEncryption();
1285
            } else {
1286 24
                $this->password = (string) $password;
1287
1288 24
                if ($encryptionMethod === null && $this->encryptionMethod === ZipEncryptionMethod::NONE) {
1289 14
                    $encryptionMethod = ZipEncryptionMethod::WINZIP_AES_256;
1290
                }
1291
1292 24
                if ($encryptionMethod !== null) {
1293 22
                    $this->setEncryptionMethod($encryptionMethod);
1294
                }
1295 23
                $this->setEncrypted(true);
1296
            }
1297
        }
1298
1299 26
        return $this;
1300
    }
1301
1302
    /**
1303
     * @return int
1304
     */
1305 54
    public function getEncryptionMethod()
1306
    {
1307 54
        return $this->encryptionMethod;
1308
    }
1309
1310
    /**
1311
     * Set encryption method.
1312
     *
1313
     * @param int|null $encryptionMethod
1314
     *
1315
     * @return ZipEntry
1316
     *
1317
     * @see ZipEncryptionMethod::NONE
1318
     * @see ZipEncryptionMethod::PKWARE
1319
     * @see ZipEncryptionMethod::WINZIP_AES_256
1320
     * @see ZipEncryptionMethod::WINZIP_AES_192
1321
     * @see ZipEncryptionMethod::WINZIP_AES_128
1322
     */
1323 33
    public function setEncryptionMethod($encryptionMethod)
1324
    {
1325 33
        if ($encryptionMethod === null) {
1326 1
            $encryptionMethod = ZipEncryptionMethod::NONE;
1327
        }
1328
1329 33
        $encryptionMethod = (int) $encryptionMethod;
1330 33
        ZipEncryptionMethod::checkSupport($encryptionMethod);
1331 29
        $this->encryptionMethod = $encryptionMethod;
1332
1333 29
        $this->setEncrypted($this->encryptionMethod !== ZipEncryptionMethod::NONE);
1334 29
        $this->extractVersion = self::UNKNOWN;
1335
1336 29
        return $this;
1337
    }
1338
1339
    /**
1340
     * @return int
1341
     */
1342 78
    public function getCompressionLevel()
1343
    {
1344 78
        return $this->compressionLevel;
1345
    }
1346
1347
    /**
1348
     * @param int $compressionLevel
1349
     *
1350
     * @return ZipEntry
1351
     */
1352 38
    public function setCompressionLevel($compressionLevel)
1353
    {
1354 38
        $compressionLevel = (int) $compressionLevel;
1355
1356 38
        if ($compressionLevel === self::UNKNOWN) {
1357 1
            $compressionLevel = ZipCompressionLevel::NORMAL;
1358
        }
1359
1360
        if (
1361 38
            $compressionLevel < ZipCompressionLevel::LEVEL_MIN ||
1362 38
            $compressionLevel > ZipCompressionLevel::LEVEL_MAX
1363
        ) {
1364 16
            throw new InvalidArgumentException(
1365
                'Invalid compression level. Minimum level ' .
1366 16
                ZipCompressionLevel::LEVEL_MIN . '. Maximum level ' . ZipCompressionLevel::LEVEL_MAX
1367
            );
1368
        }
1369 22
        $this->compressionLevel = $compressionLevel;
1370
1371 22
        $this->updateGbpfCompLevel();
1372
1373 22
        return $this;
1374
    }
1375
1376
    /**
1377
     * Update general purpose bit flogs.
1378
     */
1379 22
    private function updateGbpfCompLevel()
1380
    {
1381 22
        if ($this->compressionMethod === ZipCompressionMethod::DEFLATED) {
1382 22
            $bit1 = false;
1383 22
            $bit2 = false;
1384
1385 22
            switch ($this->compressionLevel) {
1386
                case ZipCompressionLevel::MAXIMUM:
1387 6
                    $bit1 = true;
1388 6
                    break;
1389
1390
                case ZipCompressionLevel::FAST:
1391 3
                    $bit2 = true;
1392 3
                    break;
1393
1394
                case ZipCompressionLevel::SUPER_FAST:
1395 5
                    $bit1 = true;
1396 5
                    $bit2 = true;
1397 5
                    break;
1398
                // default is ZipCompressionLevel::NORMAL
1399
            }
1400
1401 22
            $this->generalPurposeBitFlags |= ($bit1 ? GeneralPurposeBitFlag::COMPRESSION_FLAG1 : 0);
1402 22
            $this->generalPurposeBitFlags |= ($bit2 ? GeneralPurposeBitFlag::COMPRESSION_FLAG2 : 0);
1403
        }
1404 22
    }
1405
1406
    /**
1407
     * Sets Unix permissions in a way that is understood by Info-Zip's
1408
     * unzip command.
1409
     *
1410
     * @param int $mode mode an int value
1411
     *
1412
     * @return ZipEntry
1413
     */
1414 191
    public function setUnixMode($mode)
1415
    {
1416 191
        $mode = (int) $mode;
1417 191
        $this->setExternalAttributes(
1418
            ($mode << 16)
1419
            // MS-DOS read-only attribute
1420 191
            | (($mode & UnixStat::UNX_IWUSR) === 0 ? DosAttrs::DOS_HIDDEN : 0)
1421
            // MS-DOS directory flag
1422 191
            | ($this->isDirectory() ? DosAttrs::DOS_DIRECTORY : DosAttrs::DOS_ARCHIVE)
1423
        );
1424 191
        $this->createdOS = ZipPlatform::OS_UNIX;
1425
1426 191
        return $this;
1427
    }
1428
1429
    /**
1430
     * Unix permission.
1431
     *
1432
     * @return int the unix permissions
1433
     */
1434 51
    public function getUnixMode()
1435
    {
1436 51
        $mode = 0;
1437
1438 51
        if ($this->createdOS === ZipPlatform::OS_UNIX) {
1439 45
            $mode = ($this->externalAttributes >> 16) & 0xFFFF;
1440 7
        } elseif ($this->hasExtraField(AsiExtraField::HEADER_ID)) {
1441
            /** @var AsiExtraField $asiExtraField */
1442 1
            $asiExtraField = $this->getExtraField(AsiExtraField::HEADER_ID);
1443 1
            $mode = $asiExtraField->getMode();
1444
        }
1445
1446 51
        if ($mode > 0) {
1447 43
            return $mode;
1448
        }
1449
1450 8
        return $this->isDirectory ? 040755 : 0100644;
1451
    }
1452
1453
    /**
1454
     * Offset MUST be considered in decision about ZIP64 format - see
1455
     * description of Data Descriptor in ZIP File Format Specification.
1456
     *
1457
     * @return bool
1458
     */
1459 146
    public function isZip64ExtensionsRequired()
1460
    {
1461 146
        return $this->compressedSize > ZipConstants::ZIP64_MAGIC
1462 146
            || $this->uncompressedSize > ZipConstants::ZIP64_MAGIC;
1463
    }
1464
1465
    /**
1466
     * Returns true if this entry represents a unix symlink,
1467
     * in which case the entry's content contains the target path
1468
     * for the symlink.
1469
     *
1470
     * @return bool true if the entry represents a unix symlink,
1471
     *              false otherwise
1472
     */
1473 25
    public function isUnixSymlink()
1474
    {
1475 25
        return ($this->getUnixMode() & UnixStat::UNX_IFMT) === UnixStat::UNX_IFLNK;
1476
    }
1477
1478
    /**
1479
     * @return \DateTimeInterface
1480
     */
1481 16
    public function getMTime()
1482
    {
1483
        /** @var NtfsExtraField|null $ntfsExtra */
1484 16
        $ntfsExtra = $this->getExtraField(NtfsExtraField::HEADER_ID);
1485
1486 16
        if ($ntfsExtra !== null) {
1487 3
            return $ntfsExtra->getModifyDateTime();
1488
        }
1489
1490
        /** @var ExtendedTimestampExtraField|null $extendedExtra */
1491 15
        $extendedExtra = $this->getExtraField(ExtendedTimestampExtraField::HEADER_ID);
1492
1493 15
        if ($extendedExtra !== null && ($mtime = $extendedExtra->getModifyDateTime()) !== null) {
1494 3
            return $mtime;
1495
        }
1496
1497
        /** @var OldUnixExtraField|null $oldUnixExtra */
1498 14
        $oldUnixExtra = $this->getExtraField(OldUnixExtraField::HEADER_ID);
1499
1500 14
        if ($oldUnixExtra !== null && ($mtime = $oldUnixExtra->getModifyDateTime()) !== null) {
1501 1
            return $mtime;
1502
        }
1503
1504 14
        $timestamp = $this->getTime();
1505
1506
        try {
1507 14
            return new \DateTimeImmutable('@' . $timestamp);
1508
        } catch (\Exception $e) {
1509
            throw new RuntimeException('Error create DateTime object with timestamp ' . $timestamp, 1, $e);
1510
        }
1511
    }
1512
1513
    /**
1514
     * @return \DateTimeInterface|null
1515
     */
1516 17
    public function getATime()
1517
    {
1518
        /** @var NtfsExtraField|null $ntfsExtra */
1519 17
        $ntfsExtra = $this->getExtraField(NtfsExtraField::HEADER_ID);
1520
1521 17
        if ($ntfsExtra !== null) {
1522 3
            return $ntfsExtra->getAccessDateTime();
1523
        }
1524
1525
        /** @var ExtendedTimestampExtraField|null $extendedExtra */
1526 16
        $extendedExtra = $this->getExtraField(ExtendedTimestampExtraField::HEADER_ID);
1527
1528 16
        if ($extendedExtra !== null && ($atime = $extendedExtra->getAccessDateTime()) !== null) {
1529 3
            return $atime;
1530
        }
1531
1532
        /** @var OldUnixExtraField|null $oldUnixExtra */
1533 15
        $oldUnixExtra = $this->getExtraField(OldUnixExtraField::HEADER_ID);
1534
1535 15
        if ($oldUnixExtra !== null) {
1536 1
            return $oldUnixExtra->getAccessDateTime();
1537
        }
1538
1539 15
        return null;
1540
    }
1541
1542
    /**
1543
     * @return \DateTimeInterface|null
1544
     */
1545 7
    public function getCTime()
1546
    {
1547
        /** @var NtfsExtraField|null $ntfsExtra */
1548 7
        $ntfsExtra = $this->getExtraField(NtfsExtraField::HEADER_ID);
1549
1550 7
        if ($ntfsExtra !== null) {
1551 2
            return $ntfsExtra->getCreateDateTime();
1552
        }
1553
1554
        /** @var ExtendedTimestampExtraField|null $extendedExtra */
1555 7
        $extendedExtra = $this->getExtraField(ExtendedTimestampExtraField::HEADER_ID);
1556
1557 7
        if ($extendedExtra !== null) {
1558 2
            return $extendedExtra->getCreateDateTime();
1559
        }
1560
1561 7
        return null;
1562
    }
1563
1564 147
    public function __clone()
1565
    {
1566 147
        $this->cdExtraFields = clone $this->cdExtraFields;
1567 147
        $this->localExtraFields = clone $this->localExtraFields;
1568
1569 147
        if ($this->data !== null) {
1570 144
            $this->data = clone $this->data;
1571
        }
1572 147
    }
1573
}
1574