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 int $time_1st A timestamp
50     * @param 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