1
|
|
|
<?php |
|
|
|
|
2
|
|
|
|
3
|
|
|
namespace 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
|
|
|
// $Id: OLE.php,v 1.13 2007/03/07 14:38:25 schmidt Exp $ |
24
|
|
|
|
25
|
|
|
/* |
26
|
|
|
* Array for storing OLE instances that are accessed from |
27
|
|
|
* OLE_ChainedBlockStream::stream_open(). |
28
|
|
|
* @var array |
29
|
|
|
*/ |
30
|
|
|
$GLOBALS['_OLE_INSTANCES'] = []; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* OLE package base class. |
34
|
|
|
* |
35
|
|
|
* @author Xavier Noguer <[email protected]> |
36
|
|
|
* @author Christian Schmidt <[email protected]> |
37
|
|
|
* @category PhpSpreadsheet |
38
|
|
|
*/ |
39
|
|
|
class OLE |
40
|
|
|
{ |
41
|
|
|
const OLE_PPS_TYPE_ROOT = 5; |
42
|
|
|
const OLE_PPS_TYPE_DIR = 1; |
43
|
|
|
const OLE_PPS_TYPE_FILE = 2; |
44
|
|
|
const OLE_DATA_SIZE_SMALL = 0x1000; |
45
|
|
|
const OLE_LONG_INT_SIZE = 4; |
46
|
|
|
const OLE_PPS_SIZE = 0x80; |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* The file handle for reading an OLE container |
50
|
|
|
* @var resource |
51
|
|
|
*/ |
52
|
|
|
public $_file_handle; |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* Array of PPS's found on the OLE container |
56
|
|
|
* @var array |
57
|
|
|
*/ |
58
|
|
|
public $_list = []; |
59
|
|
|
|
60
|
|
|
/** |
61
|
|
|
* Root directory of OLE container |
62
|
|
|
* @var OLE_PPS_Root |
63
|
|
|
*/ |
64
|
|
|
public $root; |
65
|
|
|
|
66
|
|
|
/** |
67
|
|
|
* Big Block Allocation Table |
68
|
|
|
* @var array (blockId => nextBlockId) |
69
|
|
|
*/ |
70
|
|
|
public $bbat; |
71
|
|
|
|
72
|
|
|
/** |
73
|
|
|
* Short Block Allocation Table |
74
|
|
|
* @var array (blockId => nextBlockId) |
75
|
|
|
*/ |
76
|
|
|
public $sbat; |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* Size of big blocks. This is usually 512. |
80
|
|
|
* @var int number of octets per block. |
81
|
|
|
*/ |
82
|
|
|
public $bigBlockSize; |
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* Size of small blocks. This is usually 64. |
86
|
|
|
* @var int number of octets per block |
87
|
|
|
*/ |
88
|
|
|
public $smallBlockSize; |
89
|
|
|
|
90
|
|
|
/** |
91
|
|
|
* Reads an OLE container from the contents of the file given. |
92
|
|
|
* |
93
|
|
|
* @acces public |
94
|
|
|
* @param string $file |
95
|
|
|
* @throws \PhpSpreadsheet\Reader\Exception |
96
|
|
|
* @return mixed true on success, PEAR_Error on failure |
97
|
|
|
*/ |
98
|
|
|
public function read($file) |
99
|
|
|
{ |
100
|
|
|
$fh = fopen($file, 'r'); |
101
|
|
|
if (!$fh) { |
102
|
|
|
throw new \PhpSpreadsheet\Reader\Exception("Can't open file $file"); |
103
|
|
|
} |
104
|
|
|
$this->_file_handle = $fh; |
105
|
|
|
|
106
|
|
|
$signature = fread($fh, 8); |
107
|
|
|
if ("\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1" != $signature) { |
108
|
|
|
throw new \PhpSpreadsheet\Reader\Exception("File doesn't seem to be an OLE container."); |
109
|
|
|
} |
110
|
|
|
fseek($fh, 28); |
111
|
|
|
if (fread($fh, 2) != "\xFE\xFF") { |
112
|
|
|
// This shouldn't be a problem in practice |
113
|
|
|
throw new \PhpSpreadsheet\Reader\Exception('Only Little-Endian encoding is supported.'); |
114
|
|
|
} |
115
|
|
|
// Size of blocks and short blocks in bytes |
116
|
|
|
$this->bigBlockSize = pow(2, self::_readInt2($fh)); |
|
|
|
|
117
|
|
|
$this->smallBlockSize = pow(2, self::_readInt2($fh)); |
|
|
|
|
118
|
|
|
|
119
|
|
|
// Skip UID, revision number and version number |
120
|
|
|
fseek($fh, 44); |
121
|
|
|
// Number of blocks in Big Block Allocation Table |
122
|
|
|
$bbatBlockCount = self::_readInt4($fh); |
123
|
|
|
|
124
|
|
|
// Root chain 1st block |
125
|
|
|
$directoryFirstBlockId = self::_readInt4($fh); |
126
|
|
|
|
127
|
|
|
// Skip unused bytes |
128
|
|
|
fseek($fh, 56); |
129
|
|
|
// Streams shorter than this are stored using small blocks |
130
|
|
|
$this->bigBlockThreshold = self::_readInt4($fh); |
|
|
|
|
131
|
|
|
// Block id of first sector in Short Block Allocation Table |
132
|
|
|
$sbatFirstBlockId = self::_readInt4($fh); |
133
|
|
|
// Number of blocks in Short Block Allocation Table |
134
|
|
|
$sbbatBlockCount = self::_readInt4($fh); |
135
|
|
|
// Block id of first sector in Master Block Allocation Table |
136
|
|
|
$mbatFirstBlockId = self::_readInt4($fh); |
137
|
|
|
// Number of blocks in Master Block Allocation Table |
138
|
|
|
$mbbatBlockCount = self::_readInt4($fh); |
139
|
|
|
$this->bbat = []; |
140
|
|
|
|
141
|
|
|
// Remaining 4 * 109 bytes of current block is beginning of Master |
142
|
|
|
// Block Allocation Table |
143
|
|
|
$mbatBlocks = []; |
144
|
|
|
for ($i = 0; $i < 109; ++$i) { |
145
|
|
|
$mbatBlocks[] = self::_readInt4($fh); |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
// Read rest of Master Block Allocation Table (if any is left) |
149
|
|
|
$pos = $this->_getBlockOffset($mbatFirstBlockId); |
150
|
|
View Code Duplication |
for ($i = 0; $i < $mbbatBlockCount; ++$i) { |
|
|
|
|
151
|
|
|
fseek($fh, $pos); |
152
|
|
|
for ($j = 0; $j < $this->bigBlockSize / 4 - 1; ++$j) { |
153
|
|
|
$mbatBlocks[] = self::_readInt4($fh); |
154
|
|
|
} |
155
|
|
|
// Last block id in each block points to next block |
156
|
|
|
$pos = $this->_getBlockOffset(self::_readInt4($fh)); |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
// Read Big Block Allocation Table according to chain specified by |
160
|
|
|
// $mbatBlocks |
161
|
|
View Code Duplication |
for ($i = 0; $i < $bbatBlockCount; ++$i) { |
|
|
|
|
162
|
|
|
$pos = $this->_getBlockOffset($mbatBlocks[$i]); |
163
|
|
|
fseek($fh, $pos); |
164
|
|
|
for ($j = 0; $j < $this->bigBlockSize / 4; ++$j) { |
165
|
|
|
$this->bbat[] = self::_readInt4($fh); |
166
|
|
|
} |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
// Read short block allocation table (SBAT) |
170
|
|
|
$this->sbat = []; |
171
|
|
|
$shortBlockCount = $sbbatBlockCount * $this->bigBlockSize / 4; |
172
|
|
|
$sbatFh = $this->getStream($sbatFirstBlockId); |
173
|
|
|
for ($blockId = 0; $blockId < $shortBlockCount; ++$blockId) { |
174
|
|
|
$this->sbat[$blockId] = self::_readInt4($sbatFh); |
175
|
|
|
} |
176
|
|
|
fclose($sbatFh); |
177
|
|
|
|
178
|
|
|
$this->_readPpsWks($directoryFirstBlockId); |
179
|
|
|
|
180
|
|
|
return true; |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
/** |
184
|
|
|
* @param int block id |
185
|
|
|
* @param int byte offset from beginning of file |
186
|
|
|
*/ |
187
|
|
|
public function _getBlockOffset($blockId) |
188
|
|
|
{ |
189
|
|
|
return 512 + $blockId * $this->bigBlockSize; |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
/** |
193
|
|
|
* Returns a stream for use with fread() etc. External callers should |
194
|
|
|
* use \PhpSpreadsheet\Shared\OLE\PPS\File::getStream(). |
195
|
|
|
* @param int|PPS block id or PPS |
196
|
|
|
* @return resource read-only stream |
197
|
|
|
*/ |
198
|
|
|
public function getStream($blockIdOrPps) |
|
|
|
|
199
|
|
|
{ |
200
|
|
|
static $isRegistered = false; |
201
|
|
|
if (!$isRegistered) { |
202
|
|
|
stream_wrapper_register('ole-chainedblockstream', '\\PhpSpreadsheet\\Shared\\OLE\\ChainedBlockStream'); |
203
|
|
|
$isRegistered = true; |
204
|
|
|
} |
205
|
|
|
|
206
|
|
|
// Store current instance in global array, so that it can be accessed |
207
|
|
|
// in OLE_ChainedBlockStream::stream_open(). |
208
|
|
|
// Object is removed from self::$instances in OLE_Stream::close(). |
209
|
|
|
$GLOBALS['_OLE_INSTANCES'][] = $this; |
210
|
|
|
$instanceId = end(array_keys($GLOBALS['_OLE_INSTANCES'])); |
|
|
|
|
211
|
|
|
|
212
|
|
|
$path = 'ole-chainedblockstream://oleInstanceId=' . $instanceId; |
213
|
|
|
if ($blockIdOrPps instanceof OLE\PPS) { |
214
|
|
|
$path .= '&blockId=' . $blockIdOrPps->startBlock; |
215
|
|
|
$path .= '&size=' . $blockIdOrPps->Size; |
216
|
|
|
} else { |
217
|
|
|
$path .= '&blockId=' . $blockIdOrPps; |
218
|
|
|
} |
219
|
|
|
|
220
|
|
|
return fopen($path, 'r'); |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
/** |
224
|
|
|
* Reads a signed char. |
225
|
|
|
* @param resource file handle |
226
|
|
|
* @return int |
227
|
|
|
*/ |
228
|
|
|
private static function _readInt1($fh) |
229
|
|
|
{ |
230
|
|
|
list(, $tmp) = unpack('c', fread($fh, 1)); |
231
|
|
|
|
232
|
|
|
return $tmp; |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
/** |
236
|
|
|
* Reads an unsigned short (2 octets). |
237
|
|
|
* @param resource file handle |
238
|
|
|
* @return int |
239
|
|
|
*/ |
240
|
|
|
private static function _readInt2($fh) |
241
|
|
|
{ |
242
|
|
|
list(, $tmp) = unpack('v', fread($fh, 2)); |
243
|
|
|
|
244
|
|
|
return $tmp; |
245
|
|
|
} |
246
|
|
|
|
247
|
|
|
/** |
248
|
|
|
* Reads an unsigned long (4 octets). |
249
|
|
|
* @param resource file handle |
250
|
|
|
* @return int |
251
|
|
|
*/ |
252
|
|
|
private static function _readInt4($fh) |
253
|
|
|
{ |
254
|
|
|
list(, $tmp) = unpack('V', fread($fh, 4)); |
255
|
|
|
|
256
|
|
|
return $tmp; |
257
|
|
|
} |
258
|
|
|
|
259
|
|
|
/** |
260
|
|
|
* Gets information about all PPS's on the OLE container from the PPS WK's |
261
|
|
|
* creates an OLE_PPS object for each one. |
262
|
|
|
* |
263
|
|
|
* @param int the block id of the first block |
264
|
|
|
* @return mixed true on success, PEAR_Error on failure |
265
|
|
|
*/ |
266
|
|
|
public function _readPpsWks($blockId) |
267
|
|
|
{ |
268
|
|
|
$fh = $this->getStream($blockId); |
269
|
|
|
for ($pos = 0;; $pos += 128) { |
270
|
|
|
fseek($fh, $pos, SEEK_SET); |
271
|
|
|
$nameUtf16 = fread($fh, 64); |
272
|
|
|
$nameLength = self::_readInt2($fh); |
273
|
|
|
$nameUtf16 = substr($nameUtf16, 0, $nameLength - 2); |
274
|
|
|
// Simple conversion from UTF-16LE to ISO-8859-1 |
275
|
|
|
$name = str_replace("\x00", '', $nameUtf16); |
276
|
|
|
$type = self::_readInt1($fh); |
277
|
|
|
switch ($type) { |
278
|
|
|
case self::OLE_PPS_TYPE_ROOT: |
279
|
|
|
$pps = new OLE\PPS_Root(null, null, []); |
280
|
|
|
$this->root = $pps; |
|
|
|
|
281
|
|
|
break; |
282
|
|
|
case self::OLE_PPS_TYPE_DIR: |
283
|
|
|
$pps = new OLE\PPS(null, null, null, null, null, null, null, null, null, []); |
284
|
|
|
break; |
285
|
|
|
case self::OLE_PPS_TYPE_FILE: |
286
|
|
|
$pps = new OLE\PPS\File($name); |
287
|
|
|
break; |
288
|
|
|
default: |
289
|
|
|
continue; |
290
|
|
|
} |
291
|
|
|
fseek($fh, 1, SEEK_CUR); |
292
|
|
|
$pps->Type = $type; |
|
|
|
|
293
|
|
|
$pps->Name = $name; |
294
|
|
|
$pps->PrevPps = self::_readInt4($fh); |
295
|
|
|
$pps->NextPps = self::_readInt4($fh); |
296
|
|
|
$pps->DirPps = self::_readInt4($fh); |
297
|
|
|
fseek($fh, 20, SEEK_CUR); |
298
|
|
|
$pps->Time1st = self::OLE2LocalDate(fread($fh, 8)); |
299
|
|
|
$pps->Time2nd = self::OLE2LocalDate(fread($fh, 8)); |
300
|
|
|
$pps->startBlock = self::_readInt4($fh); |
301
|
|
|
$pps->Size = self::_readInt4($fh); |
302
|
|
|
$pps->No = count($this->_list); |
303
|
|
|
$this->_list[] = $pps; |
304
|
|
|
|
305
|
|
|
// check if the PPS tree (starting from root) is complete |
306
|
|
|
if (isset($this->root) && $this->_ppsTreeComplete($this->root->No)) { |
307
|
|
|
break; |
308
|
|
|
} |
309
|
|
|
} |
310
|
|
|
fclose($fh); |
311
|
|
|
|
312
|
|
|
// Initialize $pps->children on directories |
313
|
|
|
foreach ($this->_list as $pps) { |
314
|
|
|
if ($pps->Type == self::OLE_PPS_TYPE_DIR || $pps->Type == self::OLE_PPS_TYPE_ROOT) { |
315
|
|
|
$nos = [$pps->DirPps]; |
316
|
|
|
$pps->children = []; |
317
|
|
|
while ($nos) { |
|
|
|
|
318
|
|
|
$no = array_pop($nos); |
319
|
|
|
if ($no != -1) { |
320
|
|
|
$childPps = $this->_list[$no]; |
321
|
|
|
$nos[] = $childPps->PrevPps; |
322
|
|
|
$nos[] = $childPps->NextPps; |
323
|
|
|
$pps->children[] = $childPps; |
324
|
|
|
} |
325
|
|
|
} |
326
|
|
|
} |
327
|
|
|
} |
328
|
|
|
|
329
|
|
|
return true; |
330
|
|
|
} |
331
|
|
|
|
332
|
|
|
/** |
333
|
|
|
* It checks whether the PPS tree is complete (all PPS's read) |
334
|
|
|
* starting with the given PPS (not necessarily root) |
335
|
|
|
* |
336
|
|
|
* @param int $index The index of the PPS from which we are checking |
337
|
|
|
* @return bool Whether the PPS tree for the given PPS is complete |
338
|
|
|
*/ |
339
|
|
|
public function _ppsTreeComplete($index) |
340
|
|
|
{ |
341
|
|
|
return isset($this->_list[$index]) && |
342
|
|
|
($pps = $this->_list[$index]) && |
343
|
|
|
($pps->PrevPps == -1 || |
344
|
|
|
$this->_ppsTreeComplete($pps->PrevPps)) && |
345
|
|
|
($pps->NextPps == -1 || |
346
|
|
|
$this->_ppsTreeComplete($pps->NextPps)) && |
347
|
|
|
($pps->DirPps == -1 || |
348
|
|
|
$this->_ppsTreeComplete($pps->DirPps)); |
349
|
|
|
} |
350
|
|
|
|
351
|
|
|
/** |
352
|
|
|
* Checks whether a PPS is a File PPS or not. |
353
|
|
|
* If there is no PPS for the index given, it will return false. |
354
|
|
|
* |
355
|
|
|
* @param int $index The index for the PPS |
356
|
|
|
* @return bool true if it's a File PPS, false otherwise |
357
|
|
|
*/ |
358
|
|
View Code Duplication |
public function isFile($index) |
|
|
|
|
359
|
|
|
{ |
360
|
|
|
if (isset($this->_list[$index])) { |
361
|
|
|
return $this->_list[$index]->Type == self::OLE_PPS_TYPE_FILE; |
362
|
|
|
} |
363
|
|
|
|
364
|
|
|
return false; |
365
|
|
|
} |
366
|
|
|
|
367
|
|
|
/** |
368
|
|
|
* Checks whether a PPS is a Root PPS or not. |
369
|
|
|
* If there is no PPS for the index given, it will return false. |
370
|
|
|
* |
371
|
|
|
* @param int $index The index for the PPS. |
372
|
|
|
* @return bool true if it's a Root PPS, false otherwise |
373
|
|
|
*/ |
374
|
|
View Code Duplication |
public function isRoot($index) |
|
|
|
|
375
|
|
|
{ |
376
|
|
|
if (isset($this->_list[$index])) { |
377
|
|
|
return $this->_list[$index]->Type == self::OLE_PPS_TYPE_ROOT; |
378
|
|
|
} |
379
|
|
|
|
380
|
|
|
return false; |
381
|
|
|
} |
382
|
|
|
|
383
|
|
|
/** |
384
|
|
|
* Gives the total number of PPS's found in the OLE container. |
385
|
|
|
* |
386
|
|
|
* @return int The total number of PPS's found in the OLE container |
387
|
|
|
*/ |
388
|
|
|
public function ppsTotal() |
389
|
|
|
{ |
390
|
|
|
return count($this->_list); |
391
|
|
|
} |
392
|
|
|
|
393
|
|
|
/** |
394
|
|
|
* Gets data from a PPS |
395
|
|
|
* If there is no PPS for the index given, it will return an empty string. |
396
|
|
|
* |
397
|
|
|
* @param int $index The index for the PPS |
398
|
|
|
* @param int $position The position from which to start reading |
399
|
|
|
* (relative to the PPS) |
400
|
|
|
* @param int $length The amount of bytes to read (at most) |
401
|
|
|
* @return string The binary string containing the data requested |
402
|
|
|
* @see OLE_PPS_File::getStream() |
403
|
|
|
*/ |
404
|
|
|
public function getData($index, $position, $length) |
405
|
|
|
{ |
406
|
|
|
// if position is not valid return empty string |
407
|
|
|
if (!isset($this->_list[$index]) || ($position >= $this->_list[$index]->Size) || ($position < 0)) { |
408
|
|
|
return ''; |
409
|
|
|
} |
410
|
|
|
$fh = $this->getStream($this->_list[$index]); |
411
|
|
|
$data = stream_get_contents($fh, $length, $position); |
412
|
|
|
fclose($fh); |
413
|
|
|
|
414
|
|
|
return $data; |
415
|
|
|
} |
416
|
|
|
|
417
|
|
|
/** |
418
|
|
|
* Gets the data length from a PPS |
419
|
|
|
* If there is no PPS for the index given, it will return 0. |
420
|
|
|
* |
421
|
|
|
* @param int $index The index for the PPS |
422
|
|
|
* @return int The amount of bytes in data the PPS has |
423
|
|
|
*/ |
424
|
|
|
public function getDataLength($index) |
425
|
|
|
{ |
426
|
|
|
if (isset($this->_list[$index])) { |
427
|
|
|
return $this->_list[$index]->Size; |
428
|
|
|
} |
429
|
|
|
|
430
|
|
|
return 0; |
431
|
|
|
} |
432
|
|
|
|
433
|
|
|
/** |
434
|
|
|
* Utility function to transform ASCII text to Unicode |
435
|
|
|
* |
436
|
|
|
* @static |
437
|
|
|
* @param string $ascii The ASCII string to transform |
438
|
|
|
* @return string The string in Unicode |
439
|
|
|
*/ |
440
|
|
|
public static function ascToUcs($ascii) |
441
|
|
|
{ |
442
|
|
|
$rawname = ''; |
443
|
|
|
for ($i = 0; $i < strlen($ascii); ++$i) { |
444
|
|
|
$rawname .= $ascii{$i} |
445
|
|
|
. "\x00"; |
446
|
|
|
} |
447
|
|
|
|
448
|
|
|
return $rawname; |
449
|
|
|
} |
450
|
|
|
|
451
|
|
|
/** |
452
|
|
|
* Utility function |
453
|
|
|
* Returns a string for the OLE container with the date given |
454
|
|
|
* |
455
|
|
|
* @static |
456
|
|
|
* @param int $date A timestamp |
457
|
|
|
* @return string The string for the OLE container |
458
|
|
|
*/ |
459
|
|
|
public static function localDateToOLE($date = null) |
460
|
|
|
{ |
461
|
|
|
if (!isset($date)) { |
462
|
|
|
return "\x00\x00\x00\x00\x00\x00\x00\x00"; |
463
|
|
|
} |
464
|
|
|
|
465
|
|
|
// factor used for separating numbers into 4 bytes parts |
466
|
|
|
$factor = pow(2, 32); |
467
|
|
|
|
468
|
|
|
// days from 1-1-1601 until the beggining of UNIX era |
469
|
|
|
$days = 134774; |
470
|
|
|
// calculate seconds |
471
|
|
|
$big_date = $days * 24 * 3600 + gmmktime(date('H', $date), date('i', $date), date('s', $date), date('m', $date), date('d', $date), date('Y', $date)); |
472
|
|
|
// multiply just to make MS happy |
473
|
|
|
$big_date *= 10000000; |
474
|
|
|
|
475
|
|
|
$high_part = floor($big_date / $factor); |
476
|
|
|
// lower 4 bytes |
477
|
|
|
$low_part = floor((($big_date / $factor) - $high_part) * $factor); |
478
|
|
|
|
479
|
|
|
// Make HEX string |
480
|
|
|
$res = ''; |
481
|
|
|
|
482
|
|
View Code Duplication |
for ($i = 0; $i < 4; ++$i) { |
|
|
|
|
483
|
|
|
$hex = $low_part % 0x100; |
484
|
|
|
$res .= pack('c', $hex); |
485
|
|
|
$low_part /= 0x100; |
486
|
|
|
} |
487
|
|
View Code Duplication |
for ($i = 0; $i < 4; ++$i) { |
|
|
|
|
488
|
|
|
$hex = $high_part % 0x100; |
489
|
|
|
$res .= pack('c', $hex); |
490
|
|
|
$high_part /= 0x100; |
491
|
|
|
} |
492
|
|
|
|
493
|
|
|
return $res; |
494
|
|
|
} |
495
|
|
|
|
496
|
|
|
/** |
497
|
|
|
* Returns a timestamp from an OLE container's date |
498
|
|
|
* |
499
|
|
|
* @static |
500
|
|
|
* @param int $string A binary string with the encoded date |
501
|
|
|
* @return string The timestamp corresponding to the string |
502
|
|
|
*/ |
503
|
|
|
public static function OLE2LocalDate($string) |
504
|
|
|
{ |
505
|
|
|
if (strlen($string) != 8) { |
506
|
|
|
return new PEAR_Error('Expecting 8 byte string'); |
507
|
|
|
} |
508
|
|
|
|
509
|
|
|
// factor used for separating numbers into 4 bytes parts |
510
|
|
|
$factor = pow(2, 32); |
511
|
|
|
list(, $high_part) = unpack('V', substr($string, 4, 4)); |
512
|
|
|
list(, $low_part) = unpack('V', substr($string, 0, 4)); |
513
|
|
|
|
514
|
|
|
$big_date = ($high_part * $factor) + $low_part; |
515
|
|
|
// translate to seconds |
516
|
|
|
$big_date /= 10000000; |
517
|
|
|
|
518
|
|
|
// days from 1-1-1601 until the beggining of UNIX era |
519
|
|
|
$days = 134774; |
520
|
|
|
|
521
|
|
|
// translate to seconds from beggining of UNIX era |
522
|
|
|
$big_date -= $days * 24 * 3600; |
523
|
|
|
|
524
|
|
|
return floor($big_date); |
525
|
|
|
} |
526
|
|
|
} |
527
|
|
|
|
The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.
The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.
To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.