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