Passed
Branch master (25d5dd)
by Alexey
02:30
created

ZipEntry::create()   A

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 276
    public function __construct($name, $charset = null)
159
    {
160 276
        $this->setName($name, $charset);
161
162 273
        $this->cdExtraFields = new ExtraFieldsCollection();
163 273
        $this->localExtraFields = new ExtraFieldsCollection();
164 273
    }
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 83
    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 83
        $entry = new self($name);
211 83
        $entry->createdOS = (int) $createdOS;
212 83
        $entry->extractedOS = (int) $extractedOS;
213 83
        $entry->softwareVersion = (int) $softwareVersion;
214 83
        $entry->extractVersion = (int) $extractVersion;
215 83
        $entry->compressionMethod = (int) $compressionMethod;
216 83
        $entry->generalPurposeBitFlags = (int) $gpbf;
217 83
        $entry->dosTime = (int) $dosTime;
218 83
        $entry->crc = (int) $crc;
219 83
        $entry->compressedSize = (int) $compressedSize;
220 83
        $entry->uncompressedSize = (int) $uncompressedSize;
221 83
        $entry->internalAttributes = (int) $internalAttributes;
222 83
        $entry->externalAttributes = (int) $externalAttributes;
223 83
        $entry->localHeaderOffset = (int) $offsetLocalHeader;
224 83
        $entry->setComment($comment);
225 83
        $entry->setCharset($charset);
226 83
        $entry->updateCompressionLevel();
227
228 83
        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 276
    private function setName($name, $charset = null)
240
    {
241 276
        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 275
        $name = ltrim((string) $name, '\\/');
246
247 275
        if ($name === '') {
248 2
            throw new InvalidArgumentException('Empty zip entry name');
249
        }
250
251 273
        $name = (string) $name;
252 273
        $length = \strlen($name);
253
254 273
        if ($length > 0xffff) {
255
            throw new InvalidArgumentException('Illegal zip entry name parameter');
256
        }
257
258 273
        $this->setCharset($charset);
259
260 273
        if ($this->charset === null && !StringUtil::isASCII($name)) {
261 22
            $this->enableUtf8Name(true);
262
        }
263 273
        $this->name = $name;
264 273
        $this->isDirectory = ($length = \strlen($name)) >= 1 && $name[$length - 1] === '/';
265 273
        $this->externalAttributes = $this->isDirectory ? DosAttrs::DOS_DIRECTORY : DosAttrs::DOS_ARCHIVE;
266
267 273
        if ($this->extractVersion !== self::UNKNOWN) {
268 3
            $this->extractVersion = max(
269 3
                $this->extractVersion,
270 3
                $this->isDirectory ?
271 1
                    ZipVersion::v20_DEFLATED_FOLDER_ZIPCRYPTO :
272 3
                    ZipVersion::v10_DEFAULT_MIN
273
            );
274
        }
275
276 273
        return $this;
277
    }
278
279
    /**
280
     * @param string|null $charset
281
     *
282
     * @return ZipEntry
283
     *
284
     * @see DosCodePage::getCodePages()
285
     */
286 273
    public function setCharset($charset = null)
287
    {
288 273
        if ($charset !== null && $charset === '') {
289 1
            throw new InvalidArgumentException('Empty charset');
290
        }
291 273
        $this->charset = $charset;
292
293 273
        return $this;
294
    }
295
296
    /**
297
     * @return string|null
298
     */
299 82
    public function getCharset()
300
    {
301 82
        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 6
    public function rename($newName)
312
    {
313 6
        $newEntry = clone $this;
314 6
        $newEntry->setName($newName);
315
316 6
        $newEntry->removeExtraField(UnicodePathExtraField::HEADER_ID);
317
318 6
        return $newEntry;
319
    }
320
321
    /**
322
     * Returns the ZIP entry name.
323
     *
324
     * @return string
325
     */
326 110
    public function getName()
327
    {
328 110
        return $this->name;
329
    }
330
331
    /**
332
     * @return ZipData|null
333
     *
334
     * @internal
335
     */
336 90
    public function getData()
337
    {
338 90
        return $this->data;
339
    }
340
341
    /**
342
     * @param ZipData|null $data
343
     *
344
     * @internal
345
     */
346 104
    public function setData($data)
347
    {
348 104
        $this->data = $data;
349 104
    }
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 98
    public function getCreatedOS()
381
    {
382 98
        return $this->createdOS;
383
    }
384
385
    /**
386
     * Set platform.
387
     *
388
     * @param int $platform
389
     *
390
     * @return ZipEntry
391
     */
392 107
    public function setCreatedOS($platform)
393
    {
394 107
        $platform = (int) $platform;
395
396 107
        if ($platform < 0x00 || $platform > 0xff) {
397 2
            throw new InvalidArgumentException('Platform out of range');
398
        }
399 105
        $this->createdOS = $platform;
400
401 105
        return $this;
402
    }
403
404
    /**
405
     * @return int
406
     */
407 83
    public function getExtractedOS()
408
    {
409 83
        return $this->extractedOS;
410
    }
411
412
    /**
413
     * Set extracted OS.
414
     *
415
     * @param int $platform
416
     *
417
     * @return ZipEntry
418
     */
419 107
    public function setExtractedOS($platform)
420
    {
421 107
        $platform = (int) $platform;
422
423 107
        if ($platform < 0x00 || $platform > 0xff) {
424 2
            throw new InvalidArgumentException('Platform out of range');
425
        }
426 105
        $this->extractedOS = $platform;
427
428 105
        return $this;
429
    }
430
431
    /**
432
     * @return int
433
     */
434 81
    public function getSoftwareVersion()
435
    {
436 81
        if ($this->softwareVersion === self::UNKNOWN) {
437 78
            return $this->getExtractVersion();
438
        }
439
440 22
        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 89
    public function getExtractVersion()
475
    {
476 89
        if ($this->extractVersion === self::UNKNOWN) {
477 87
            if (ZipEncryptionMethod::isWinZipAesMethod($this->encryptionMethod)) {
478 14
                return ZipVersion::v51_ENCR_AES_RC2_CORRECT;
479
            }
480
481 82
            if ($this->compressionMethod === ZipCompressionMethod::BZIP2) {
482 4
                return ZipVersion::v46_BZIP2;
483
            }
484
485 82
            if ($this->isZip64ExtensionsRequired()) {
486
                return ZipVersion::v45_ZIP64_EXT;
487
            }
488
489
            if (
490 82
                $this->compressionMethod === ZipCompressionMethod::DEFLATED ||
491 72
                $this->isDirectory ||
492 82
                $this->encryptionMethod === ZipEncryptionMethod::PKWARE
493
            ) {
494 46
                return ZipVersion::v20_DEFLATED_FOLDER_ZIPCRYPTO;
495
            }
496
497 65
            return ZipVersion::v10_DEFAULT_MIN;
498
        }
499
500 18
        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 96
    public function getCompressedSize()
539
    {
540 96
        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 95
    public function setCompressedSize($compressedSize)
553
    {
554 95
        $compressedSize = (int) $compressedSize;
555
556 95
        if ($compressedSize < self::UNKNOWN) {
557 1
            throw new InvalidArgumentException('Compressed size < ' . self::UNKNOWN);
558
        }
559 94
        $this->compressedSize = $compressedSize;
560
561 94
        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 96
    public function getUncompressedSize()
602
    {
603 96
        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 111
    public function setUncompressedSize($uncompressedSize)
616
    {
617 111
        $uncompressedSize = (int) $uncompressedSize;
618
619 111
        if ($uncompressedSize < self::UNKNOWN) {
620 1
            throw new InvalidArgumentException('Uncompressed size < ' . self::UNKNOWN);
621
        }
622 110
        $this->uncompressedSize = $uncompressedSize;
623
624 110
        return $this;
625
    }
626
627
    /**
628
     * Return relative Offset Of Local File Header.
629
     *
630
     * @return int
631
     */
632 88
    public function getLocalHeaderOffset()
633
    {
634 88
        return $this->localHeaderOffset;
635
    }
636
637
    /**
638
     * @param int $localHeaderOffset
639
     *
640
     * @return ZipEntry
641
     *
642
     * @internal
643
     */
644 80
    public function setLocalHeaderOffset($localHeaderOffset)
645
    {
646 80
        $localHeaderOffset = (int) $localHeaderOffset;
647
648 80
        if ($localHeaderOffset < 0) {
649 1
            throw new InvalidArgumentException('Negative $localHeaderOffset');
650
        }
651 80
        $this->localHeaderOffset = $localHeaderOffset;
652
653 80
        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 95
    public function getGeneralPurposeBitFlags()
698
    {
699 95
        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 126
    private function updateCompressionLevel()
725
    {
726 126
        if ($this->compressionMethod === ZipCompressionMethod::DEFLATED) {
727 58
            $bit1 = $this->isSetGeneralBitFlag(GeneralPurposeBitFlag::COMPRESSION_FLAG1);
728 58
            $bit2 = $this->isSetGeneralBitFlag(GeneralPurposeBitFlag::COMPRESSION_FLAG2);
729
730 58
            if ($bit1 && !$bit2) {
731 3
                $this->compressionLevel = ZipCompressionLevel::MAXIMUM;
732 57
            } elseif (!$bit1 && $bit2) {
733 2
                $this->compressionLevel = ZipCompressionLevel::FAST;
734 57
            } elseif ($bit1 && $bit2) {
735 3
                $this->compressionLevel = ZipCompressionLevel::SUPER_FAST;
736
            } else {
737 57
                $this->compressionLevel = ZipCompressionLevel::NORMAL;
738
            }
739
        }
740 126
    }
741
742
    /**
743
     * @param int  $mask
744
     * @param bool $enable
745
     *
746
     * @return ZipEntry
747
     */
748 49
    private function setGeneralBitFlag($mask, $enable)
749
    {
750 49
        if ($enable) {
751 44
            $this->generalPurposeBitFlags |= $mask;
752
        } else {
753 8
            $this->generalPurposeBitFlags &= ~$mask;
754
        }
755
756 49
        return $this;
757
    }
758
759
    /**
760
     * @param int $mask
761
     *
762
     * @return bool
763
     */
764 128
    private function isSetGeneralBitFlag($mask)
765
    {
766 128
        return ($this->generalPurposeBitFlags & $mask) === $mask;
767
    }
768
769
    /**
770
     * @return bool
771
     */
772 81
    public function isDataDescriptorEnabled()
773
    {
774 81
        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 25
    public function enableUtf8Name($enabled)
791
    {
792 25
        $this->setGeneralBitFlag(GeneralPurposeBitFlag::UTF8, (bool) $enabled);
793 25
    }
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 104
    public function isEncrypted()
809
    {
810 104
        return $this->isSetGeneralBitFlag(GeneralPurposeBitFlag::ENCRYPTION);
811
    }
812
813
    /**
814
     * @return bool
815
     */
816 54
    public function isStrongEncryption()
817
    {
818 54
        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 31
    private function setEncrypted($encrypted)
846
    {
847 31
        $encrypted = (bool) $encrypted;
848 31
        $this->setGeneralBitFlag(GeneralPurposeBitFlag::ENCRYPTION, $encrypted);
849
850 31
        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 86
    public function getCompressionMethod()
876
    {
877 86
        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 142
    public function setCompressionMethod($compressionMethod)
915
    {
916 142
        $compressionMethod = (int) $compressionMethod;
917
918 142
        if ($compressionMethod < 0x0000 || $compressionMethod > 0xffff) {
919 2
            throw new InvalidArgumentException('method out of range: ' . $compressionMethod);
920
        }
921
922 140
        ZipCompressionMethod::checkSupport($compressionMethod);
923
924 115
        $this->compressionMethod = $compressionMethod;
925 115
        $this->updateCompressionLevel();
926 115
        $this->extractVersion = self::UNKNOWN;
927
928 115
        return $this;
929
    }
930
931
    /**
932
     * Get Unix Timestamp.
933
     *
934
     * @return int
935
     */
936 10
    public function getTime()
937
    {
938 10
        if ($this->getDosTime() === self::UNKNOWN) {
939 1
            return self::UNKNOWN;
940
        }
941
942 9
        return DateTimeConverter::msDosToUnix($this->getDosTime());
943
    }
944
945
    /**
946
     * Get Dos Time.
947
     *
948
     * @return int
949
     */
950 91
    public function getDosTime()
951
    {
952 91
        return $this->dosTime;
953
    }
954
955
    /**
956
     * Set Dos Time.
957
     *
958
     * @param int $dosTime
959
     *
960
     * @return ZipEntry
961
     */
962 103
    public function setDosTime($dosTime)
963
    {
964 103
        $dosTime = (int) $dosTime;
965
966 103
        if ($dosTime < 0x00000000 || $dosTime > 0xffffffff) {
967 2
            throw new InvalidArgumentException('DosTime out of range');
968
        }
969 101
        $this->dosTime = $dosTime;
970
971 101
        return $this;
972
    }
973
974
    /**
975
     * Set time from unix timestamp.
976
     *
977
     * @param int $unixTimestamp
978
     *
979
     * @return ZipEntry
980
     */
981 97
    public function setTime($unixTimestamp)
982
    {
983 97
        if ($unixTimestamp !== self::UNKNOWN) {
984 97
            $this->setDosTime(DateTimeConverter::unixToMsDos($unixTimestamp));
985
        } else {
986 1
            $this->dosTime = 0;
987
        }
988
989 97
        return $this;
990
    }
991
992
    /**
993
     * Returns the external file attributes.
994
     *
995
     * @return int the external file attributes
996
     */
997 89
    public function getExternalAttributes()
998
    {
999 89
        return $this->externalAttributes;
1000
    }
1001
1002
    /**
1003
     * Sets the external file attributes.
1004
     *
1005
     * @param int $externalAttributes the external file attributes
1006
     *
1007
     * @return ZipEntry
1008
     */
1009 128
    public function setExternalAttributes($externalAttributes)
1010
    {
1011 128
        $this->externalAttributes = (int) $externalAttributes;
1012
1013 128
        if ($externalAttributes < 0x00000000 || $externalAttributes > 0xffffffff) {
1014 2
            throw new InvalidArgumentException('external attributes out of range: ' . $externalAttributes);
1015
        }
1016 126
        $this->externalAttributes = $externalAttributes;
1017
1018 126
        return $this;
1019
    }
1020
1021
    /**
1022
     * Returns the internal file attributes.
1023
     *
1024
     * @return int the internal file attributes
1025
     */
1026 81
    public function getInternalAttributes()
1027
    {
1028 81
        return $this->internalAttributes;
1029
    }
1030
1031
    /**
1032
     * Sets the internal file attributes.
1033
     *
1034
     * @param int $internalAttributes the internal file attributes
1035
     *
1036
     * @return ZipEntry
1037
     */
1038 4
    public function setInternalAttributes($internalAttributes)
1039
    {
1040 4
        $internalAttributes = (int) $internalAttributes;
1041
1042 4
        if ($internalAttributes < 0x0000 || $internalAttributes > 0xffff) {
1043 2
            throw new InvalidArgumentException('internal attributes out of range');
1044
        }
1045 2
        $this->internalAttributes = $internalAttributes;
1046
1047 2
        return $this;
1048
    }
1049
1050
    /**
1051
     * Returns true if and only if this ZIP entry represents a directory entry
1052
     * (i.e. end with '/').
1053
     *
1054
     * @return bool
1055
     */
1056 132
    final public function isDirectory()
1057
    {
1058 132
        return $this->isDirectory;
1059
    }
1060
1061
    /**
1062
     * @return ExtraFieldsCollection
1063
     */
1064 94
    public function getCdExtraFields()
1065
    {
1066 94
        return $this->cdExtraFields;
1067
    }
1068
1069
    /**
1070
     * @param int $headerId
1071
     *
1072
     * @return ZipExtraField|null
1073
     */
1074 94
    public function getCdExtraField($headerId)
1075
    {
1076 94
        return $this->cdExtraFields->get((int) $headerId);
1077
    }
1078
1079
    /**
1080
     * @param ExtraFieldsCollection $cdExtraFields
1081
     *
1082
     * @return ZipEntry
1083
     */
1084 1
    public function setCdExtraFields(ExtraFieldsCollection $cdExtraFields)
1085
    {
1086 1
        $this->cdExtraFields = $cdExtraFields;
1087
1088 1
        return $this;
1089
    }
1090
1091
    /**
1092
     * @return ExtraFieldsCollection
1093
     */
1094 93
    public function getLocalExtraFields()
1095
    {
1096 93
        return $this->localExtraFields;
1097
    }
1098
1099
    /**
1100
     * @param int $headerId
1101
     *
1102
     * @return ZipExtraField|null
1103
     */
1104 98
    public function getLocalExtraField($headerId)
1105
    {
1106 98
        return $this->localExtraFields[(int) $headerId];
1107
    }
1108
1109
    /**
1110
     * @param ExtraFieldsCollection $localExtraFields
1111
     *
1112
     * @return ZipEntry
1113
     */
1114 1
    public function setLocalExtraFields(ExtraFieldsCollection $localExtraFields)
1115
    {
1116 1
        $this->localExtraFields = $localExtraFields;
1117
1118 1
        return $this;
1119
    }
1120
1121
    /**
1122
     * @param int $headerId
1123
     *
1124
     * @return ZipExtraField|null
1125
     */
1126 91
    public function getExtraField($headerId)
1127
    {
1128 91
        $headerId = (int) $headerId;
1129 91
        $local = $this->getLocalExtraField($headerId);
1130
1131 91
        if ($local === null) {
1132 90
            return $this->getCdExtraField($headerId);
1133
        }
1134
1135 16
        return $local;
1136
    }
1137
1138
    /**
1139
     * @param int $headerId
1140
     *
1141
     * @return bool
1142
     */
1143 17
    public function hasExtraField($headerId)
1144
    {
1145 17
        $headerId = (int) $headerId;
1146
1147
        return
1148 17
            isset($this->localExtraFields[$headerId]) ||
1149 17
            isset($this->cdExtraFields[$headerId]);
1150
    }
1151
1152
    /**
1153
     * @param int $headerId
1154
     */
1155 16
    public function removeExtraField($headerId)
1156
    {
1157 16
        $headerId = (int) $headerId;
1158
1159 16
        $this->cdExtraFields->remove($headerId);
1160 16
        $this->localExtraFields->remove($headerId);
1161 16
    }
1162
1163
    /**
1164
     * @param ZipExtraField $zipExtraField
1165
     */
1166 4
    public function addExtraField(ZipExtraField $zipExtraField)
1167
    {
1168 4
        $this->addLocalExtraField($zipExtraField);
1169 4
        $this->addCdExtraField($zipExtraField);
1170 4
    }
1171
1172
    /**
1173
     * @param ZipExtraField $zipExtraField
1174
     */
1175 4
    public function addLocalExtraField(ZipExtraField $zipExtraField)
1176
    {
1177 4
        $this->localExtraFields->add($zipExtraField);
1178 4
    }
1179
1180
    /**
1181
     * @param ZipExtraField $zipExtraField
1182
     */
1183 4
    public function addCdExtraField(ZipExtraField $zipExtraField)
1184
    {
1185 4
        $this->cdExtraFields->add($zipExtraField);
1186 4
    }
1187
1188
    /**
1189
     * Returns comment entry.
1190
     *
1191
     * @return string
1192
     */
1193 81
    public function getComment()
1194
    {
1195 81
        return $this->comment !== null ? $this->comment : '';
1196
    }
1197
1198
    /**
1199
     * Set entry comment.
1200
     *
1201
     * @param string|null $comment
1202
     *
1203
     * @return ZipEntry
1204
     */
1205 86
    public function setComment($comment)
1206
    {
1207 86
        if ($comment !== null) {
1208 5
            $commentLength = \strlen($comment);
1209
1210 5
            if ($commentLength > 0xffff) {
1211 2
                throw new InvalidArgumentException('Comment too long');
1212
            }
1213
1214 3
            if ($this->charset === null && !StringUtil::isASCII($comment)) {
1215 3
                $this->enableUtf8Name(true);
1216
            }
1217
        }
1218 84
        $this->comment = $comment;
1219
1220 84
        return $this;
1221
    }
1222
1223
    /**
1224
     * @return bool
1225
     */
1226 87
    public function isDataDescriptorRequired()
1227
    {
1228 87
        return ($this->getCrc() | $this->getCompressedSize() | $this->getUncompressedSize()) === self::UNKNOWN;
1229
    }
1230
1231
    /**
1232
     * Return crc32 content or 0 for WinZip AES v2.
1233
     *
1234
     * @return int
1235
     */
1236 91
    public function getCrc()
1237
    {
1238 91
        return $this->crc;
1239
    }
1240
1241
    /**
1242
     * Set crc32 content.
1243
     *
1244
     * @param int $crc
1245
     *
1246
     * @return ZipEntry
1247
     *
1248
     * @internal
1249
     */
1250 85
    public function setCrc($crc)
1251
    {
1252 85
        $this->crc = (int) $crc;
1253
1254 85
        return $this;
1255
    }
1256
1257
    /**
1258
     * @return string|null
1259
     */
1260 36
    public function getPassword()
1261
    {
1262 36
        return $this->password;
1263
    }
1264
1265
    /**
1266
     * Set password and encryption method from entry.
1267
     *
1268
     * @param string|null $password
1269
     * @param int|null    $encryptionMethod
1270
     *
1271
     * @return ZipEntry
1272
     */
1273 26
    public function setPassword($password, $encryptionMethod = null)
1274
    {
1275 26
        if (!$this->isDirectory) {
1276 25
            if ($password === null || $password === '') {
1277 4
                $this->password = null;
1278 4
                $this->disableEncryption();
1279
            } else {
1280 23
                $this->password = (string) $password;
1281
1282 23
                if ($encryptionMethod === null && $this->encryptionMethod === ZipEncryptionMethod::NONE) {
1283 13
                    $encryptionMethod = ZipEncryptionMethod::WINZIP_AES_256;
1284
                }
1285
1286 23
                if ($encryptionMethod !== null) {
1287 21
                    $this->setEncryptionMethod($encryptionMethod);
1288
                }
1289 22
                $this->setEncrypted(true);
1290
            }
1291
        }
1292
1293 25
        return $this;
1294
    }
1295
1296
    /**
1297
     * @return int
1298
     */
1299 42
    public function getEncryptionMethod()
1300
    {
1301 42
        return $this->encryptionMethod;
1302
    }
1303
1304
    /**
1305
     * Set encryption method.
1306
     *
1307
     * @param int|null $encryptionMethod
1308
     *
1309
     * @return ZipEntry
1310
     *
1311
     * @see ZipEncryptionMethod::NONE
1312
     * @see ZipEncryptionMethod::PKWARE
1313
     * @see ZipEncryptionMethod::WINZIP_AES_256
1314
     * @see ZipEncryptionMethod::WINZIP_AES_192
1315
     * @see ZipEncryptionMethod::WINZIP_AES_128
1316
     */
1317 32
    public function setEncryptionMethod($encryptionMethod)
1318
    {
1319 32
        if ($encryptionMethod === null) {
1320 1
            $encryptionMethod = ZipEncryptionMethod::NONE;
1321
        }
1322
1323 32
        $encryptionMethod = (int) $encryptionMethod;
1324 32
        ZipEncryptionMethod::checkSupport($encryptionMethod);
1325 28
        $this->encryptionMethod = $encryptionMethod;
1326
1327 28
        $this->setEncrypted($this->encryptionMethod !== ZipEncryptionMethod::NONE);
1328 28
        $this->extractVersion = self::UNKNOWN;
1329
1330 28
        return $this;
1331
    }
1332
1333
    /**
1334
     * @return int
1335
     */
1336 52
    public function getCompressionLevel()
1337
    {
1338 52
        return $this->compressionLevel;
1339
    }
1340
1341
    /**
1342
     * @param int $compressionLevel
1343
     *
1344
     * @return ZipEntry
1345
     */
1346 26
    public function setCompressionLevel($compressionLevel)
1347
    {
1348 26
        $compressionLevel = (int) $compressionLevel;
1349
1350 26
        if ($compressionLevel === self::UNKNOWN) {
1351 1
            $compressionLevel = ZipCompressionLevel::NORMAL;
1352
        }
1353
1354
        if (
1355 26
            $compressionLevel < ZipCompressionLevel::LEVEL_MIN ||
1356 26
            $compressionLevel > ZipCompressionLevel::LEVEL_MAX
1357
        ) {
1358 10
            throw new InvalidArgumentException(
1359
                'Invalid compression level. Minimum level ' .
1360 10
                ZipCompressionLevel::LEVEL_MIN . '. Maximum level ' . ZipCompressionLevel::LEVEL_MAX
1361
            );
1362
        }
1363 16
        $this->compressionLevel = $compressionLevel;
1364
1365 16
        $this->updateGbpfCompLevel();
1366
1367 16
        return $this;
1368
    }
1369
1370
    /**
1371
     * Update general purpose bit flogs.
1372
     */
1373 16
    private function updateGbpfCompLevel()
1374
    {
1375 16
        if ($this->compressionMethod === ZipCompressionMethod::DEFLATED) {
1376 16
            $bit1 = false;
1377 16
            $bit2 = false;
1378
1379 16
            switch ($this->compressionLevel) {
1380
                case ZipCompressionLevel::MAXIMUM:
1381 4
                    $bit1 = true;
1382 4
                    break;
1383
1384
                case ZipCompressionLevel::FAST:
1385 2
                    $bit2 = true;
1386 2
                    break;
1387
1388
                case ZipCompressionLevel::SUPER_FAST:
1389 3
                    $bit1 = true;
1390 3
                    $bit2 = true;
1391 3
                    break;
1392
                // default is ZipCompressionLevel::NORMAL
1393
            }
1394
1395 16
            $this->generalPurposeBitFlags |= ($bit1 ? GeneralPurposeBitFlag::COMPRESSION_FLAG1 : 0);
1396 16
            $this->generalPurposeBitFlags |= ($bit2 ? GeneralPurposeBitFlag::COMPRESSION_FLAG2 : 0);
1397
        }
1398 16
    }
1399
1400
    /**
1401
     * Sets Unix permissions in a way that is understood by Info-Zip's
1402
     * unzip command.
1403
     *
1404
     * @param int $mode mode an int value
1405
     *
1406
     * @return ZipEntry
1407
     */
1408 123
    public function setUnixMode($mode)
1409
    {
1410 123
        $mode = (int) $mode;
1411 123
        $this->setExternalAttributes(
1412
            ($mode << 16)
1413
            // MS-DOS read-only attribute
1414 123
            | (($mode & UnixStat::UNX_IWUSR) === 0 ? DosAttrs::DOS_HIDDEN : 0)
1415
            // MS-DOS directory flag
1416 123
            | ($this->isDirectory() ? DosAttrs::DOS_DIRECTORY : DosAttrs::DOS_ARCHIVE)
1417
        );
1418 123
        $this->createdOS = ZipPlatform::OS_UNIX;
1419
1420 123
        return $this;
1421
    }
1422
1423
    /**
1424
     * Unix permission.
1425
     *
1426
     * @return int the unix permissions
1427
     */
1428 47
    public function getUnixMode()
1429
    {
1430 47
        $mode = 0;
1431
1432 47
        if ($this->createdOS === ZipPlatform::OS_UNIX) {
1433 41
            $mode = ($this->externalAttributes >> 16) & 0xFFFF;
1434 7
        } elseif ($this->hasExtraField(AsiExtraField::HEADER_ID)) {
1435
            /** @var AsiExtraField $asiExtraField */
1436 1
            $asiExtraField = $this->getExtraField(AsiExtraField::HEADER_ID);
1437 1
            $mode = $asiExtraField->getMode();
1438
        }
1439
1440 47
        if ($mode > 0) {
1441 39
            return $mode;
1442
        }
1443
1444 8
        return $this->isDirectory ? 040755 : 0100644;
1445
    }
1446
1447
    /**
1448
     * Offset MUST be considered in decision about ZIP64 format - see
1449
     * description of Data Descriptor in ZIP File Format Specification.
1450
     *
1451
     * @return bool
1452
     */
1453 90
    public function isZip64ExtensionsRequired()
1454
    {
1455 90
        return $this->compressedSize > ZipConstants::ZIP64_MAGIC
1456 90
            || $this->uncompressedSize > ZipConstants::ZIP64_MAGIC;
1457
    }
1458
1459
    /**
1460
     * Returns true if this entry represents a unix symlink,
1461
     * in which case the entry's content contains the target path
1462
     * for the symlink.
1463
     *
1464
     * @return bool true if the entry represents a unix symlink,
1465
     *              false otherwise
1466
     */
1467 21
    public function isUnixSymlink()
1468
    {
1469 21
        return ($this->getUnixMode() & UnixStat::UNX_IFMT) === UnixStat::UNX_IFLNK;
1470
    }
1471
1472
    /**
1473
     * @return \DateTimeInterface
1474
     */
1475 11
    public function getMTime()
1476
    {
1477
        /** @var NtfsExtraField|null $ntfsExtra */
1478 11
        $ntfsExtra = $this->getExtraField(NtfsExtraField::HEADER_ID);
1479
1480 11
        if ($ntfsExtra !== null) {
1481 3
            return $ntfsExtra->getModifyDateTime();
1482
        }
1483
1484
        /** @var ExtendedTimestampExtraField|null $extendedExtra */
1485 10
        $extendedExtra = $this->getExtraField(ExtendedTimestampExtraField::HEADER_ID);
1486
1487 10
        if ($extendedExtra !== null && ($mtime = $extendedExtra->getModifyDateTime()) !== null) {
1488 3
            return $mtime;
1489
        }
1490
1491
        /** @var OldUnixExtraField|null $oldUnixExtra */
1492 9
        $oldUnixExtra = $this->getExtraField(OldUnixExtraField::HEADER_ID);
1493
1494 9
        if ($oldUnixExtra !== null && ($mtime = $oldUnixExtra->getModifyDateTime()) !== null) {
1495 1
            return $mtime;
1496
        }
1497
1498 9
        $timestamp = $this->getTime();
1499
1500
        try {
1501 9
            return new \DateTimeImmutable('@' . $timestamp);
1502
        } catch (\Exception $e) {
1503
            throw new RuntimeException('Error create DateTime object with timestamp ' . $timestamp, 1, $e);
1504
        }
1505
    }
1506
1507
    /**
1508
     * @return \DateTimeInterface|null
1509
     */
1510 12
    public function getATime()
1511
    {
1512
        /** @var NtfsExtraField|null $ntfsExtra */
1513 12
        $ntfsExtra = $this->getExtraField(NtfsExtraField::HEADER_ID);
1514
1515 12
        if ($ntfsExtra !== null) {
1516 3
            return $ntfsExtra->getAccessDateTime();
1517
        }
1518
1519
        /** @var ExtendedTimestampExtraField|null $extendedExtra */
1520 11
        $extendedExtra = $this->getExtraField(ExtendedTimestampExtraField::HEADER_ID);
1521
1522 11
        if ($extendedExtra !== null && ($atime = $extendedExtra->getAccessDateTime()) !== null) {
1523 3
            return $atime;
1524
        }
1525
1526
        /** @var OldUnixExtraField|null $oldUnixExtra */
1527 10
        $oldUnixExtra = $this->getExtraField(OldUnixExtraField::HEADER_ID);
1528
1529 10
        if ($oldUnixExtra !== null) {
1530 1
            return $oldUnixExtra->getAccessDateTime();
1531
        }
1532
1533 10
        return null;
1534
    }
1535
1536
    /**
1537
     * @return \DateTimeInterface|null
1538
     */
1539 6
    public function getCTime()
1540
    {
1541
        /** @var NtfsExtraField|null $ntfsExtra */
1542 6
        $ntfsExtra = $this->getExtraField(NtfsExtraField::HEADER_ID);
1543
1544 6
        if ($ntfsExtra !== null) {
1545 2
            return $ntfsExtra->getCreateDateTime();
1546
        }
1547
1548
        /** @var ExtendedTimestampExtraField|null $extendedExtra */
1549 6
        $extendedExtra = $this->getExtraField(ExtendedTimestampExtraField::HEADER_ID);
1550
1551 6
        if ($extendedExtra !== null) {
1552 2
            return $extendedExtra->getCreateDateTime();
1553
        }
1554
1555 6
        return null;
1556
    }
1557
1558 88
    public function __clone()
1559
    {
1560 88
        $this->cdExtraFields = clone $this->cdExtraFields;
1561 88
        $this->localExtraFields = clone $this->localExtraFields;
1562
1563 88
        if ($this->data !== null) {
1564 85
            $this->data = clone $this->data;
1565
        }
1566 88
    }
1567
}
1568