forked from SimplesIP/pabx-app
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
526 lines
17 KiB
526 lines
17 KiB
<?php |
|
/* vim: set expandtab tabstop=4 shiftwidth=4: */ |
|
// +----------------------------------------------------------------------+ |
|
// | PHP Version 4 | |
|
// +----------------------------------------------------------------------+ |
|
// | Copyright (c) 1997-2002 The PHP Group | |
|
// +----------------------------------------------------------------------+ |
|
// | This source file is subject to version 2.02 of the PHP license, | |
|
// | that is bundled with this package in the file LICENSE, and is | |
|
// | available at through the world-wide-web at | |
|
// | http://www.php.net/license/2_02.txt. | |
|
// | If you did not receive a copy of the PHP license and are unable to | |
|
// | obtain it through the world-wide-web, please send a note to | |
|
// | license@php.net so we can mail you a copy immediately. | |
|
// +----------------------------------------------------------------------+ |
|
// | Author: Xavier Noguer <xnoguer@php.net> | |
|
// | Based on OLE::Storage_Lite by Kawai, Takanori | |
|
// +----------------------------------------------------------------------+ |
|
// |
|
// $Id: OLE.php,v 1.13 2007/03/07 14:38:25 schmidt Exp $ |
|
|
|
|
|
/** |
|
* Array for storing OLE instances that are accessed from |
|
* OLE_ChainedBlockStream::stream_open(). |
|
* @var array |
|
*/ |
|
$GLOBALS['_OLE_INSTANCES'] = array(); |
|
|
|
/** |
|
* OLE package base class. |
|
* |
|
* @author Xavier Noguer <xnoguer@php.net> |
|
* @author Christian Schmidt <schmidt@php.net> |
|
* @category PHPExcel |
|
* @package PHPExcel_Shared_OLE |
|
*/ |
|
class PHPExcel_Shared_OLE |
|
{ |
|
const OLE_PPS_TYPE_ROOT = 5; |
|
const OLE_PPS_TYPE_DIR = 1; |
|
const OLE_PPS_TYPE_FILE = 2; |
|
const OLE_DATA_SIZE_SMALL = 0x1000; |
|
const OLE_LONG_INT_SIZE = 4; |
|
const OLE_PPS_SIZE = 0x80; |
|
|
|
/** |
|
* The file handle for reading an OLE container |
|
* @var resource |
|
*/ |
|
public $_file_handle; |
|
|
|
/** |
|
* Array of PPS's found on the OLE container |
|
* @var array |
|
*/ |
|
public $_list = array(); |
|
|
|
/** |
|
* Root directory of OLE container |
|
* @var OLE_PPS_Root |
|
*/ |
|
public $root; |
|
|
|
/** |
|
* Big Block Allocation Table |
|
* @var array (blockId => nextBlockId) |
|
*/ |
|
public $bbat; |
|
|
|
/** |
|
* Short Block Allocation Table |
|
* @var array (blockId => nextBlockId) |
|
*/ |
|
public $sbat; |
|
|
|
/** |
|
* Size of big blocks. This is usually 512. |
|
* @var int number of octets per block. |
|
*/ |
|
public $bigBlockSize; |
|
|
|
/** |
|
* Size of small blocks. This is usually 64. |
|
* @var int number of octets per block |
|
*/ |
|
public $smallBlockSize; |
|
|
|
/** |
|
* Reads an OLE container from the contents of the file given. |
|
* |
|
* @acces public |
|
* @param string $file |
|
* @return mixed true on success, PEAR_Error on failure |
|
*/ |
|
public function read($file) |
|
{ |
|
$fh = fopen($file, "r"); |
|
if (!$fh) { |
|
throw new PHPExcel_Reader_Exception("Can't open file $file"); |
|
} |
|
$this->_file_handle = $fh; |
|
|
|
$signature = fread($fh, 8); |
|
if ("\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1" != $signature) { |
|
throw new PHPExcel_Reader_Exception("File doesn't seem to be an OLE container."); |
|
} |
|
fseek($fh, 28); |
|
if (fread($fh, 2) != "\xFE\xFF") { |
|
// This shouldn't be a problem in practice |
|
throw new PHPExcel_Reader_Exception("Only Little-Endian encoding is supported."); |
|
} |
|
// Size of blocks and short blocks in bytes |
|
$this->bigBlockSize = pow(2, self::_readInt2($fh)); |
|
$this->smallBlockSize = pow(2, self::_readInt2($fh)); |
|
|
|
// Skip UID, revision number and version number |
|
fseek($fh, 44); |
|
// Number of blocks in Big Block Allocation Table |
|
$bbatBlockCount = self::_readInt4($fh); |
|
|
|
// Root chain 1st block |
|
$directoryFirstBlockId = self::_readInt4($fh); |
|
|
|
// Skip unused bytes |
|
fseek($fh, 56); |
|
// Streams shorter than this are stored using small blocks |
|
$this->bigBlockThreshold = self::_readInt4($fh); |
|
// Block id of first sector in Short Block Allocation Table |
|
$sbatFirstBlockId = self::_readInt4($fh); |
|
// Number of blocks in Short Block Allocation Table |
|
$sbbatBlockCount = self::_readInt4($fh); |
|
// Block id of first sector in Master Block Allocation Table |
|
$mbatFirstBlockId = self::_readInt4($fh); |
|
// Number of blocks in Master Block Allocation Table |
|
$mbbatBlockCount = self::_readInt4($fh); |
|
$this->bbat = array(); |
|
|
|
// Remaining 4 * 109 bytes of current block is beginning of Master |
|
// Block Allocation Table |
|
$mbatBlocks = array(); |
|
for ($i = 0; $i < 109; ++$i) { |
|
$mbatBlocks[] = self::_readInt4($fh); |
|
} |
|
|
|
// Read rest of Master Block Allocation Table (if any is left) |
|
$pos = $this->_getBlockOffset($mbatFirstBlockId); |
|
for ($i = 0; $i < $mbbatBlockCount; ++$i) { |
|
fseek($fh, $pos); |
|
for ($j = 0; $j < $this->bigBlockSize / 4 - 1; ++$j) { |
|
$mbatBlocks[] = self::_readInt4($fh); |
|
} |
|
// Last block id in each block points to next block |
|
$pos = $this->_getBlockOffset(self::_readInt4($fh)); |
|
} |
|
|
|
// Read Big Block Allocation Table according to chain specified by |
|
// $mbatBlocks |
|
for ($i = 0; $i < $bbatBlockCount; ++$i) { |
|
$pos = $this->_getBlockOffset($mbatBlocks[$i]); |
|
fseek($fh, $pos); |
|
for ($j = 0; $j < $this->bigBlockSize / 4; ++$j) { |
|
$this->bbat[] = self::_readInt4($fh); |
|
} |
|
} |
|
|
|
// Read short block allocation table (SBAT) |
|
$this->sbat = array(); |
|
$shortBlockCount = $sbbatBlockCount * $this->bigBlockSize / 4; |
|
$sbatFh = $this->getStream($sbatFirstBlockId); |
|
for ($blockId = 0; $blockId < $shortBlockCount; ++$blockId) { |
|
$this->sbat[$blockId] = self::_readInt4($sbatFh); |
|
} |
|
fclose($sbatFh); |
|
|
|
$this->_readPpsWks($directoryFirstBlockId); |
|
|
|
return true; |
|
} |
|
|
|
/** |
|
* @param int block id |
|
* @param int byte offset from beginning of file |
|
* @access public |
|
*/ |
|
public function _getBlockOffset($blockId) |
|
{ |
|
return 512 + $blockId * $this->bigBlockSize; |
|
} |
|
|
|
/** |
|
* Returns a stream for use with fread() etc. External callers should |
|
* use PHPExcel_Shared_OLE_PPS_File::getStream(). |
|
* @param int|PPS block id or PPS |
|
* @return resource read-only stream |
|
*/ |
|
public function getStream($blockIdOrPps) |
|
{ |
|
static $isRegistered = false; |
|
if (!$isRegistered) { |
|
stream_wrapper_register('ole-chainedblockstream', 'PHPExcel_Shared_OLE_ChainedBlockStream'); |
|
$isRegistered = true; |
|
} |
|
|
|
// Store current instance in global array, so that it can be accessed |
|
// in OLE_ChainedBlockStream::stream_open(). |
|
// Object is removed from self::$instances in OLE_Stream::close(). |
|
$GLOBALS['_OLE_INSTANCES'][] = $this; |
|
$instanceId = end(array_keys($GLOBALS['_OLE_INSTANCES'])); |
|
|
|
$path = 'ole-chainedblockstream://oleInstanceId=' . $instanceId; |
|
if ($blockIdOrPps instanceof PHPExcel_Shared_OLE_PPS) { |
|
$path .= '&blockId=' . $blockIdOrPps->_StartBlock; |
|
$path .= '&size=' . $blockIdOrPps->Size; |
|
} else { |
|
$path .= '&blockId=' . $blockIdOrPps; |
|
} |
|
return fopen($path, 'r'); |
|
} |
|
|
|
/** |
|
* Reads a signed char. |
|
* @param resource file handle |
|
* @return int |
|
* @access public |
|
*/ |
|
private static function _readInt1($fh) |
|
{ |
|
list(, $tmp) = unpack("c", fread($fh, 1)); |
|
return $tmp; |
|
} |
|
|
|
/** |
|
* Reads an unsigned short (2 octets). |
|
* @param resource file handle |
|
* @return int |
|
* @access public |
|
*/ |
|
private static function _readInt2($fh) |
|
{ |
|
list(, $tmp) = unpack("v", fread($fh, 2)); |
|
return $tmp; |
|
} |
|
|
|
/** |
|
* Reads an unsigned long (4 octets). |
|
* @param resource file handle |
|
* @return int |
|
* @access public |
|
*/ |
|
private static function _readInt4($fh) |
|
{ |
|
list(, $tmp) = unpack("V", fread($fh, 4)); |
|
return $tmp; |
|
} |
|
|
|
/** |
|
* Gets information about all PPS's on the OLE container from the PPS WK's |
|
* creates an OLE_PPS object for each one. |
|
* |
|
* @access public |
|
* @param integer the block id of the first block |
|
* @return mixed true on success, PEAR_Error on failure |
|
*/ |
|
public function _readPpsWks($blockId) |
|
{ |
|
$fh = $this->getStream($blockId); |
|
for ($pos = 0;; $pos += 128) { |
|
fseek($fh, $pos, SEEK_SET); |
|
$nameUtf16 = fread($fh, 64); |
|
$nameLength = self::_readInt2($fh); |
|
$nameUtf16 = substr($nameUtf16, 0, $nameLength - 2); |
|
// Simple conversion from UTF-16LE to ISO-8859-1 |
|
$name = str_replace("\x00", "", $nameUtf16); |
|
$type = self::_readInt1($fh); |
|
switch ($type) { |
|
case self::OLE_PPS_TYPE_ROOT: |
|
$pps = new PHPExcel_Shared_OLE_PPS_Root(null, null, array()); |
|
$this->root = $pps; |
|
break; |
|
case self::OLE_PPS_TYPE_DIR: |
|
$pps = new PHPExcel_Shared_OLE_PPS(null, null, null, null, null, null, null, null, null, array()); |
|
break; |
|
case self::OLE_PPS_TYPE_FILE: |
|
$pps = new PHPExcel_Shared_OLE_PPS_File($name); |
|
break; |
|
default: |
|
continue 2; |
|
} |
|
fseek($fh, 1, SEEK_CUR); |
|
$pps->Type = $type; |
|
$pps->Name = $name; |
|
$pps->PrevPps = self::_readInt4($fh); |
|
$pps->NextPps = self::_readInt4($fh); |
|
$pps->DirPps = self::_readInt4($fh); |
|
fseek($fh, 20, SEEK_CUR); |
|
$pps->Time1st = self::OLE2LocalDate(fread($fh, 8)); |
|
$pps->Time2nd = self::OLE2LocalDate(fread($fh, 8)); |
|
$pps->_StartBlock = self::_readInt4($fh); |
|
$pps->Size = self::_readInt4($fh); |
|
$pps->No = count($this->_list); |
|
$this->_list[] = $pps; |
|
|
|
// check if the PPS tree (starting from root) is complete |
|
if (isset($this->root) && $this->_ppsTreeComplete($this->root->No)) { |
|
break; |
|
} |
|
} |
|
fclose($fh); |
|
|
|
// Initialize $pps->children on directories |
|
foreach ($this->_list as $pps) { |
|
if ($pps->Type == self::OLE_PPS_TYPE_DIR || $pps->Type == self::OLE_PPS_TYPE_ROOT) { |
|
$nos = array($pps->DirPps); |
|
$pps->children = array(); |
|
while ($nos) { |
|
$no = array_pop($nos); |
|
if ($no != -1) { |
|
$childPps = $this->_list[$no]; |
|
$nos[] = $childPps->PrevPps; |
|
$nos[] = $childPps->NextPps; |
|
$pps->children[] = $childPps; |
|
} |
|
} |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
/** |
|
* It checks whether the PPS tree is complete (all PPS's read) |
|
* starting with the given PPS (not necessarily root) |
|
* |
|
* @access public |
|
* @param integer $index The index of the PPS from which we are checking |
|
* @return boolean Whether the PPS tree for the given PPS is complete |
|
*/ |
|
public function _ppsTreeComplete($index) |
|
{ |
|
return isset($this->_list[$index]) && |
|
($pps = $this->_list[$index]) && |
|
($pps->PrevPps == -1 || |
|
$this->_ppsTreeComplete($pps->PrevPps)) && |
|
($pps->NextPps == -1 || |
|
$this->_ppsTreeComplete($pps->NextPps)) && |
|
($pps->DirPps == -1 || |
|
$this->_ppsTreeComplete($pps->DirPps)); |
|
} |
|
|
|
/** |
|
* Checks whether a PPS is a File PPS or not. |
|
* If there is no PPS for the index given, it will return false. |
|
* |
|
* @access public |
|
* @param integer $index The index for the PPS |
|
* @return bool true if it's a File PPS, false otherwise |
|
*/ |
|
public function isFile($index) |
|
{ |
|
if (isset($this->_list[$index])) { |
|
return ($this->_list[$index]->Type == self::OLE_PPS_TYPE_FILE); |
|
} |
|
return false; |
|
} |
|
|
|
/** |
|
* Checks whether a PPS is a Root PPS or not. |
|
* If there is no PPS for the index given, it will return false. |
|
* |
|
* @access public |
|
* @param integer $index The index for the PPS. |
|
* @return bool true if it's a Root PPS, false otherwise |
|
*/ |
|
public function isRoot($index) |
|
{ |
|
if (isset($this->_list[$index])) { |
|
return ($this->_list[$index]->Type == self::OLE_PPS_TYPE_ROOT); |
|
} |
|
return false; |
|
} |
|
|
|
/** |
|
* Gives the total number of PPS's found in the OLE container. |
|
* |
|
* @access public |
|
* @return integer The total number of PPS's found in the OLE container |
|
*/ |
|
public function ppsTotal() |
|
{ |
|
return count($this->_list); |
|
} |
|
|
|
/** |
|
* Gets data from a PPS |
|
* If there is no PPS for the index given, it will return an empty string. |
|
* |
|
* @access public |
|
* @param integer $index The index for the PPS |
|
* @param integer $position The position from which to start reading |
|
* (relative to the PPS) |
|
* @param integer $length The amount of bytes to read (at most) |
|
* @return string The binary string containing the data requested |
|
* @see OLE_PPS_File::getStream() |
|
*/ |
|
public function getData($index, $position, $length) |
|
{ |
|
// if position is not valid return empty string |
|
if (!isset($this->_list[$index]) || ($position >= $this->_list[$index]->Size) || ($position < 0)) { |
|
return ''; |
|
} |
|
$fh = $this->getStream($this->_list[$index]); |
|
$data = stream_get_contents($fh, $length, $position); |
|
fclose($fh); |
|
return $data; |
|
} |
|
|
|
/** |
|
* Gets the data length from a PPS |
|
* If there is no PPS for the index given, it will return 0. |
|
* |
|
* @access public |
|
* @param integer $index The index for the PPS |
|
* @return integer The amount of bytes in data the PPS has |
|
*/ |
|
public function getDataLength($index) |
|
{ |
|
if (isset($this->_list[$index])) { |
|
return $this->_list[$index]->Size; |
|
} |
|
return 0; |
|
} |
|
|
|
/** |
|
* Utility function to transform ASCII text to Unicode |
|
* |
|
* @access public |
|
* @static |
|
* @param string $ascii The ASCII string to transform |
|
* @return string The string in Unicode |
|
*/ |
|
public static function Asc2Ucs($ascii) |
|
{ |
|
$rawname = ''; |
|
for ($i = 0; $i < strlen($ascii); ++$i) { |
|
$rawname .= $ascii[$i] . "\x00"; |
|
} |
|
return $rawname; |
|
} |
|
|
|
/** |
|
* Utility function |
|
* Returns a string for the OLE container with the date given |
|
* |
|
* @access public |
|
* @static |
|
* @param integer $date A timestamp |
|
* @return string The string for the OLE container |
|
*/ |
|
public static function LocalDate2OLE($date = null) |
|
{ |
|
if (!isset($date)) { |
|
return "\x00\x00\x00\x00\x00\x00\x00\x00"; |
|
} |
|
|
|
// factor used for separating numbers into 4 bytes parts |
|
$factor = pow(2, 32); |
|
|
|
// days from 1-1-1601 until the beggining of UNIX era |
|
$days = 134774; |
|
// calculate seconds |
|
$big_date = $days*24*3600 + gmmktime(date("H", $date), date("i", $date), date("s", $date), date("m", $date), date("d", $date), date("Y", $date)); |
|
// multiply just to make MS happy |
|
$big_date *= 10000000; |
|
|
|
$high_part = floor($big_date / $factor); |
|
// lower 4 bytes |
|
$low_part = floor((($big_date / $factor) - $high_part) * $factor); |
|
|
|
// Make HEX string |
|
$res = ''; |
|
|
|
for ($i = 0; $i < 4; ++$i) { |
|
$hex = $low_part % 0x100; |
|
$res .= pack('c', $hex); |
|
$low_part /= 0x100; |
|
} |
|
for ($i = 0; $i < 4; ++$i) { |
|
$hex = $high_part % 0x100; |
|
$res .= pack('c', $hex); |
|
$high_part /= 0x100; |
|
} |
|
return $res; |
|
} |
|
|
|
/** |
|
* Returns a timestamp from an OLE container's date |
|
* |
|
* @access public |
|
* @static |
|
* @param integer $string A binary string with the encoded date |
|
* @return string The timestamp corresponding to the string |
|
*/ |
|
public static function OLE2LocalDate($string) |
|
{ |
|
if (strlen($string) != 8) { |
|
return new PEAR_Error("Expecting 8 byte string"); |
|
} |
|
|
|
// factor used for separating numbers into 4 bytes parts |
|
$factor = pow(2, 32); |
|
list(, $high_part) = unpack('V', substr($string, 4, 4)); |
|
list(, $low_part) = unpack('V', substr($string, 0, 4)); |
|
|
|
$big_date = ($high_part * $factor) + $low_part; |
|
// translate to seconds |
|
$big_date /= 10000000; |
|
|
|
// days from 1-1-1601 until the beggining of UNIX era |
|
$days = 134774; |
|
|
|
// translate to seconds from beggining of UNIX era |
|
$big_date -= $days * 24 * 3600; |
|
return floor($big_date); |
|
} |
|
}
|
|
|