Root.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  1. <?php
  2. namespace PhpOffice\PhpSpreadsheet\Shared\OLE\PPS;
  3. // vim: set expandtab tabstop=4 shiftwidth=4:
  4. // +----------------------------------------------------------------------+
  5. // | PHP Version 4 |
  6. // +----------------------------------------------------------------------+
  7. // | Copyright (c) 1997-2002 The PHP Group |
  8. // +----------------------------------------------------------------------+
  9. // | This source file is subject to version 2.02 of the PHP license, |
  10. // | that is bundled with this package in the file LICENSE, and is |
  11. // | available at through the world-wide-web at |
  12. // | http://www.php.net/license/2_02.txt. |
  13. // | If you did not receive a copy of the PHP license and are unable to |
  14. // | obtain it through the world-wide-web, please send a note to |
  15. // | license@php.net so we can mail you a copy immediately. |
  16. // +----------------------------------------------------------------------+
  17. // | Author: Xavier Noguer <xnoguer@php.net> |
  18. // | Based on OLE::Storage_Lite by Kawai, Takanori |
  19. // +----------------------------------------------------------------------+
  20. //
  21. use PhpOffice\PhpSpreadsheet\Shared\OLE;
  22. use PhpOffice\PhpSpreadsheet\Shared\OLE\PPS;
  23. use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException;
  24. /**
  25. * Class for creating Root PPS's for OLE containers.
  26. *
  27. * @author Xavier Noguer <xnoguer@php.net>
  28. *
  29. * @category PhpSpreadsheet
  30. */
  31. class Root extends PPS
  32. {
  33. /**
  34. * Directory for temporary files.
  35. *
  36. * @var string
  37. */
  38. protected $tempDirectory;
  39. /**
  40. * @var resource
  41. */
  42. private $fileHandle;
  43. /**
  44. * @var string
  45. */
  46. private $tempFilename;
  47. /**
  48. * @var int
  49. */
  50. private $smallBlockSize;
  51. /**
  52. * @var int
  53. */
  54. private $bigBlockSize;
  55. /**
  56. * @param int $time_1st A timestamp
  57. * @param int $time_2nd A timestamp
  58. * @param File[] $raChild
  59. */
  60. public function __construct($time_1st, $time_2nd, $raChild)
  61. {
  62. $this->tempDirectory = \PhpOffice\PhpSpreadsheet\Shared\File::sysGetTempDir();
  63. parent::__construct(null, OLE::ascToUcs('Root Entry'), OLE::OLE_PPS_TYPE_ROOT, null, null, null, $time_1st, $time_2nd, null, $raChild);
  64. }
  65. /**
  66. * Method for saving the whole OLE container (including files).
  67. * In fact, if called with an empty argument (or '-'), it saves to a
  68. * temporary file and then outputs it's contents to stdout.
  69. * If a resource pointer to a stream created by fopen() is passed
  70. * it will be used, but you have to close such stream by yourself.
  71. *
  72. * @param resource|string $filename the name of the file or stream where to save the OLE container
  73. *
  74. * @throws WriterException
  75. *
  76. * @return bool true on success
  77. */
  78. public function save($filename)
  79. {
  80. // Initial Setting for saving
  81. $this->bigBlockSize = pow(
  82. 2,
  83. (isset($this->bigBlockSize)) ? self::adjust2($this->bigBlockSize) : 9
  84. );
  85. $this->smallBlockSize = pow(
  86. 2,
  87. (isset($this->smallBlockSize)) ? self::adjust2($this->smallBlockSize) : 6
  88. );
  89. if (is_resource($filename)) {
  90. $this->fileHandle = $filename;
  91. } elseif ($filename == '-' || $filename == '') {
  92. if ($this->tempDirectory === null) {
  93. $this->tempDirectory = \PhpOffice\PhpSpreadsheet\Shared\File::sysGetTempDir();
  94. }
  95. $this->tempFilename = tempnam($this->tempDirectory, 'OLE_PPS_Root');
  96. $this->fileHandle = fopen($this->tempFilename, 'w+b');
  97. if ($this->fileHandle == false) {
  98. throw new WriterException("Can't create temporary file.");
  99. }
  100. } else {
  101. $this->fileHandle = fopen($filename, 'wb');
  102. }
  103. if ($this->fileHandle == false) {
  104. throw new WriterException("Can't open $filename. It may be in use or protected.");
  105. }
  106. // Make an array of PPS's (for Save)
  107. $aList = [];
  108. PPS::_savePpsSetPnt($aList, [$this]);
  109. // calculate values for header
  110. list($iSBDcnt, $iBBcnt, $iPPScnt) = $this->_calcSize($aList); //, $rhInfo);
  111. // Save Header
  112. $this->_saveHeader($iSBDcnt, $iBBcnt, $iPPScnt);
  113. // Make Small Data string (write SBD)
  114. $this->_data = $this->_makeSmallData($aList);
  115. // Write BB
  116. $this->_saveBigData($iSBDcnt, $aList);
  117. // Write PPS
  118. $this->_savePps($aList);
  119. // Write Big Block Depot and BDList and Adding Header informations
  120. $this->_saveBbd($iSBDcnt, $iBBcnt, $iPPScnt);
  121. if (!is_resource($filename)) {
  122. fclose($this->fileHandle);
  123. }
  124. return true;
  125. }
  126. /**
  127. * Calculate some numbers.
  128. *
  129. * @param array $raList Reference to an array of PPS's
  130. *
  131. * @return float[] The array of numbers
  132. */
  133. public function _calcSize(&$raList)
  134. {
  135. // Calculate Basic Setting
  136. list($iSBDcnt, $iBBcnt, $iPPScnt) = [0, 0, 0];
  137. $iSmallLen = 0;
  138. $iSBcnt = 0;
  139. $iCount = count($raList);
  140. for ($i = 0; $i < $iCount; ++$i) {
  141. if ($raList[$i]->Type == OLE::OLE_PPS_TYPE_FILE) {
  142. $raList[$i]->Size = $raList[$i]->getDataLen();
  143. if ($raList[$i]->Size < OLE::OLE_DATA_SIZE_SMALL) {
  144. $iSBcnt += floor($raList[$i]->Size / $this->smallBlockSize)
  145. + (($raList[$i]->Size % $this->smallBlockSize) ? 1 : 0);
  146. } else {
  147. $iBBcnt += (floor($raList[$i]->Size / $this->bigBlockSize) +
  148. (($raList[$i]->Size % $this->bigBlockSize) ? 1 : 0));
  149. }
  150. }
  151. }
  152. $iSmallLen = $iSBcnt * $this->smallBlockSize;
  153. $iSlCnt = floor($this->bigBlockSize / OLE::OLE_LONG_INT_SIZE);
  154. $iSBDcnt = floor($iSBcnt / $iSlCnt) + (($iSBcnt % $iSlCnt) ? 1 : 0);
  155. $iBBcnt += (floor($iSmallLen / $this->bigBlockSize) +
  156. (($iSmallLen % $this->bigBlockSize) ? 1 : 0));
  157. $iCnt = count($raList);
  158. $iBdCnt = $this->bigBlockSize / OLE::OLE_PPS_SIZE;
  159. $iPPScnt = (floor($iCnt / $iBdCnt) + (($iCnt % $iBdCnt) ? 1 : 0));
  160. return [$iSBDcnt, $iBBcnt, $iPPScnt];
  161. }
  162. /**
  163. * Helper function for caculating a magic value for block sizes.
  164. *
  165. * @param int $i2 The argument
  166. *
  167. * @see save()
  168. *
  169. * @return float
  170. */
  171. private static function adjust2($i2)
  172. {
  173. $iWk = log($i2) / log(2);
  174. return ($iWk > floor($iWk)) ? floor($iWk) + 1 : $iWk;
  175. }
  176. /**
  177. * Save OLE header.
  178. *
  179. * @param int $iSBDcnt
  180. * @param int $iBBcnt
  181. * @param int $iPPScnt
  182. */
  183. public function _saveHeader($iSBDcnt, $iBBcnt, $iPPScnt)
  184. {
  185. $FILE = $this->fileHandle;
  186. // Calculate Basic Setting
  187. $iBlCnt = $this->bigBlockSize / OLE::OLE_LONG_INT_SIZE;
  188. $i1stBdL = ($this->bigBlockSize - 0x4C) / OLE::OLE_LONG_INT_SIZE;
  189. $iBdExL = 0;
  190. $iAll = $iBBcnt + $iPPScnt + $iSBDcnt;
  191. $iAllW = $iAll;
  192. $iBdCntW = floor($iAllW / $iBlCnt) + (($iAllW % $iBlCnt) ? 1 : 0);
  193. $iBdCnt = floor(($iAll + $iBdCntW) / $iBlCnt) + ((($iAllW + $iBdCntW) % $iBlCnt) ? 1 : 0);
  194. // Calculate BD count
  195. if ($iBdCnt > $i1stBdL) {
  196. while (1) {
  197. ++$iBdExL;
  198. ++$iAllW;
  199. $iBdCntW = floor($iAllW / $iBlCnt) + (($iAllW % $iBlCnt) ? 1 : 0);
  200. $iBdCnt = floor(($iAllW + $iBdCntW) / $iBlCnt) + ((($iAllW + $iBdCntW) % $iBlCnt) ? 1 : 0);
  201. if ($iBdCnt <= ($iBdExL * $iBlCnt + $i1stBdL)) {
  202. break;
  203. }
  204. }
  205. }
  206. // Save Header
  207. fwrite(
  208. $FILE,
  209. "\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1"
  210. . "\x00\x00\x00\x00"
  211. . "\x00\x00\x00\x00"
  212. . "\x00\x00\x00\x00"
  213. . "\x00\x00\x00\x00"
  214. . pack('v', 0x3b)
  215. . pack('v', 0x03)
  216. . pack('v', -2)
  217. . pack('v', 9)
  218. . pack('v', 6)
  219. . pack('v', 0)
  220. . "\x00\x00\x00\x00"
  221. . "\x00\x00\x00\x00"
  222. . pack('V', $iBdCnt)
  223. . pack('V', $iBBcnt + $iSBDcnt) //ROOT START
  224. . pack('V', 0)
  225. . pack('V', 0x1000)
  226. . pack('V', $iSBDcnt ? 0 : -2) //Small Block Depot
  227. . pack('V', $iSBDcnt)
  228. );
  229. // Extra BDList Start, Count
  230. if ($iBdCnt < $i1stBdL) {
  231. fwrite(
  232. $FILE,
  233. pack('V', -2) // Extra BDList Start
  234. . pack('V', 0)// Extra BDList Count
  235. );
  236. } else {
  237. fwrite($FILE, pack('V', $iAll + $iBdCnt) . pack('V', $iBdExL));
  238. }
  239. // BDList
  240. for ($i = 0; $i < $i1stBdL && $i < $iBdCnt; ++$i) {
  241. fwrite($FILE, pack('V', $iAll + $i));
  242. }
  243. if ($i < $i1stBdL) {
  244. $jB = $i1stBdL - $i;
  245. for ($j = 0; $j < $jB; ++$j) {
  246. fwrite($FILE, (pack('V', -1)));
  247. }
  248. }
  249. }
  250. /**
  251. * Saving big data (PPS's with data bigger than \PhpOffice\PhpSpreadsheet\Shared\OLE::OLE_DATA_SIZE_SMALL).
  252. *
  253. * @param int $iStBlk
  254. * @param array &$raList Reference to array of PPS's
  255. */
  256. public function _saveBigData($iStBlk, &$raList)
  257. {
  258. $FILE = $this->fileHandle;
  259. // cycle through PPS's
  260. $iCount = count($raList);
  261. for ($i = 0; $i < $iCount; ++$i) {
  262. if ($raList[$i]->Type != OLE::OLE_PPS_TYPE_DIR) {
  263. $raList[$i]->Size = $raList[$i]->getDataLen();
  264. if (($raList[$i]->Size >= OLE::OLE_DATA_SIZE_SMALL) || (($raList[$i]->Type == OLE::OLE_PPS_TYPE_ROOT) && isset($raList[$i]->_data))) {
  265. fwrite($FILE, $raList[$i]->_data);
  266. if ($raList[$i]->Size % $this->bigBlockSize) {
  267. fwrite($FILE, str_repeat("\x00", $this->bigBlockSize - ($raList[$i]->Size % $this->bigBlockSize)));
  268. }
  269. // Set For PPS
  270. $raList[$i]->startBlock = $iStBlk;
  271. $iStBlk +=
  272. (floor($raList[$i]->Size / $this->bigBlockSize) +
  273. (($raList[$i]->Size % $this->bigBlockSize) ? 1 : 0));
  274. }
  275. }
  276. }
  277. }
  278. /**
  279. * get small data (PPS's with data smaller than \PhpOffice\PhpSpreadsheet\Shared\OLE::OLE_DATA_SIZE_SMALL).
  280. *
  281. * @param array &$raList Reference to array of PPS's
  282. *
  283. * @return string
  284. */
  285. public function _makeSmallData(&$raList)
  286. {
  287. $sRes = '';
  288. $FILE = $this->fileHandle;
  289. $iSmBlk = 0;
  290. $iCount = count($raList);
  291. for ($i = 0; $i < $iCount; ++$i) {
  292. // Make SBD, small data string
  293. if ($raList[$i]->Type == OLE::OLE_PPS_TYPE_FILE) {
  294. if ($raList[$i]->Size <= 0) {
  295. continue;
  296. }
  297. if ($raList[$i]->Size < OLE::OLE_DATA_SIZE_SMALL) {
  298. $iSmbCnt = floor($raList[$i]->Size / $this->smallBlockSize)
  299. + (($raList[$i]->Size % $this->smallBlockSize) ? 1 : 0);
  300. // Add to SBD
  301. $jB = $iSmbCnt - 1;
  302. for ($j = 0; $j < $jB; ++$j) {
  303. fwrite($FILE, pack('V', $j + $iSmBlk + 1));
  304. }
  305. fwrite($FILE, pack('V', -2));
  306. // Add to Data String(this will be written for RootEntry)
  307. $sRes .= $raList[$i]->_data;
  308. if ($raList[$i]->Size % $this->smallBlockSize) {
  309. $sRes .= str_repeat("\x00", $this->smallBlockSize - ($raList[$i]->Size % $this->smallBlockSize));
  310. }
  311. // Set for PPS
  312. $raList[$i]->startBlock = $iSmBlk;
  313. $iSmBlk += $iSmbCnt;
  314. }
  315. }
  316. }
  317. $iSbCnt = floor($this->bigBlockSize / OLE::OLE_LONG_INT_SIZE);
  318. if ($iSmBlk % $iSbCnt) {
  319. $iB = $iSbCnt - ($iSmBlk % $iSbCnt);
  320. for ($i = 0; $i < $iB; ++$i) {
  321. fwrite($FILE, pack('V', -1));
  322. }
  323. }
  324. return $sRes;
  325. }
  326. /**
  327. * Saves all the PPS's WKs.
  328. *
  329. * @param array $raList Reference to an array with all PPS's
  330. */
  331. public function _savePps(&$raList)
  332. {
  333. // Save each PPS WK
  334. $iC = count($raList);
  335. for ($i = 0; $i < $iC; ++$i) {
  336. fwrite($this->fileHandle, $raList[$i]->_getPpsWk());
  337. }
  338. // Adjust for Block
  339. $iCnt = count($raList);
  340. $iBCnt = $this->bigBlockSize / OLE::OLE_PPS_SIZE;
  341. if ($iCnt % $iBCnt) {
  342. fwrite($this->fileHandle, str_repeat("\x00", ($iBCnt - ($iCnt % $iBCnt)) * OLE::OLE_PPS_SIZE));
  343. }
  344. }
  345. /**
  346. * Saving Big Block Depot.
  347. *
  348. * @param int $iSbdSize
  349. * @param int $iBsize
  350. * @param int $iPpsCnt
  351. */
  352. public function _saveBbd($iSbdSize, $iBsize, $iPpsCnt)
  353. {
  354. $FILE = $this->fileHandle;
  355. // Calculate Basic Setting
  356. $iBbCnt = $this->bigBlockSize / OLE::OLE_LONG_INT_SIZE;
  357. $i1stBdL = ($this->bigBlockSize - 0x4C) / OLE::OLE_LONG_INT_SIZE;
  358. $iBdExL = 0;
  359. $iAll = $iBsize + $iPpsCnt + $iSbdSize;
  360. $iAllW = $iAll;
  361. $iBdCntW = floor($iAllW / $iBbCnt) + (($iAllW % $iBbCnt) ? 1 : 0);
  362. $iBdCnt = floor(($iAll + $iBdCntW) / $iBbCnt) + ((($iAllW + $iBdCntW) % $iBbCnt) ? 1 : 0);
  363. // Calculate BD count
  364. if ($iBdCnt > $i1stBdL) {
  365. while (1) {
  366. ++$iBdExL;
  367. ++$iAllW;
  368. $iBdCntW = floor($iAllW / $iBbCnt) + (($iAllW % $iBbCnt) ? 1 : 0);
  369. $iBdCnt = floor(($iAllW + $iBdCntW) / $iBbCnt) + ((($iAllW + $iBdCntW) % $iBbCnt) ? 1 : 0);
  370. if ($iBdCnt <= ($iBdExL * $iBbCnt + $i1stBdL)) {
  371. break;
  372. }
  373. }
  374. }
  375. // Making BD
  376. // Set for SBD
  377. if ($iSbdSize > 0) {
  378. for ($i = 0; $i < ($iSbdSize - 1); ++$i) {
  379. fwrite($FILE, pack('V', $i + 1));
  380. }
  381. fwrite($FILE, pack('V', -2));
  382. }
  383. // Set for B
  384. for ($i = 0; $i < ($iBsize - 1); ++$i) {
  385. fwrite($FILE, pack('V', $i + $iSbdSize + 1));
  386. }
  387. fwrite($FILE, pack('V', -2));
  388. // Set for PPS
  389. for ($i = 0; $i < ($iPpsCnt - 1); ++$i) {
  390. fwrite($FILE, pack('V', $i + $iSbdSize + $iBsize + 1));
  391. }
  392. fwrite($FILE, pack('V', -2));
  393. // Set for BBD itself ( 0xFFFFFFFD : BBD)
  394. for ($i = 0; $i < $iBdCnt; ++$i) {
  395. fwrite($FILE, pack('V', 0xFFFFFFFD));
  396. }
  397. // Set for ExtraBDList
  398. for ($i = 0; $i < $iBdExL; ++$i) {
  399. fwrite($FILE, pack('V', 0xFFFFFFFC));
  400. }
  401. // Adjust for Block
  402. if (($iAllW + $iBdCnt) % $iBbCnt) {
  403. $iBlock = ($iBbCnt - (($iAllW + $iBdCnt) % $iBbCnt));
  404. for ($i = 0; $i < $iBlock; ++$i) {
  405. fwrite($FILE, pack('V', -1));
  406. }
  407. }
  408. // Extra BDList
  409. if ($iBdCnt > $i1stBdL) {
  410. $iN = 0;
  411. $iNb = 0;
  412. for ($i = $i1stBdL; $i < $iBdCnt; $i++, ++$iN) {
  413. if ($iN >= ($iBbCnt - 1)) {
  414. $iN = 0;
  415. ++$iNb;
  416. fwrite($FILE, pack('V', $iAll + $iBdCnt + $iNb));
  417. }
  418. fwrite($FILE, pack('V', $iBsize + $iSbdSize + $iPpsCnt + $i));
  419. }
  420. if (($iBdCnt - $i1stBdL) % ($iBbCnt - 1)) {
  421. $iB = ($iBbCnt - 1) - (($iBdCnt - $i1stBdL) % ($iBbCnt - 1));
  422. for ($i = 0; $i < $iB; ++$i) {
  423. fwrite($FILE, pack('V', -1));
  424. }
  425. }
  426. fwrite($FILE, pack('V', -2));
  427. }
  428. }
  429. }