Completed
Push — master ( a54ed6...f4f4d5 )
by Mark
129:01 queued 64:01
created

OLE   D

Complexity

Total Complexity 58

Size/Duplication

Total Lines 526
Duplicated Lines 0 %

Test Coverage

Coverage 19.57%

Importance

Changes 0
Metric Value
eloc 189
dl 0
loc 526
ccs 36
cts 184
cp 0.1957
rs 4.5599
c 0
b 0
f 0
wmc 58

16 Methods

Rating   Name   Duplication   Size   Complexity  
A ppsTotal() 0 3 1
A _getBlockOffset() 0 3 1
B _ppsTreeComplete() 0 10 8
A getData() 0 11 4
A _readInt1() 0 5 1
A ascToUcs() 0 10 2
A getStream() 0 23 3
A isFile() 0 7 2
A localDateToOLE() 0 35 4
C _readPpsWks() 0 67 12
A _readInt2() 0 5 1
A getDataLength() 0 7 2
A _readInt4() 0 5 1
A isRoot() 0 7 2
B read() 0 82 10
A OLE2LocalDate() 0 25 4

How to fix   Complexity   

Complex Class

Complex classes like OLE 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 OLE, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Shared;
4
5
// vim: set expandtab tabstop=4 shiftwidth=4:
6
// +----------------------------------------------------------------------+
7
// | PHP Version 4                                                        |
8
// +----------------------------------------------------------------------+
9
// | Copyright (c) 1997-2002 The PHP Group                                |
10
// +----------------------------------------------------------------------+
11
// | This source file is subject to version 2.02 of the PHP license,      |
12
// | that is bundled with this package in the file LICENSE, and is        |
13
// | available at through the world-wide-web at                           |
14
// | http://www.php.net/license/2_02.txt.                                 |
15
// | If you did not receive a copy of the PHP license and are unable to   |
16
// | obtain it through the world-wide-web, please send a note to          |
17
// | [email protected] so we can mail you a copy immediately.               |
18
// +----------------------------------------------------------------------+
19
// | Author: Xavier Noguer <[email protected]>                              |
20
// | Based on OLE::Storage_Lite by Kawai, Takanori                        |
21
// +----------------------------------------------------------------------+
22
//
23
24
use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException;
25
use PhpOffice\PhpSpreadsheet\Shared\OLE\ChainedBlockStream;
26
use PhpOffice\PhpSpreadsheet\Shared\OLE\PPS\Root;
27
28
/*
29
 * Array for storing OLE instances that are accessed from
30
 * OLE_ChainedBlockStream::stream_open().
31
 *
32
 * @var array
33
 */
34 54
$GLOBALS['_OLE_INSTANCES'] = [];
35
36
/**
37
 * OLE package base class.
38
 *
39
 * @author   Xavier Noguer <[email protected]>
40
 * @author   Christian Schmidt <[email protected]>
41
 *
42
 * @category   PhpSpreadsheet
43
 */
44
class OLE
45
{
46
    const OLE_PPS_TYPE_ROOT = 5;
47
    const OLE_PPS_TYPE_DIR = 1;
48
    const OLE_PPS_TYPE_FILE = 2;
49
    const OLE_DATA_SIZE_SMALL = 0x1000;
50
    const OLE_LONG_INT_SIZE = 4;
51
    const OLE_PPS_SIZE = 0x80;
52
53
    /**
54
     * The file handle for reading an OLE container.
55
     *
56
     * @var resource
57
     */
58
    public $_file_handle;
59
60
    /**
61
     * Array of PPS's found on the OLE container.
62
     *
63
     * @var array
64
     */
65
    public $_list = [];
66
67
    /**
68
     * Root directory of OLE container.
69
     *
70
     * @var Root
71
     */
72
    public $root;
73
74
    /**
75
     * Big Block Allocation Table.
76
     *
77
     * @var array (blockId => nextBlockId)
78
     */
79
    public $bbat;
80
81
    /**
82
     * Short Block Allocation Table.
83
     *
84
     * @var array (blockId => nextBlockId)
85
     */
86
    public $sbat;
87
88
    /**
89
     * Size of big blocks. This is usually 512.
90
     *
91
     * @var int number of octets per block
92
     */
93
    public $bigBlockSize;
94
95
    /**
96
     * Size of small blocks. This is usually 64.
97
     *
98
     * @var int number of octets per block
99
     */
100
    public $smallBlockSize;
101
102
    /**
103
     * Threshold for big blocks.
104
     *
105
     * @var int
106
     */
107
    public $bigBlockThreshold;
108
109
    /**
110
     * Reads an OLE container from the contents of the file given.
111
     *
112
     * @acces public
113
     *
114
     * @param string $file
115
     *
116
     * @throws ReaderException
117
     *
118
     * @return bool true on success, PEAR_Error on failure
119
     */
120
    public function read($file)
121
    {
122
        $fh = fopen($file, 'r');
123
        if (!$fh) {
0 ignored issues
show
introduced by
$fh is of type false|resource, thus it always evaluated to false.
Loading history...
124
            throw new ReaderException("Can't open file $file");
125
        }
126
        $this->_file_handle = $fh;
127
128
        $signature = fread($fh, 8);
129
        if ("\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1" != $signature) {
130
            throw new ReaderException("File doesn't seem to be an OLE container.");
131
        }
132
        fseek($fh, 28);
133
        if (fread($fh, 2) != "\xFE\xFF") {
134
            // This shouldn't be a problem in practice
135
            throw new ReaderException('Only Little-Endian encoding is supported.');
136
        }
137
        // Size of blocks and short blocks in bytes
138
        $this->bigBlockSize = pow(2, self::_readInt2($fh));
139
        $this->smallBlockSize = pow(2, self::_readInt2($fh));
140
141
        // Skip UID, revision number and version number
142
        fseek($fh, 44);
143
        // Number of blocks in Big Block Allocation Table
144
        $bbatBlockCount = self::_readInt4($fh);
145
146
        // Root chain 1st block
147
        $directoryFirstBlockId = self::_readInt4($fh);
148
149
        // Skip unused bytes
150
        fseek($fh, 56);
151
        // Streams shorter than this are stored using small blocks
152
        $this->bigBlockThreshold = self::_readInt4($fh);
153
        // Block id of first sector in Short Block Allocation Table
154
        $sbatFirstBlockId = self::_readInt4($fh);
155
        // Number of blocks in Short Block Allocation Table
156
        $sbbatBlockCount = self::_readInt4($fh);
157
        // Block id of first sector in Master Block Allocation Table
158
        $mbatFirstBlockId = self::_readInt4($fh);
159
        // Number of blocks in Master Block Allocation Table
160
        $mbbatBlockCount = self::_readInt4($fh);
161
        $this->bbat = [];
162
163
        // Remaining 4 * 109 bytes of current block is beginning of Master
164
        // Block Allocation Table
165
        $mbatBlocks = [];
166
        for ($i = 0; $i < 109; ++$i) {
167
            $mbatBlocks[] = self::_readInt4($fh);
168
        }
169
170
        // Read rest of Master Block Allocation Table (if any is left)
171
        $pos = $this->_getBlockOffset($mbatFirstBlockId);
172
        for ($i = 0; $i < $mbbatBlockCount; ++$i) {
173
            fseek($fh, $pos);
174
            for ($j = 0; $j < $this->bigBlockSize / 4 - 1; ++$j) {
175
                $mbatBlocks[] = self::_readInt4($fh);
176
            }
177
            // Last block id in each block points to next block
178
            $pos = $this->_getBlockOffset(self::_readInt4($fh));
179
        }
180
181
        // Read Big Block Allocation Table according to chain specified by $mbatBlocks
182
        for ($i = 0; $i < $bbatBlockCount; ++$i) {
183
            $pos = $this->_getBlockOffset($mbatBlocks[$i]);
184
            fseek($fh, $pos);
185
            for ($j = 0; $j < $this->bigBlockSize / 4; ++$j) {
186
                $this->bbat[] = self::_readInt4($fh);
187
            }
188
        }
189
190
        // Read short block allocation table (SBAT)
191
        $this->sbat = [];
192
        $shortBlockCount = $sbbatBlockCount * $this->bigBlockSize / 4;
193
        $sbatFh = $this->getStream($sbatFirstBlockId);
194
        for ($blockId = 0; $blockId < $shortBlockCount; ++$blockId) {
195
            $this->sbat[$blockId] = self::_readInt4($sbatFh);
196
        }
197
        fclose($sbatFh);
198
199
        $this->_readPpsWks($directoryFirstBlockId);
200
201
        return true;
202
    }
203
204
    /**
205
     * @param int $blockId byte offset from beginning of file
206
     *
207
     * @return int
208
     */
209
    public function _getBlockOffset($blockId)
210
    {
211
        return 512 + $blockId * $this->bigBlockSize;
212
    }
213
214
    /**
215
     * Returns a stream for use with fread() etc. External callers should
216
     * use \PhpOffice\PhpSpreadsheet\Shared\OLE\PPS\File::getStream().
217
     *
218
     * @param int|OLE\PPS $blockIdOrPps block id or PPS
219
     *
220
     * @return resource read-only stream
221
     */
222
    public function getStream($blockIdOrPps)
223
    {
224
        static $isRegistered = false;
225
        if (!$isRegistered) {
226
            stream_wrapper_register('ole-chainedblockstream', ChainedBlockStream::class);
227
            $isRegistered = true;
228
        }
229
230
        // Store current instance in global array, so that it can be accessed
231
        // in OLE_ChainedBlockStream::stream_open().
232
        // Object is removed from self::$instances in OLE_Stream::close().
233
        $GLOBALS['_OLE_INSTANCES'][] = $this;
234
        $instanceId = end(array_keys($GLOBALS['_OLE_INSTANCES']));
235
236
        $path = 'ole-chainedblockstream://oleInstanceId=' . $instanceId;
237
        if ($blockIdOrPps instanceof OLE\PPS) {
238
            $path .= '&blockId=' . $blockIdOrPps->startBlock;
239
            $path .= '&size=' . $blockIdOrPps->Size;
240
        } else {
241
            $path .= '&blockId=' . $blockIdOrPps;
242
        }
243
244
        return fopen($path, 'r');
0 ignored issues
show
Bug Best Practice introduced by
The expression return fopen($path, 'r') could also return false which is incompatible with the documented return type resource. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
245
    }
246
247
    /**
248
     * Reads a signed char.
249
     *
250
     * @param resource $fh file handle
251
     *
252
     * @return int
253
     */
254
    private static function _readInt1($fh)
255
    {
256
        list(, $tmp) = unpack('c', fread($fh, 1));
257
258
        return $tmp;
259
    }
260
261
    /**
262
     * Reads an unsigned short (2 octets).
263
     *
264
     * @param resource $fh file handle
265
     *
266
     * @return int
267
     */
268
    private static function _readInt2($fh)
269
    {
270
        list(, $tmp) = unpack('v', fread($fh, 2));
271
272
        return $tmp;
273
    }
274
275
    /**
276
     * Reads an unsigned long (4 octets).
277
     *
278
     * @param resource $fh file handle
279
     *
280
     * @return int
281
     */
282
    private static function _readInt4($fh)
283
    {
284
        list(, $tmp) = unpack('V', fread($fh, 4));
285
286
        return $tmp;
287
    }
288
289
    /**
290
     * Gets information about all PPS's on the OLE container from the PPS WK's
291
     * creates an OLE_PPS object for each one.
292
     *
293
     * @param int $blockId the block id of the first block
294
     *
295
     * @return bool true on success, PEAR_Error on failure
296
     */
297
    public function _readPpsWks($blockId)
298
    {
299
        $fh = $this->getStream($blockId);
300
        for ($pos = 0; true; $pos += 128) {
301
            fseek($fh, $pos, SEEK_SET);
302
            $nameUtf16 = fread($fh, 64);
303
            $nameLength = self::_readInt2($fh);
304
            $nameUtf16 = substr($nameUtf16, 0, $nameLength - 2);
305
            // Simple conversion from UTF-16LE to ISO-8859-1
306
            $name = str_replace("\x00", '', $nameUtf16);
307
            $type = self::_readInt1($fh);
308
            switch ($type) {
309
                case self::OLE_PPS_TYPE_ROOT:
310
                    $pps = new OLE\PPS\Root(null, null, []);
311
                    $this->root = $pps;
312
313
                    break;
314
                case self::OLE_PPS_TYPE_DIR:
315
                    $pps = new OLE\PPS(null, null, null, null, null, null, null, null, null, []);
316
317
                    break;
318
                case self::OLE_PPS_TYPE_FILE:
319
                    $pps = new OLE\PPS\File($name);
320
321
                    break;
322
                default:
323
                    break;
324
            }
325
            fseek($fh, 1, SEEK_CUR);
326
            $pps->Type = $type;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $pps does not seem to be defined for all execution paths leading up to this point.
Loading history...
327
            $pps->Name = $name;
328
            $pps->PrevPps = self::_readInt4($fh);
329
            $pps->NextPps = self::_readInt4($fh);
330
            $pps->DirPps = self::_readInt4($fh);
331
            fseek($fh, 20, SEEK_CUR);
332
            $pps->Time1st = self::OLE2LocalDate(fread($fh, 8));
333
            $pps->Time2nd = self::OLE2LocalDate(fread($fh, 8));
334
            $pps->startBlock = self::_readInt4($fh);
335
            $pps->Size = self::_readInt4($fh);
336
            $pps->No = count($this->_list);
337
            $this->_list[] = $pps;
338
339
            // check if the PPS tree (starting from root) is complete
340
            if (isset($this->root) && $this->_ppsTreeComplete($this->root->No)) {
341
                break;
342
            }
343
        }
344
        fclose($fh);
345
346
        // Initialize $pps->children on directories
347
        foreach ($this->_list as $pps) {
348
            if ($pps->Type == self::OLE_PPS_TYPE_DIR || $pps->Type == self::OLE_PPS_TYPE_ROOT) {
349
                $nos = [$pps->DirPps];
350
                $pps->children = [];
351
                while ($nos) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $nos of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
352
                    $no = array_pop($nos);
353
                    if ($no != -1) {
354
                        $childPps = $this->_list[$no];
355
                        $nos[] = $childPps->PrevPps;
356
                        $nos[] = $childPps->NextPps;
357
                        $pps->children[] = $childPps;
358
                    }
359
                }
360
            }
361
        }
362
363
        return true;
364
    }
365
366
    /**
367
     * It checks whether the PPS tree is complete (all PPS's read)
368
     * starting with the given PPS (not necessarily root).
369
     *
370
     * @param int $index The index of the PPS from which we are checking
371
     *
372
     * @return bool Whether the PPS tree for the given PPS is complete
373
     */
374
    public function _ppsTreeComplete($index)
375
    {
376
        return isset($this->_list[$index]) &&
377
            ($pps = $this->_list[$index]) &&
378
            ($pps->PrevPps == -1 ||
379
                $this->_ppsTreeComplete($pps->PrevPps)) &&
380
            ($pps->NextPps == -1 ||
381
                $this->_ppsTreeComplete($pps->NextPps)) &&
382
            ($pps->DirPps == -1 ||
383
                $this->_ppsTreeComplete($pps->DirPps));
384
    }
385
386
    /**
387
     * Checks whether a PPS is a File PPS or not.
388
     * If there is no PPS for the index given, it will return false.
389
     *
390
     * @param int $index The index for the PPS
391
     *
392
     * @return bool true if it's a File PPS, false otherwise
393
     */
394
    public function isFile($index)
395
    {
396
        if (isset($this->_list[$index])) {
397
            return $this->_list[$index]->Type == self::OLE_PPS_TYPE_FILE;
398
        }
399
400
        return false;
401
    }
402
403
    /**
404
     * Checks whether a PPS is a Root PPS or not.
405
     * If there is no PPS for the index given, it will return false.
406
     *
407
     * @param int $index the index for the PPS
408
     *
409
     * @return bool true if it's a Root PPS, false otherwise
410
     */
411
    public function isRoot($index)
412
    {
413
        if (isset($this->_list[$index])) {
414
            return $this->_list[$index]->Type == self::OLE_PPS_TYPE_ROOT;
415
        }
416
417
        return false;
418
    }
419
420
    /**
421
     * Gives the total number of PPS's found in the OLE container.
422
     *
423
     * @return int The total number of PPS's found in the OLE container
424
     */
425
    public function ppsTotal()
426
    {
427
        return count($this->_list);
428
    }
429
430
    /**
431
     * Gets data from a PPS
432
     * If there is no PPS for the index given, it will return an empty string.
433
     *
434
     * @param int $index The index for the PPS
435
     * @param int $position The position from which to start reading
436
     *                          (relative to the PPS)
437
     * @param int $length The amount of bytes to read (at most)
438
     *
439
     * @return string The binary string containing the data requested
440
     *
441
     * @see OLE_PPS_File::getStream()
442
     */
443
    public function getData($index, $position, $length)
444
    {
445
        // if position is not valid return empty string
446
        if (!isset($this->_list[$index]) || ($position >= $this->_list[$index]->Size) || ($position < 0)) {
447
            return '';
448
        }
449
        $fh = $this->getStream($this->_list[$index]);
450
        $data = stream_get_contents($fh, $length, $position);
451
        fclose($fh);
452
453
        return $data;
454
    }
455
456
    /**
457
     * Gets the data length from a PPS
458
     * If there is no PPS for the index given, it will return 0.
459
     *
460
     * @param int $index The index for the PPS
461
     *
462
     * @return int The amount of bytes in data the PPS has
463
     */
464
    public function getDataLength($index)
465
    {
466
        if (isset($this->_list[$index])) {
467
            return $this->_list[$index]->Size;
468
        }
469
470
        return 0;
471
    }
472
473
    /**
474
     * Utility function to transform ASCII text to Unicode.
475
     *
476
     * @param string $ascii The ASCII string to transform
477
     *
478
     * @return string The string in Unicode
479
     */
480 43
    public static function ascToUcs($ascii)
481
    {
482 43
        $rawname = '';
483 43
        $iMax = strlen($ascii);
484 43
        for ($i = 0; $i < $iMax; ++$i) {
485 43
            $rawname .= $ascii[$i]
486 43
                . "\x00";
487
        }
488
489 43
        return $rawname;
490
    }
491
492
    /**
493
     * Utility function
494
     * Returns a string for the OLE container with the date given.
495
     *
496
     * @param int $date A timestamp
497
     *
498
     * @return string The string for the OLE container
499
     */
500 43
    public static function localDateToOLE($date)
501
    {
502 43
        if (!isset($date)) {
503 43
            return "\x00\x00\x00\x00\x00\x00\x00\x00";
504
        }
505
506
        // factor used for separating numbers into 4 bytes parts
507 43
        $factor = pow(2, 32);
508
509
        // days from 1-1-1601 until the beggining of UNIX era
510 43
        $days = 134774;
511
        // calculate seconds
512 43
        $big_date = $days * 24 * 3600 + gmmktime(date('H', $date), date('i', $date), date('s', $date), date('m', $date), date('d', $date), date('Y', $date));
0 ignored issues
show
Bug introduced by
date('Y', $date) of type string is incompatible with the type integer expected by parameter $year of gmmktime(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

512
        $big_date = $days * 24 * 3600 + gmmktime(date('H', $date), date('i', $date), date('s', $date), date('m', $date), date('d', $date), /** @scrutinizer ignore-type */ date('Y', $date));
Loading history...
Bug introduced by
date('i', $date) of type string is incompatible with the type integer expected by parameter $minute of gmmktime(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

512
        $big_date = $days * 24 * 3600 + gmmktime(date('H', $date), /** @scrutinizer ignore-type */ date('i', $date), date('s', $date), date('m', $date), date('d', $date), date('Y', $date));
Loading history...
Bug introduced by
date('s', $date) of type string is incompatible with the type integer expected by parameter $second of gmmktime(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

512
        $big_date = $days * 24 * 3600 + gmmktime(date('H', $date), date('i', $date), /** @scrutinizer ignore-type */ date('s', $date), date('m', $date), date('d', $date), date('Y', $date));
Loading history...
Bug introduced by
date('d', $date) of type string is incompatible with the type integer expected by parameter $day of gmmktime(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

512
        $big_date = $days * 24 * 3600 + gmmktime(date('H', $date), date('i', $date), date('s', $date), date('m', $date), /** @scrutinizer ignore-type */ date('d', $date), date('Y', $date));
Loading history...
Bug introduced by
date('H', $date) of type string is incompatible with the type integer expected by parameter $hour of gmmktime(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

512
        $big_date = $days * 24 * 3600 + gmmktime(/** @scrutinizer ignore-type */ date('H', $date), date('i', $date), date('s', $date), date('m', $date), date('d', $date), date('Y', $date));
Loading history...
Bug introduced by
date('m', $date) of type string is incompatible with the type integer expected by parameter $month of gmmktime(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

512
        $big_date = $days * 24 * 3600 + gmmktime(date('H', $date), date('i', $date), date('s', $date), /** @scrutinizer ignore-type */ date('m', $date), date('d', $date), date('Y', $date));
Loading history...
513
        // multiply just to make MS happy
514 43
        $big_date *= 10000000;
515
516 43
        $high_part = floor($big_date / $factor);
517
        // lower 4 bytes
518 43
        $low_part = floor((($big_date / $factor) - $high_part) * $factor);
519
520
        // Make HEX string
521 43
        $res = '';
522
523 43
        for ($i = 0; $i < 4; ++$i) {
524 43
            $hex = $low_part % 0x100;
525 43
            $res .= pack('c', $hex);
526 43
            $low_part /= 0x100;
527
        }
528 43
        for ($i = 0; $i < 4; ++$i) {
529 43
            $hex = $high_part % 0x100;
530 43
            $res .= pack('c', $hex);
531 43
            $high_part /= 0x100;
532
        }
533
534 43
        return $res;
535
    }
536
537
    /**
538
     * Returns a timestamp from an OLE container's date.
539
     *
540
     * @param string $oleTimestamp A binary string with the encoded date
541
     *
542
     * @return int The Unix timestamp corresponding to the string
543
     * @throws ReaderException
544 22
     */
545
    public static function OLE2LocalDate($oleTimestamp)
546 22
    {
547
        if (strlen($oleTimestamp) != 8) {
548
            throw new ReaderException('Expecting 8 byte string');
549
        }
550
551 22
        // convert to units of 100 ns since 1601:
552 22
        $unpackedTimestamp = unpack('v4', $oleTimestamp);
553 22
        $timestampHigh = (float)$unpackedTimestamp[4] * 65536 + (float)$unpackedTimestamp[3];
554
        $timestampLow = (float)$unpackedTimestamp[2] * 65536 + (float)$unpackedTimestamp[1];
555 22
556
        // translate to seconds since 1601:
557 22
        $timestampHigh /= 10000000;
558
        $timestampLow /= 10000000;
559
560 22
        // days from 1601 to 1970:
561
        $days = 134774;
562
563 22
        // translate to seconds since 1970:
564
        $unixTimestamp = floor(65536.0 * 65536.0 * $timestampHigh + $timestampLow - $days * 24 * 3600 + 0.5);
565 22
566
        if( (int) $unixTimestamp == $unixTimestamp ) {
567
            return (int) $unixTimestamp;
568
        } else {
569
            return $unixTimestamp >= 0.0 ? PHP_INT_MAX : PHP_INT_MIN;
570
        }
571
    }
572
}
573