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