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

ZipEntry::getSoftwareVersion()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 2
eloc 3
nc 2
nop 0
dl 0
loc 7
ccs 4
cts 4
cp 1
crap 2
rs 10
c 1
b 0
f 1
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