Passed
Push — master ( 25d5dd...d305ab )
by Alexey
08:29 queued 11s
created

ExtendedTimestampExtraField   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 378
Duplicated Lines 0 %

Test Coverage

Coverage 36.67%

Importance

Changes 1
Bugs 0 Features 1
Metric Value
eloc 88
c 1
b 0
f 1
dl 0
loc 378
ccs 33
cts 90
cp 0.3667
rs 8.96
wmc 43

20 Methods

Rating   Name   Duplication   Size   Complexity  
B packLocalFileData() 0 17 7
A unpackCentralDirData() 0 3 1
A getCreateTime() 0 3 1
A getAccessTime() 0 3 1
A getCreateDateTime() 0 3 1
A getModifyTime() 0 3 1
A getFlags() 0 3 1
A __construct() 0 6 1
A packCentralDirData() 0 5 2
A create() 0 20 4
A getModifyDateTime() 0 3 1
A getAccessDateTime() 0 3 1
A unpackLocalFileData() 0 27 6
A getHeaderId() 0 3 1
A timestampToDateTime() 0 6 3
A setAccessTime() 0 4 1
A updateFlags() 0 16 4
A setModifyTime() 0 4 1
A __toString() 0 21 4
A setCreateTime() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like ExtendedTimestampExtraField often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ExtendedTimestampExtraField, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace PhpZip\Model\Extra\Fields;
4
5
use PhpZip\Model\Extra\ZipExtraField;
6
use PhpZip\Model\ZipEntry;
7
8
/**
9
 * Extended Timestamp Extra Field:
10
 * ==============================.
11
 *
12
 * The following is the layout of the extended-timestamp extra block.
13
 * (Last Revision 19970118)
14
 *
15
 * Local-header version:
16
 *
17
 * Value         Size        Description
18
 * -----         ----        -----------
19
 * (time) 0x5455 Short       tag for this extra block type ("UT")
20
 * TSize         Short       total data size for this block
21
 * Flags         Byte        info bits
22
 * (ModTime)     Long        time of last modification (UTC/GMT)
23
 * (AcTime)      Long        time of last access (UTC/GMT)
24
 * (CrTime)      Long        time of original creation (UTC/GMT)
25
 *
26
 * Central-header version:
27
 *
28
 * Value         Size        Description
29
 * -----         ----        -----------
30
 * (time) 0x5455 Short       tag for this extra block type ("UT")
31
 * TSize         Short       total data size for this block
32
 * Flags         Byte        info bits (refers to local header!)
33
 * (ModTime)     Long        time of last modification (UTC/GMT)
34
 *
35
 * The central-header extra field contains the modification time only,
36
 * or no timestamp at all.  TSize is used to flag its presence or
37
 * absence.  But note:
38
 *
39
 * If "Flags" indicates that Modtime is present in the local header
40
 * field, it MUST be present in the central header field, too!
41
 * This correspondence is required because the modification time
42
 * value may be used to support trans-timezone freshening and
43
 * updating operations with zip archives.
44
 *
45
 * The time values are in standard Unix signed-long format, indicating
46
 * the number of seconds since 1 January 1970 00:00:00.  The times
47
 * are relative to Coordinated Universal Time (UTC), also sometimes
48
 * referred to as Greenwich Mean Time (GMT).  To convert to local time,
49
 * the software must know the local timezone offset from UTC/GMT.
50
 *
51
 * The lower three bits of Flags in both headers indicate which time-
52
 * stamps are present in the LOCAL extra field:
53
 *
54
 * bit 0           if set, modification time is present
55
 * bit 1           if set, access time is present
56
 * bit 2           if set, creation time is present
57
 * bits 3-7        reserved for additional timestamps; not set
58
 *
59
 * Those times that are present will appear in the order indicated, but
60
 * any combination of times may be omitted.  (Creation time may be
61
 * present without access time, for example.)  TSize should equal
62
 * (1 + 4*(number of set bits in Flags)), as the block is currently
63
 * defined.  Other timestamps may be added in the future.
64
 *
65
 * @see ftp://ftp.info-zip.org/pub/infozip/doc/appnote-iz-latest.zip Info-ZIP version Specification
66
 */
67
class ExtendedTimestampExtraField implements ZipExtraField
68
{
69
    /** @var int Header id */
70
    const HEADER_ID = 0x5455;
71
72
    /**
73
     * @var int the bit set inside the flags by when the last modification time
74
     *          is present in this extra field
75
     */
76
    const MODIFY_TIME_BIT = 1;
77
78
    /**
79
     * @var int the bit set inside the flags by when the last access time is
80
     *          present in this extra field
81
     */
82
    const ACCESS_TIME_BIT = 2;
83
84
    /**
85
     * @var int the bit set inside the flags by when the original creation time
86
     *          is present in this extra field
87
     */
88
    const CREATE_TIME_BIT = 4;
89
90
    /**
91
     * @var int The 3 boolean fields (below) come from this flags byte.  The remaining 5 bits
92
     *          are ignored according to the current version of the spec (December 2012).
93
     */
94
    private $flags;
95
96
    /** @var int|null Modify time */
97
    private $modifyTime;
98
99
    /** @var int|null Access time */
100
    private $accessTime;
101
102
    /** @var int|null Create time */
103
    private $createTime;
104
105
    /**
106
     * @param int      $flags
107
     * @param int|null $modifyTime
108
     * @param int|null $accessTime
109
     * @param int|null $createTime
110
     */
111 2
    public function __construct($flags, $modifyTime, $accessTime, $createTime)
112
    {
113 2
        $this->flags = (int) $flags;
114 2
        $this->modifyTime = $modifyTime;
115 2
        $this->accessTime = $accessTime;
116 2
        $this->createTime = $createTime;
117 2
    }
118
119
    /**
120
     * @param int|null $modifyTime
121
     * @param int|null $accessTime
122
     * @param int|null $createTime
123
     *
124
     * @return ExtendedTimestampExtraField
125
     */
126
    public static function create($modifyTime, $accessTime, $createTime)
127
    {
128
        $flags = 0;
129
130
        if ($modifyTime !== null) {
131
            $modifyTime = (int) $modifyTime;
132
            $flags |= self::MODIFY_TIME_BIT;
133
        }
134
135
        if ($accessTime !== null) {
136
            $accessTime = (int) $accessTime;
137
            $flags |= self::ACCESS_TIME_BIT;
138
        }
139
140
        if ($createTime !== null) {
141
            $createTime = (int) $createTime;
142
            $flags |= self::CREATE_TIME_BIT;
143
        }
144
145
        return new self($flags, $modifyTime, $accessTime, $createTime);
146
    }
147
148
    /**
149
     * Returns the Header ID (type) of this Extra Field.
150
     * The Header ID is an unsigned short integer (two bytes)
151
     * which must be constant during the life cycle of this object.
152
     *
153
     * @return int
154
     */
155 5
    public function getHeaderId()
156
    {
157 5
        return self::HEADER_ID;
158
    }
159
160
    /**
161
     * Populate data from this array as if it was in local file data.
162
     *
163
     * @param string        $buffer the buffer to read data from
164
     * @param ZipEntry|null $entry
165
     *
166
     * @return ExtendedTimestampExtraField
167
     */
168 2
    public static function unpackLocalFileData($buffer, ZipEntry $entry = null)
169
    {
170 2
        $length = \strlen($buffer);
171 2
        $flags = unpack('C', $buffer)[1];
172 2
        $offset = 1;
173
174 2
        $modifyTime = null;
175 2
        $accessTime = null;
176 2
        $createTime = null;
177
178 2
        if (($flags & self::MODIFY_TIME_BIT) === self::MODIFY_TIME_BIT) {
179 2
            $modifyTime = unpack('V', substr($buffer, $offset, 4))[1];
180 2
            $offset += 4;
181
        }
182
183
        // Notice the extra length check in case we are parsing the shorter
184
        // central data field (for both access and create timestamps).
185 2
        if ((($flags & self::ACCESS_TIME_BIT) === self::ACCESS_TIME_BIT) && $offset + 4 <= $length) {
186 2
            $accessTime = unpack('V', substr($buffer, $offset, 4))[1];
187 2
            $offset += 4;
188
        }
189
190 2
        if ((($flags & self::CREATE_TIME_BIT) === self::CREATE_TIME_BIT) && $offset + 4 <= $length) {
191
            $createTime = unpack('V', substr($buffer, $offset, 4))[1];
192
        }
193
194 2
        return new self($flags, $modifyTime, $accessTime, $createTime);
195
    }
196
197
    /**
198
     * Populate data from this array as if it was in central directory data.
199
     *
200
     * @param string        $buffer the buffer to read data from
201
     * @param ZipEntry|null $entry
202
     *
203
     * @return ExtendedTimestampExtraField
204
     */
205 2
    public static function unpackCentralDirData($buffer, ZipEntry $entry = null)
206
    {
207 2
        return self::unpackLocalFileData($buffer, $entry);
208
    }
209
210
    /**
211
     * The actual data to put into local file data - without Header-ID
212
     * or length specifier.
213
     *
214
     * @return string the data
215
     */
216
    public function packLocalFileData()
217
    {
218
        $data = '';
219
220
        if (($this->flags & self::MODIFY_TIME_BIT) === self::MODIFY_TIME_BIT && $this->modifyTime !== null) {
221
            $data .= pack('V', $this->modifyTime);
222
        }
223
224
        if (($this->flags & self::ACCESS_TIME_BIT) === self::ACCESS_TIME_BIT && $this->accessTime !== null) {
225
            $data .= pack('V', $this->accessTime);
226
        }
227
228
        if (($this->flags & self::CREATE_TIME_BIT) === self::CREATE_TIME_BIT && $this->createTime !== null) {
229
            $data .= pack('V', $this->createTime);
230
        }
231
232
        return pack('C', $this->flags) . $data;
233
    }
234
235
    /**
236
     * The actual data to put into central directory - without Header-ID or
237
     * length specifier.
238
     *
239
     * Note: even if bit1 and bit2 are set, the Central data will still
240
     * not contain access/create fields: only local data ever holds those!
241
     *
242
     * @return string the data
243
     */
244
    public function packCentralDirData()
245
    {
246
        $cdLength = 1 + ($this->modifyTime !== null ? 4 : 0);
247
248
        return substr($this->packLocalFileData(), 0, $cdLength);
249
    }
250
251
    /**
252
     * Gets flags byte.
253
     *
254
     * The flags byte tells us which of the three datestamp fields are
255
     * present in the data:
256
     * bit0 - modify time
257
     * bit1 - access time
258
     * bit2 - create time
259
     *
260
     * Only first 3 bits of flags are used according to the
261
     * latest version of the spec (December 2012).
262
     *
263
     * @return int flags byte indicating which of the
264
     *             three datestamp fields are present
265
     */
266
    public function getFlags()
267
    {
268
        return $this->flags;
269
    }
270
271
    /**
272
     * Returns the modify time (seconds since epoch) of this zip entry,
273
     * or null if no such timestamp exists in the zip entry.
274
     *
275
     * @return int|null modify time (seconds since epoch) or null
276
     */
277
    public function getModifyTime()
278
    {
279
        return $this->modifyTime;
280
    }
281
282
    /**
283
     * Returns the access time (seconds since epoch) of this zip entry,
284
     * or null if no such timestamp exists in the zip entry.
285
     *
286
     * @return int|null access time (seconds since epoch) or null
287
     */
288
    public function getAccessTime()
289
    {
290
        return $this->accessTime;
291
    }
292
293
    /**
294
     * Returns the create time (seconds since epoch) of this zip entry,
295
     * or null if no such timestamp exists in the zip entry.
296
     *
297
     * Note: modern linux file systems (e.g., ext2)
298
     * do not appear to store a "create time" value, and so
299
     * it's usually omitted altogether in the zip extra
300
     * field. Perhaps other unix systems track this.
301
     *
302
     * @return int|null create time (seconds since epoch) or null
303
     */
304
    public function getCreateTime()
305
    {
306
        return $this->createTime;
307
    }
308
309
    /**
310
     * Returns the modify time as a \DateTimeInterface
311
     * of this zip entry, or null if no such timestamp exists in the zip entry.
312
     * The milliseconds are always zeroed out, since the underlying data
313
     * offers only per-second precision.
314
     *
315
     * @return \DateTimeInterface|null modify time as \DateTimeInterface or null
316
     */
317 3
    public function getModifyDateTime()
318
    {
319 3
        return self::timestampToDateTime($this->modifyTime);
320
    }
321
322
    /**
323
     * Returns the access time as a \DateTimeInterface
324
     * of this zip entry, or null if no such timestamp exists in the zip entry.
325
     * The milliseconds are always zeroed out, since the underlying data
326
     * offers only per-second precision.
327
     *
328
     * @return \DateTimeInterface|null access time as \DateTimeInterface or null
329
     */
330 3
    public function getAccessDateTime()
331
    {
332 3
        return self::timestampToDateTime($this->accessTime);
333
    }
334
335
    /**
336
     * Returns the create time as a a \DateTimeInterface
337
     * of this zip entry, or null if no such timestamp exists in the zip entry.
338
     * The milliseconds are always zeroed out, since the underlying data
339
     * offers only per-second precision.
340
     *
341
     * Note: modern linux file systems (e.g., ext2)
342
     * do not appear to store a "create time" value, and so
343
     * it's usually omitted altogether in the zip extra
344
     * field.  Perhaps other unix systems track $this->.
345
     *
346
     * @return \DateTimeInterface|null create time as \DateTimeInterface or null
347
     */
348 2
    public function getCreateDateTime()
349
    {
350 2
        return self::timestampToDateTime($this->createTime);
351
    }
352
353
    /**
354
     * Sets the modify time (seconds since epoch) of this zip entry
355
     * using a integer.
356
     *
357
     * @param int|null $unixTime unix time of the modify time (seconds per epoch) or null
358
     */
359
    public function setModifyTime($unixTime)
360
    {
361
        $this->modifyTime = $unixTime;
362
        $this->updateFlags();
363
    }
364
365
    private function updateFlags()
366
    {
367
        $flags = 0;
368
369
        if ($this->modifyTime !== null) {
370
            $flags |= self::MODIFY_TIME_BIT;
371
        }
372
373
        if ($this->accessTime !== null) {
374
            $flags |= self::ACCESS_TIME_BIT;
375
        }
376
377
        if ($this->createTime !== null) {
378
            $flags |= self::CREATE_TIME_BIT;
379
        }
380
        $this->flags = $flags;
381
    }
382
383
    /**
384
     * Sets the access time (seconds since epoch) of this zip entry
385
     * using a integer.
386
     *
387
     * @param int|null $unixTime Unix time of the access time (seconds per epoch) or null
388
     */
389
    public function setAccessTime($unixTime)
390
    {
391 3
        $this->accessTime = $unixTime;
392
        $this->updateFlags();
393
    }
394 3
395
    /**
396
     * Sets the create time (seconds since epoch) of this zip entry
397
     * using a integer.
398
     *
399
     * @param int|null $unixTime Unix time of the create time (seconds per epoch) or null
400
     */
401
    public function setCreateTime($unixTime)
402
    {
403
        $this->createTime = $unixTime;
404
        $this->updateFlags();
405
    }
406
407
    /**
408
     * @param int|null $timestamp
409
     *
410
     * @return \DateTimeInterface|null
411
     */
412
    private static function timestampToDateTime($timestamp)
413
    {
414
        try {
415
            return $timestamp !== null ? new \DateTimeImmutable('@' . $timestamp) : null;
416
        } catch (\Exception $e) {
417
            return null;
418
        }
419
    }
420
421
    /**
422
     * @return string
423
     */
424
    public function __toString()
425
    {
426
        $args = [self::HEADER_ID];
427
        $format = '0x%04x ExtendedTimestamp:';
428
429
        if ($this->modifyTime !== null) {
430
            $format .= ' Modify:[%s]';
431
            $args[] = date(\DATE_W3C, $this->modifyTime);
432
        }
433
434
        if ($this->accessTime !== null) {
435
            $format .= ' Access:[%s]';
436
            $args[] = date(\DATE_W3C, $this->accessTime);
437
        }
438
439
        if ($this->createTime !== null) {
440
            $format .= ' Create:[%s]';
441
            $args[] = date(\DATE_W3C, $this->createTime);
442
        }
443
444
        return vsprintf($format, $args);
445
    }
446
}
447