Completed
Push — master ( 4b5c92...896769 )
by Adrien
16:06 queued 09:43
created

Xls::writeDocumentSummaryInformation()   C

Complexity

Conditions 10
Paths 18

Size

Total Lines 208
Code Lines 122

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 88
CRAP Score 10.0035

Importance

Changes 0
Metric Value
cc 10
eloc 122
nc 18
nop 0
dl 0
loc 208
ccs 88
cts 91
cp 0.967
crap 10.0035
rs 6.1333
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Writer;
4
5
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
6
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
7
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
8
use PhpOffice\PhpSpreadsheet\RichText\RichText;
9
use PhpOffice\PhpSpreadsheet\RichText\Run;
10
use PhpOffice\PhpSpreadsheet\Shared\Drawing as SharedDrawing;
11
use PhpOffice\PhpSpreadsheet\Shared\Escher;
12
use PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer;
13
use PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer;
14
use PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer\SpContainer;
15
use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer;
16
use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer;
17
use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE;
18
use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE\Blip;
19
use PhpOffice\PhpSpreadsheet\Shared\OLE;
20
use PhpOffice\PhpSpreadsheet\Shared\OLE\PPS\File;
21
use PhpOffice\PhpSpreadsheet\Shared\OLE\PPS\Root;
22
use PhpOffice\PhpSpreadsheet\Spreadsheet;
23
use PhpOffice\PhpSpreadsheet\Worksheet\BaseDrawing;
24
use PhpOffice\PhpSpreadsheet\Worksheet\Drawing;
25
use PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing;
26
use RuntimeException;
27
28
class Xls extends BaseWriter
29
{
30
    /**
31
     * PhpSpreadsheet object.
32
     *
33
     * @var Spreadsheet
34
     */
35
    private $spreadsheet;
36
37
    /**
38
     * Total number of shared strings in workbook.
39
     *
40
     * @var int
41
     */
42
    private $strTotal = 0;
43
44
    /**
45
     * Number of unique shared strings in workbook.
46
     *
47
     * @var int
48
     */
49
    private $strUnique = 0;
50
51
    /**
52
     * Array of unique shared strings in workbook.
53
     *
54
     * @var array
55
     */
56
    private $strTable = [];
57
58
    /**
59
     * Color cache. Mapping between RGB value and color index.
60
     *
61
     * @var array
62
     */
63
    private $colors;
64
65
    /**
66
     * Formula parser.
67
     *
68
     * @var \PhpOffice\PhpSpreadsheet\Writer\Xls\Parser
69
     */
70
    private $parser;
71
72
    /**
73
     * Identifier clusters for drawings. Used in MSODRAWINGGROUP record.
74
     *
75
     * @var array
76
     */
77
    private $IDCLs;
78
79
    /**
80
     * Basic OLE object summary information.
81
     *
82
     * @var array
83
     */
84
    private $summaryInformation;
85
86
    /**
87
     * Extended OLE object document summary information.
88
     *
89
     * @var array
90
     */
91
    private $documentSummaryInformation;
92
93
    /**
94
     * @var \PhpOffice\PhpSpreadsheet\Writer\Xls\Workbook
95
     */
96
    private $writerWorkbook;
97
98
    /**
99
     * @var \PhpOffice\PhpSpreadsheet\Writer\Xls\Worksheet[]
100
     */
101
    private $writerWorksheets;
102
103
    /**
104
     * Create a new Xls Writer.
105
     *
106
     * @param Spreadsheet $spreadsheet PhpSpreadsheet object
107
     */
108 52
    public function __construct(Spreadsheet $spreadsheet)
109
    {
110 52
        $this->spreadsheet = $spreadsheet;
111
112 52
        $this->parser = new Xls\Parser();
113 52
    }
114
115
    /**
116
     * Save Spreadsheet to file.
117
     *
118
     * @param resource|string $pFilename
119
     *
120
     * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
121
     */
122 51
    public function save($pFilename)
123
    {
124
        // garbage collect
125 51
        $this->spreadsheet->garbageCollect();
126
127 51
        $saveDebugLog = Calculation::getInstance($this->spreadsheet)->getDebugLog()->getWriteDebugLog();
128 51
        Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog(false);
129 51
        $saveDateReturnType = Functions::getReturnDateType();
130 51
        Functions::setReturnDateType(Functions::RETURNDATE_EXCEL);
131
132
        // initialize colors array
133 51
        $this->colors = [];
134
135
        // Initialise workbook writer
136 51
        $this->writerWorkbook = new Xls\Workbook($this->spreadsheet, $this->strTotal, $this->strUnique, $this->strTable, $this->colors, $this->parser);
137
138
        // Initialise worksheet writers
139 51
        $countSheets = $this->spreadsheet->getSheetCount();
140 51
        for ($i = 0; $i < $countSheets; ++$i) {
141 51
            $this->writerWorksheets[$i] = new Xls\Worksheet($this->strTotal, $this->strUnique, $this->strTable, $this->colors, $this->parser, $this->preCalculateFormulas, $this->spreadsheet->getSheet($i));
142
        }
143
144
        // build Escher objects. Escher objects for workbooks needs to be build before Escher object for workbook.
145 51
        $this->buildWorksheetEschers();
146 51
        $this->buildWorkbookEscher();
147
148
        // add 15 identical cell style Xfs
149
        // for now, we use the first cellXf instead of cellStyleXf
150 51
        $cellXfCollection = $this->spreadsheet->getCellXfCollection();
151 51
        for ($i = 0; $i < 15; ++$i) {
152 51
            $this->writerWorkbook->addXfWriter($cellXfCollection[0], true);
153
        }
154
155
        // add all the cell Xfs
156 51
        foreach ($this->spreadsheet->getCellXfCollection() as $style) {
157 51
            $this->writerWorkbook->addXfWriter($style, false);
158
        }
159
160
        // add fonts from rich text eleemnts
161 51
        for ($i = 0; $i < $countSheets; ++$i) {
162 51
            foreach ($this->writerWorksheets[$i]->phpSheet->getCoordinates() as $coordinate) {
163 48
                $cell = $this->writerWorksheets[$i]->phpSheet->getCell($coordinate);
164 48
                $cVal = $cell->getValue();
165 48
                if ($cVal instanceof RichText) {
166 9
                    $elements = $cVal->getRichTextElements();
167 9
                    foreach ($elements as $element) {
168 9
                        if ($element instanceof Run) {
169 9
                            $font = $element->getFont();
170 9
                            $this->writerWorksheets[$i]->fontHashIndex[$font->getHashCode()] = $this->writerWorkbook->addFont($font);
171
                        }
172
                    }
173
                }
174
            }
175
        }
176
177
        // initialize OLE file
178 51
        $workbookStreamName = 'Workbook';
179 51
        $OLE = new File(OLE::ascToUcs($workbookStreamName));
180
181
        // Write the worksheet streams before the global workbook stream,
182
        // because the byte sizes of these are needed in the global workbook stream
183 51
        $worksheetSizes = [];
184 51
        for ($i = 0; $i < $countSheets; ++$i) {
185 51
            $this->writerWorksheets[$i]->close();
186 51
            $worksheetSizes[] = $this->writerWorksheets[$i]->_datasize;
187
        }
188
189
        // add binary data for global workbook stream
190 51
        $OLE->append($this->writerWorkbook->writeWorkbook($worksheetSizes));
191
192
        // add binary data for sheet streams
193 51
        for ($i = 0; $i < $countSheets; ++$i) {
194 51
            $OLE->append($this->writerWorksheets[$i]->getData());
195
        }
196
197 51
        $this->documentSummaryInformation = $this->writeDocumentSummaryInformation();
198
        // initialize OLE Document Summary Information
199 51
        if (isset($this->documentSummaryInformation) && !empty($this->documentSummaryInformation)) {
200 51
            $OLE_DocumentSummaryInformation = new File(OLE::ascToUcs(chr(5) . 'DocumentSummaryInformation'));
201 51
            $OLE_DocumentSummaryInformation->append($this->documentSummaryInformation);
202
        }
203
204 51
        $this->summaryInformation = $this->writeSummaryInformation();
205
        // initialize OLE Summary Information
206 51
        if (isset($this->summaryInformation) && !empty($this->summaryInformation)) {
207 51
            $OLE_SummaryInformation = new File(OLE::ascToUcs(chr(5) . 'SummaryInformation'));
208 51
            $OLE_SummaryInformation->append($this->summaryInformation);
209
        }
210
211
        // define OLE Parts
212 51
        $arrRootData = [$OLE];
213
        // initialize OLE Properties file
214 51
        if (isset($OLE_SummaryInformation)) {
215 51
            $arrRootData[] = $OLE_SummaryInformation;
216
        }
217
        // initialize OLE Extended Properties file
218 51
        if (isset($OLE_DocumentSummaryInformation)) {
219 51
            $arrRootData[] = $OLE_DocumentSummaryInformation;
220
        }
221
222 51
        $root = new Root(time(), time(), $arrRootData);
223
        // save the OLE file
224 51
        $this->openFileHandle($pFilename);
225 51
        $root->save($this->fileHandle);
226 51
        $this->maybeCloseFileHandle();
227
228 51
        Functions::setReturnDateType($saveDateReturnType);
229 51
        Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog($saveDebugLog);
230 51
    }
231
232
    /**
233
     * Build the Worksheet Escher objects.
234
     */
235 51
    private function buildWorksheetEschers()
236
    {
237
        // 1-based index to BstoreContainer
238 51
        $blipIndex = 0;
239 51
        $lastReducedSpId = 0;
240 51
        $lastSpId = 0;
241
242 51
        foreach ($this->spreadsheet->getAllsheets() as $sheet) {
243
            // sheet index
244 51
            $sheetIndex = $sheet->getParent()->getIndex($sheet);
245
246 51
            $escher = null;
247
248
            // check if there are any shapes for this sheet
249 51
            $filterRange = $sheet->getAutoFilter()->getRange();
250 51
            if (count($sheet->getDrawingCollection()) == 0 && empty($filterRange)) {
251 42
                continue;
252
            }
253
254
            // create intermediate Escher object
255 10
            $escher = new Escher();
256
257
            // dgContainer
258 10
            $dgContainer = new DgContainer();
259
260
            // set the drawing index (we use sheet index + 1)
261 10
            $dgId = $sheet->getParent()->getIndex($sheet) + 1;
262 10
            $dgContainer->setDgId($dgId);
263 10
            $escher->setDgContainer($dgContainer);
264
265
            // spgrContainer
266 10
            $spgrContainer = new SpgrContainer();
267 10
            $dgContainer->setSpgrContainer($spgrContainer);
268
269
            // add one shape which is the group shape
270 10
            $spContainer = new SpContainer();
271 10
            $spContainer->setSpgr(true);
272 10
            $spContainer->setSpType(0);
273 10
            $spContainer->setSpId(($sheet->getParent()->getIndex($sheet) + 1) << 10);
274 10
            $spgrContainer->addChild($spContainer);
275
276
            // add the shapes
277
278 10
            $countShapes[$sheetIndex] = 0; // count number of shapes (minus group shape), in sheet
279
280 10
            foreach ($sheet->getDrawingCollection() as $drawing) {
281 7
                ++$blipIndex;
282
283 7
                ++$countShapes[$sheetIndex];
284
285
                // add the shape
286 7
                $spContainer = new SpContainer();
287
288
                // set the shape type
289 7
                $spContainer->setSpType(0x004B);
290
                // set the shape flag
291 7
                $spContainer->setSpFlag(0x02);
292
293
                // set the shape index (we combine 1-based sheet index and $countShapes to create unique shape index)
294 7
                $reducedSpId = $countShapes[$sheetIndex];
295 7
                $spId = $reducedSpId | ($sheet->getParent()->getIndex($sheet) + 1) << 10;
296 7
                $spContainer->setSpId($spId);
297
298
                // keep track of last reducedSpId
299 7
                $lastReducedSpId = $reducedSpId;
300
301
                // keep track of last spId
302 7
                $lastSpId = $spId;
303
304
                // set the BLIP index
305 7
                $spContainer->setOPT(0x4104, $blipIndex);
306
307
                // set coordinates and offsets, client anchor
308 7
                $coordinates = $drawing->getCoordinates();
309 7
                $offsetX = $drawing->getOffsetX();
310 7
                $offsetY = $drawing->getOffsetY();
311 7
                $width = $drawing->getWidth();
312 7
                $height = $drawing->getHeight();
313
314 7
                $twoAnchor = \PhpOffice\PhpSpreadsheet\Shared\Xls::oneAnchor2twoAnchor($sheet, $coordinates, $offsetX, $offsetY, $width, $height);
315
316 7
                $spContainer->setStartCoordinates($twoAnchor['startCoordinates']);
317 7
                $spContainer->setStartOffsetX($twoAnchor['startOffsetX']);
318 7
                $spContainer->setStartOffsetY($twoAnchor['startOffsetY']);
319 7
                $spContainer->setEndCoordinates($twoAnchor['endCoordinates']);
320 7
                $spContainer->setEndOffsetX($twoAnchor['endOffsetX']);
321 7
                $spContainer->setEndOffsetY($twoAnchor['endOffsetY']);
322
323 7
                $spgrContainer->addChild($spContainer);
324
            }
325
326
            // AutoFilters
327 10
            if (!empty($filterRange)) {
328 3
                $rangeBounds = Coordinate::rangeBoundaries($filterRange);
329 3
                $iNumColStart = $rangeBounds[0][0];
330 3
                $iNumColEnd = $rangeBounds[1][0];
331
332 3
                $iInc = $iNumColStart;
333 3
                while ($iInc <= $iNumColEnd) {
334 3
                    ++$countShapes[$sheetIndex];
335
336
                    // create an Drawing Object for the dropdown
337 3
                    $oDrawing = new BaseDrawing();
338
                    // get the coordinates of drawing
339 3
                    $cDrawing = Coordinate::stringFromColumnIndex($iInc) . $rangeBounds[0][1];
340 3
                    $oDrawing->setCoordinates($cDrawing);
341 3
                    $oDrawing->setWorksheet($sheet);
342
343
                    // add the shape
344 3
                    $spContainer = new SpContainer();
345
                    // set the shape type
346 3
                    $spContainer->setSpType(0x00C9);
347
                    // set the shape flag
348 3
                    $spContainer->setSpFlag(0x01);
349
350
                    // set the shape index (we combine 1-based sheet index and $countShapes to create unique shape index)
351 3
                    $reducedSpId = $countShapes[$sheetIndex];
352 3
                    $spId = $reducedSpId | ($sheet->getParent()->getIndex($sheet) + 1) << 10;
353 3
                    $spContainer->setSpId($spId);
354
355
                    // keep track of last reducedSpId
356 3
                    $lastReducedSpId = $reducedSpId;
357
358
                    // keep track of last spId
359 3
                    $lastSpId = $spId;
360
361 3
                    $spContainer->setOPT(0x007F, 0x01040104); // Protection -> fLockAgainstGrouping
362 3
                    $spContainer->setOPT(0x00BF, 0x00080008); // Text -> fFitTextToShape
363 3
                    $spContainer->setOPT(0x01BF, 0x00010000); // Fill Style -> fNoFillHitTest
364 3
                    $spContainer->setOPT(0x01FF, 0x00080000); // Line Style -> fNoLineDrawDash
365 3
                    $spContainer->setOPT(0x03BF, 0x000A0000); // Group Shape -> fPrint
366
367
                    // set coordinates and offsets, client anchor
368 3
                    $endCoordinates = Coordinate::stringFromColumnIndex($iInc);
369 3
                    $endCoordinates .= $rangeBounds[0][1] + 1;
370
371 3
                    $spContainer->setStartCoordinates($cDrawing);
372 3
                    $spContainer->setStartOffsetX(0);
373 3
                    $spContainer->setStartOffsetY(0);
374 3
                    $spContainer->setEndCoordinates($endCoordinates);
375 3
                    $spContainer->setEndOffsetX(0);
376 3
                    $spContainer->setEndOffsetY(0);
377
378 3
                    $spgrContainer->addChild($spContainer);
379 3
                    ++$iInc;
380
                }
381
            }
382
383
            // identifier clusters, used for workbook Escher object
384 10
            $this->IDCLs[$dgId] = $lastReducedSpId;
385
386
            // set last shape index
387 10
            $dgContainer->setLastSpId($lastSpId);
388
389
            // set the Escher object
390 10
            $this->writerWorksheets[$sheetIndex]->setEscher($escher);
391
        }
392 51
    }
393
394
    /**
395
     * Build the Escher object corresponding to the MSODRAWINGGROUP record.
396
     */
397 51
    private function buildWorkbookEscher()
398
    {
399 51
        $escher = null;
400
401
        // any drawings in this workbook?
402 51
        $found = false;
403 51
        foreach ($this->spreadsheet->getAllSheets() as $sheet) {
404 51
            if (count($sheet->getDrawingCollection()) > 0) {
405 10
                $found = true;
406
407 10
                break;
408
            }
409
        }
410
411
        // nothing to do if there are no drawings
412 51
        if (!$found) {
413 41
            return;
414
        }
415
416
        // if we reach here, then there are drawings in the workbook
417 10
        $escher = new Escher();
418
419
        // dggContainer
420 10
        $dggContainer = new DggContainer();
421 10
        $escher->setDggContainer($dggContainer);
422
423
        // set IDCLs (identifier clusters)
424 10
        $dggContainer->setIDCLs($this->IDCLs);
425
426
        // this loop is for determining maximum shape identifier of all drawing
427 10
        $spIdMax = 0;
428 10
        $totalCountShapes = 0;
429 10
        $countDrawings = 0;
430
431 10
        foreach ($this->spreadsheet->getAllsheets() as $sheet) {
432 10
            $sheetCountShapes = 0; // count number of shapes (minus group shape), in sheet
433
434 10
            if (count($sheet->getDrawingCollection()) > 0) {
435 10
                ++$countDrawings;
436
437 10
                foreach ($sheet->getDrawingCollection() as $drawing) {
438 10
                    ++$sheetCountShapes;
439 10
                    ++$totalCountShapes;
440
441 10
                    $spId = $sheetCountShapes | ($this->spreadsheet->getIndex($sheet) + 1) << 10;
442 10
                    $spIdMax = max($spId, $spIdMax);
443
                }
444
            }
445
        }
446
447 10
        $dggContainer->setSpIdMax($spIdMax + 1);
448 10
        $dggContainer->setCDgSaved($countDrawings);
449 10
        $dggContainer->setCSpSaved($totalCountShapes + $countDrawings); // total number of shapes incl. one group shapes per drawing
450
451
        // bstoreContainer
452 10
        $bstoreContainer = new BstoreContainer();
453 10
        $dggContainer->setBstoreContainer($bstoreContainer);
454
455
        // the BSE's (all the images)
456 10
        foreach ($this->spreadsheet->getAllsheets() as $sheet) {
457 10
            foreach ($sheet->getDrawingCollection() as $drawing) {
458 10
                if (!extension_loaded('gd')) {
459
                    throw new RuntimeException('Saving images in xls requires gd extension');
460
                }
461 10
                if ($drawing instanceof Drawing) {
462 5
                    $filename = $drawing->getPath();
463
464 5
                    [$imagesx, $imagesy, $imageFormat] = getimagesize($filename);
465
466
                    switch ($imageFormat) {
467 5
                        case 1: // GIF, not supported by BIFF8, we convert to PNG
468
                            $blipType = BSE::BLIPTYPE_PNG;
469
                            ob_start();
470
                            imagepng(imagecreatefromgif($filename));
471
                            $blipData = ob_get_contents();
472
                            ob_end_clean();
473
474
                            break;
475 5
                        case 2: // JPEG
476 5
                            $blipType = BSE::BLIPTYPE_JPEG;
477 5
                            $blipData = file_get_contents($filename);
478
479 5
                            break;
480 5
                        case 3: // PNG
481 5
                            $blipType = BSE::BLIPTYPE_PNG;
482 5
                            $blipData = file_get_contents($filename);
483
484 5
                            break;
485
                        case 6: // Windows DIB (BMP), we convert to PNG
486
                            $blipType = BSE::BLIPTYPE_PNG;
487
                            ob_start();
488
                            imagepng(SharedDrawing::imagecreatefrombmp($filename));
489
                            $blipData = ob_get_contents();
490
                            ob_end_clean();
491
492
                            break;
493
                        default:
494
                            continue 2;
495
                    }
496
497 5
                    $blip = new Blip();
498 5
                    $blip->setData($blipData);
499
500 5
                    $BSE = new BSE();
501 5
                    $BSE->setBlipType($blipType);
502 5
                    $BSE->setBlip($blip);
503
504 5
                    $bstoreContainer->addBSE($BSE);
505 6
                } elseif ($drawing instanceof MemoryDrawing) {
506 3
                    switch ($drawing->getRenderingFunction()) {
507 1
                        case MemoryDrawing::RENDERING_JPEG:
508 3
                            $blipType = BSE::BLIPTYPE_JPEG;
509 3
                            $renderingFunction = 'imagejpeg';
510
511 3
                            break;
512 1
                        case MemoryDrawing::RENDERING_GIF:
513 1
                        case MemoryDrawing::RENDERING_PNG:
514
                        case MemoryDrawing::RENDERING_DEFAULT:
515 3
                            $blipType = BSE::BLIPTYPE_PNG;
516 3
                            $renderingFunction = 'imagepng';
517
518 3
                            break;
519
                    }
520
521 3
                    ob_start();
522 3
                    call_user_func($renderingFunction, $drawing->getImageResource());
523 3
                    $blipData = ob_get_contents();
524 3
                    ob_end_clean();
525
526 3
                    $blip = new Blip();
527 3
                    $blip->setData($blipData);
528
529 3
                    $BSE = new BSE();
530 3
                    $BSE->setBlipType($blipType);
531 3
                    $BSE->setBlip($blip);
532
533 3
                    $bstoreContainer->addBSE($BSE);
534
                }
535
            }
536
        }
537
538
        // Set the Escher object
539 10
        $this->writerWorkbook->setEscher($escher);
540 10
    }
541
542
    /**
543
     * Build the OLE Part for DocumentSummary Information.
544
     *
545
     * @return string
546
     */
547 51
    private function writeDocumentSummaryInformation()
548
    {
549
        // offset: 0; size: 2; must be 0xFE 0xFF (UTF-16 LE byte order mark)
550 51
        $data = pack('v', 0xFFFE);
551
        // offset: 2; size: 2;
552 51
        $data .= pack('v', 0x0000);
553
        // offset: 4; size: 2; OS version
554 51
        $data .= pack('v', 0x0106);
555
        // offset: 6; size: 2; OS indicator
556 51
        $data .= pack('v', 0x0002);
557
        // offset: 8; size: 16
558 51
        $data .= pack('VVVV', 0x00, 0x00, 0x00, 0x00);
559
        // offset: 24; size: 4; section count
560 51
        $data .= pack('V', 0x0001);
561
562
        // offset: 28; size: 16; first section's class id: 02 d5 cd d5 9c 2e 1b 10 93 97 08 00 2b 2c f9 ae
563 51
        $data .= pack('vvvvvvvv', 0xD502, 0xD5CD, 0x2E9C, 0x101B, 0x9793, 0x0008, 0x2C2B, 0xAEF9);
564
        // offset: 44; size: 4; offset of the start
565 51
        $data .= pack('V', 0x30);
566
567
        // SECTION
568 51
        $dataSection = [];
569 51
        $dataSection_NumProps = 0;
570 51
        $dataSection_Summary = '';
571 51
        $dataSection_Content = '';
572
573
        // GKPIDDSI_CODEPAGE: CodePage
574 51
        $dataSection[] = [
575
            'summary' => ['pack' => 'V', 'data' => 0x01],
576
            'offset' => ['pack' => 'V'],
577
            'type' => ['pack' => 'V', 'data' => 0x02], // 2 byte signed integer
578
            'data' => ['data' => 1252],
579
        ];
580 51
        ++$dataSection_NumProps;
581
582
        // GKPIDDSI_CATEGORY : Category
583 51
        if ($this->spreadsheet->getProperties()->getCategory()) {
584 31
            $dataProp = $this->spreadsheet->getProperties()->getCategory();
585 31
            $dataSection[] = [
586 31
                'summary' => ['pack' => 'V', 'data' => 0x02],
587
                'offset' => ['pack' => 'V'],
588
                'type' => ['pack' => 'V', 'data' => 0x1E],
589 31
                'data' => ['data' => $dataProp, 'length' => strlen($dataProp)],
590
            ];
591 31
            ++$dataSection_NumProps;
592
        }
593
        // GKPIDDSI_VERSION :Version of the application that wrote the property storage
594 51
        $dataSection[] = [
595
            'summary' => ['pack' => 'V', 'data' => 0x17],
596
            'offset' => ['pack' => 'V'],
597
            'type' => ['pack' => 'V', 'data' => 0x03],
598
            'data' => ['pack' => 'V', 'data' => 0x000C0000],
599
        ];
600 51
        ++$dataSection_NumProps;
601
        // GKPIDDSI_SCALE : FALSE
602 51
        $dataSection[] = [
603
            'summary' => ['pack' => 'V', 'data' => 0x0B],
604
            'offset' => ['pack' => 'V'],
605
            'type' => ['pack' => 'V', 'data' => 0x0B],
606
            'data' => ['data' => false],
607
        ];
608 51
        ++$dataSection_NumProps;
609
        // GKPIDDSI_LINKSDIRTY : True if any of the values for the linked properties have changed outside of the application
610 51
        $dataSection[] = [
611
            'summary' => ['pack' => 'V', 'data' => 0x10],
612
            'offset' => ['pack' => 'V'],
613
            'type' => ['pack' => 'V', 'data' => 0x0B],
614
            'data' => ['data' => false],
615
        ];
616 51
        ++$dataSection_NumProps;
617
        // GKPIDDSI_SHAREDOC : FALSE
618 51
        $dataSection[] = [
619
            'summary' => ['pack' => 'V', 'data' => 0x13],
620
            'offset' => ['pack' => 'V'],
621
            'type' => ['pack' => 'V', 'data' => 0x0B],
622
            'data' => ['data' => false],
623
        ];
624 51
        ++$dataSection_NumProps;
625
        // GKPIDDSI_HYPERLINKSCHANGED : True if any of the values for the _PID_LINKS (hyperlink text) have changed outside of the application
626 51
        $dataSection[] = [
627
            'summary' => ['pack' => 'V', 'data' => 0x16],
628
            'offset' => ['pack' => 'V'],
629
            'type' => ['pack' => 'V', 'data' => 0x0B],
630
            'data' => ['data' => false],
631
        ];
632 51
        ++$dataSection_NumProps;
633
634
        // GKPIDDSI_DOCSPARTS
635
        // MS-OSHARED p75 (2.3.3.2.2.1)
636
        // Structure is VtVecUnalignedLpstrValue (2.3.3.1.9)
637
        // cElements
638 51
        $dataProp = pack('v', 0x0001);
639 51
        $dataProp .= pack('v', 0x0000);
640
        // array of UnalignedLpstr
641
        // cch
642 51
        $dataProp .= pack('v', 0x000A);
643 51
        $dataProp .= pack('v', 0x0000);
644
        // value
645 51
        $dataProp .= 'Worksheet' . chr(0);
646
647 51
        $dataSection[] = [
648 51
            'summary' => ['pack' => 'V', 'data' => 0x0D],
649
            'offset' => ['pack' => 'V'],
650
            'type' => ['pack' => 'V', 'data' => 0x101E],
651 51
            'data' => ['data' => $dataProp, 'length' => strlen($dataProp)],
652
        ];
653 51
        ++$dataSection_NumProps;
654
655
        // GKPIDDSI_HEADINGPAIR
656
        // VtVecHeadingPairValue
657
        // cElements
658 51
        $dataProp = pack('v', 0x0002);
659 51
        $dataProp .= pack('v', 0x0000);
660
        // Array of vtHeadingPair
661
        // vtUnalignedString - headingString
662
        // stringType
663 51
        $dataProp .= pack('v', 0x001E);
664
        // padding
665 51
        $dataProp .= pack('v', 0x0000);
666
        // UnalignedLpstr
667
        // cch
668 51
        $dataProp .= pack('v', 0x0013);
669 51
        $dataProp .= pack('v', 0x0000);
670
        // value
671 51
        $dataProp .= 'Feuilles de calcul';
672
        // vtUnalignedString - headingParts
673
        // wType : 0x0003 = 32 bit signed integer
674 51
        $dataProp .= pack('v', 0x0300);
675
        // padding
676 51
        $dataProp .= pack('v', 0x0000);
677
        // value
678 51
        $dataProp .= pack('v', 0x0100);
679 51
        $dataProp .= pack('v', 0x0000);
680 51
        $dataProp .= pack('v', 0x0000);
681 51
        $dataProp .= pack('v', 0x0000);
682
683 51
        $dataSection[] = [
684 51
            'summary' => ['pack' => 'V', 'data' => 0x0C],
685
            'offset' => ['pack' => 'V'],
686
            'type' => ['pack' => 'V', 'data' => 0x100C],
687 51
            'data' => ['data' => $dataProp, 'length' => strlen($dataProp)],
688
        ];
689 51
        ++$dataSection_NumProps;
690
691
        //         4     Section Length
692
        //        4     Property count
693
        //        8 * $dataSection_NumProps (8 =  ID (4) + OffSet(4))
694 51
        $dataSection_Content_Offset = 8 + $dataSection_NumProps * 8;
695 51
        foreach ($dataSection as $dataProp) {
696
            // Summary
697 51
            $dataSection_Summary .= pack($dataProp['summary']['pack'], $dataProp['summary']['data']);
698
            // Offset
699 51
            $dataSection_Summary .= pack($dataProp['offset']['pack'], $dataSection_Content_Offset);
700
            // DataType
701 51
            $dataSection_Content .= pack($dataProp['type']['pack'], $dataProp['type']['data']);
702
            // Data
703 51
            if ($dataProp['type']['data'] == 0x02) { // 2 byte signed integer
704 51
                $dataSection_Content .= pack('V', $dataProp['data']['data']);
705
706 51
                $dataSection_Content_Offset += 4 + 4;
707 51
            } elseif ($dataProp['type']['data'] == 0x03) { // 4 byte signed integer
708 51
                $dataSection_Content .= pack('V', $dataProp['data']['data']);
709
710 51
                $dataSection_Content_Offset += 4 + 4;
711 51
            } elseif ($dataProp['type']['data'] == 0x0B) { // Boolean
712 51
                if ($dataProp['data']['data'] == false) {
713 51
                    $dataSection_Content .= pack('V', 0x0000);
714
                } else {
715
                    $dataSection_Content .= pack('V', 0x0001);
716
                }
717 51
                $dataSection_Content_Offset += 4 + 4;
718 51
            } elseif ($dataProp['type']['data'] == 0x1E) { // null-terminated string prepended by dword string length
719
                // Null-terminated string
720 31
                $dataProp['data']['data'] .= chr(0);
721 31
                $dataProp['data']['length'] += 1;
722
                // Complete the string with null string for being a %4
723 31
                $dataProp['data']['length'] = $dataProp['data']['length'] + ((4 - $dataProp['data']['length'] % 4) == 4 ? 0 : (4 - $dataProp['data']['length'] % 4));
724 31
                $dataProp['data']['data'] = str_pad($dataProp['data']['data'], $dataProp['data']['length'], chr(0), STR_PAD_RIGHT);
725
726 31
                $dataSection_Content .= pack('V', $dataProp['data']['length']);
727 31
                $dataSection_Content .= $dataProp['data']['data'];
728
729 31
                $dataSection_Content_Offset += 4 + 4 + strlen($dataProp['data']['data']);
730 51
            } elseif ($dataProp['type']['data'] == 0x40) { // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601)
731
                $dataSection_Content .= $dataProp['data']['data'];
732
733
                $dataSection_Content_Offset += 4 + 8;
734
            } else {
735
                // Data Type Not Used at the moment
736 51
                $dataSection_Content .= $dataProp['data']['data'];
737
738 51
                $dataSection_Content_Offset += 4 + $dataProp['data']['length'];
739
            }
740
        }
741
        // Now $dataSection_Content_Offset contains the size of the content
742
743
        // section header
744
        // offset: $secOffset; size: 4; section length
745
        //         + x  Size of the content (summary + content)
746 51
        $data .= pack('V', $dataSection_Content_Offset);
747
        // offset: $secOffset+4; size: 4; property count
748 51
        $data .= pack('V', $dataSection_NumProps);
749
        // Section Summary
750 51
        $data .= $dataSection_Summary;
751
        // Section Content
752 51
        $data .= $dataSection_Content;
753
754 51
        return $data;
755
    }
756
757
    /**
758
     * Build the OLE Part for Summary Information.
759
     *
760
     * @return string
761
     */
762 51
    private function writeSummaryInformation()
763
    {
764
        // offset: 0; size: 2; must be 0xFE 0xFF (UTF-16 LE byte order mark)
765 51
        $data = pack('v', 0xFFFE);
766
        // offset: 2; size: 2;
767 51
        $data .= pack('v', 0x0000);
768
        // offset: 4; size: 2; OS version
769 51
        $data .= pack('v', 0x0106);
770
        // offset: 6; size: 2; OS indicator
771 51
        $data .= pack('v', 0x0002);
772
        // offset: 8; size: 16
773 51
        $data .= pack('VVVV', 0x00, 0x00, 0x00, 0x00);
774
        // offset: 24; size: 4; section count
775 51
        $data .= pack('V', 0x0001);
776
777
        // offset: 28; size: 16; first section's class id: e0 85 9f f2 f9 4f 68 10 ab 91 08 00 2b 27 b3 d9
778 51
        $data .= pack('vvvvvvvv', 0x85E0, 0xF29F, 0x4FF9, 0x1068, 0x91AB, 0x0008, 0x272B, 0xD9B3);
779
        // offset: 44; size: 4; offset of the start
780 51
        $data .= pack('V', 0x30);
781
782
        // SECTION
783 51
        $dataSection = [];
784 51
        $dataSection_NumProps = 0;
785 51
        $dataSection_Summary = '';
786 51
        $dataSection_Content = '';
787
788
        // CodePage : CP-1252
789 51
        $dataSection[] = [
790
            'summary' => ['pack' => 'V', 'data' => 0x01],
791
            'offset' => ['pack' => 'V'],
792
            'type' => ['pack' => 'V', 'data' => 0x02], // 2 byte signed integer
793
            'data' => ['data' => 1252],
794
        ];
795 51
        ++$dataSection_NumProps;
796
797
        //    Title
798 51
        if ($this->spreadsheet->getProperties()->getTitle()) {
799 50
            $dataProp = $this->spreadsheet->getProperties()->getTitle();
800 50
            $dataSection[] = [
801 50
                'summary' => ['pack' => 'V', 'data' => 0x02],
802
                'offset' => ['pack' => 'V'],
803
                'type' => ['pack' => 'V', 'data' => 0x1E], // null-terminated string prepended by dword string length
804 50
                'data' => ['data' => $dataProp, 'length' => strlen($dataProp)],
805
            ];
806 50
            ++$dataSection_NumProps;
807
        }
808
        //    Subject
809 51
        if ($this->spreadsheet->getProperties()->getSubject()) {
810 32
            $dataProp = $this->spreadsheet->getProperties()->getSubject();
811 32
            $dataSection[] = [
812 32
                'summary' => ['pack' => 'V', 'data' => 0x03],
813
                'offset' => ['pack' => 'V'],
814
                'type' => ['pack' => 'V', 'data' => 0x1E], // null-terminated string prepended by dword string length
815 32
                'data' => ['data' => $dataProp, 'length' => strlen($dataProp)],
816
            ];
817 32
            ++$dataSection_NumProps;
818
        }
819
        //    Author (Creator)
820 51
        if ($this->spreadsheet->getProperties()->getCreator()) {
821 51
            $dataProp = $this->spreadsheet->getProperties()->getCreator();
822 51
            $dataSection[] = [
823 51
                'summary' => ['pack' => 'V', 'data' => 0x04],
824
                'offset' => ['pack' => 'V'],
825
                'type' => ['pack' => 'V', 'data' => 0x1E], // null-terminated string prepended by dword string length
826 51
                'data' => ['data' => $dataProp, 'length' => strlen($dataProp)],
827
            ];
828 51
            ++$dataSection_NumProps;
829
        }
830
        //    Keywords
831 51
        if ($this->spreadsheet->getProperties()->getKeywords()) {
832 32
            $dataProp = $this->spreadsheet->getProperties()->getKeywords();
833 32
            $dataSection[] = [
834 32
                'summary' => ['pack' => 'V', 'data' => 0x05],
835
                'offset' => ['pack' => 'V'],
836
                'type' => ['pack' => 'V', 'data' => 0x1E], // null-terminated string prepended by dword string length
837 32
                'data' => ['data' => $dataProp, 'length' => strlen($dataProp)],
838
            ];
839 32
            ++$dataSection_NumProps;
840
        }
841
        //    Comments (Description)
842 51
        if ($this->spreadsheet->getProperties()->getDescription()) {
843 31
            $dataProp = $this->spreadsheet->getProperties()->getDescription();
844 31
            $dataSection[] = [
845 31
                'summary' => ['pack' => 'V', 'data' => 0x06],
846
                'offset' => ['pack' => 'V'],
847
                'type' => ['pack' => 'V', 'data' => 0x1E], // null-terminated string prepended by dword string length
848 31
                'data' => ['data' => $dataProp, 'length' => strlen($dataProp)],
849
            ];
850 31
            ++$dataSection_NumProps;
851
        }
852
        //    Last Saved By (LastModifiedBy)
853 51
        if ($this->spreadsheet->getProperties()->getLastModifiedBy()) {
854 51
            $dataProp = $this->spreadsheet->getProperties()->getLastModifiedBy();
855 51
            $dataSection[] = [
856 51
                'summary' => ['pack' => 'V', 'data' => 0x08],
857
                'offset' => ['pack' => 'V'],
858
                'type' => ['pack' => 'V', 'data' => 0x1E], // null-terminated string prepended by dword string length
859 51
                'data' => ['data' => $dataProp, 'length' => strlen($dataProp)],
860
            ];
861 51
            ++$dataSection_NumProps;
862
        }
863
        //    Created Date/Time
864 51
        if ($this->spreadsheet->getProperties()->getCreated()) {
865 51
            $dataProp = $this->spreadsheet->getProperties()->getCreated();
866 51
            $dataSection[] = [
867 51
                'summary' => ['pack' => 'V', 'data' => 0x0C],
868
                'offset' => ['pack' => 'V'],
869
                'type' => ['pack' => 'V', 'data' => 0x40], // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601)
870 51
                'data' => ['data' => OLE::localDateToOLE($dataProp)],
871
            ];
872 51
            ++$dataSection_NumProps;
873
        }
874
        //    Modified Date/Time
875 51
        if ($this->spreadsheet->getProperties()->getModified()) {
876 51
            $dataProp = $this->spreadsheet->getProperties()->getModified();
877 51
            $dataSection[] = [
878 51
                'summary' => ['pack' => 'V', 'data' => 0x0D],
879
                'offset' => ['pack' => 'V'],
880
                'type' => ['pack' => 'V', 'data' => 0x40], // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601)
881 51
                'data' => ['data' => OLE::localDateToOLE($dataProp)],
882
            ];
883 51
            ++$dataSection_NumProps;
884
        }
885
        //    Security
886 51
        $dataSection[] = [
887
            'summary' => ['pack' => 'V', 'data' => 0x13],
888
            'offset' => ['pack' => 'V'],
889
            'type' => ['pack' => 'V', 'data' => 0x03], // 4 byte signed integer
890
            'data' => ['data' => 0x00],
891
        ];
892 51
        ++$dataSection_NumProps;
893
894
        //         4     Section Length
895
        //        4     Property count
896
        //        8 * $dataSection_NumProps (8 =  ID (4) + OffSet(4))
897 51
        $dataSection_Content_Offset = 8 + $dataSection_NumProps * 8;
898 51
        foreach ($dataSection as $dataProp) {
899
            // Summary
900 51
            $dataSection_Summary .= pack($dataProp['summary']['pack'], $dataProp['summary']['data']);
901
            // Offset
902 51
            $dataSection_Summary .= pack($dataProp['offset']['pack'], $dataSection_Content_Offset);
903
            // DataType
904 51
            $dataSection_Content .= pack($dataProp['type']['pack'], $dataProp['type']['data']);
905
            // Data
906 51
            if ($dataProp['type']['data'] == 0x02) { // 2 byte signed integer
907 51
                $dataSection_Content .= pack('V', $dataProp['data']['data']);
908
909 51
                $dataSection_Content_Offset += 4 + 4;
910 51
            } elseif ($dataProp['type']['data'] == 0x03) { // 4 byte signed integer
911 51
                $dataSection_Content .= pack('V', $dataProp['data']['data']);
912
913 51
                $dataSection_Content_Offset += 4 + 4;
914 51
            } elseif ($dataProp['type']['data'] == 0x1E) { // null-terminated string prepended by dword string length
915
                // Null-terminated string
916 51
                $dataProp['data']['data'] .= chr(0);
917 51
                $dataProp['data']['length'] += 1;
918
                // Complete the string with null string for being a %4
919 51
                $dataProp['data']['length'] = $dataProp['data']['length'] + ((4 - $dataProp['data']['length'] % 4) == 4 ? 0 : (4 - $dataProp['data']['length'] % 4));
920 51
                $dataProp['data']['data'] = str_pad($dataProp['data']['data'], $dataProp['data']['length'], chr(0), STR_PAD_RIGHT);
921
922 51
                $dataSection_Content .= pack('V', $dataProp['data']['length']);
923 51
                $dataSection_Content .= $dataProp['data']['data'];
924
925 51
                $dataSection_Content_Offset += 4 + 4 + strlen($dataProp['data']['data']);
926 51
            } elseif ($dataProp['type']['data'] == 0x40) { // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601)
927 51
                $dataSection_Content .= $dataProp['data']['data'];
928
929 51
                $dataSection_Content_Offset += 4 + 8;
930
            }
931
            // Data Type Not Used at the moment
932
        }
933
        // Now $dataSection_Content_Offset contains the size of the content
934
935
        // section header
936
        // offset: $secOffset; size: 4; section length
937
        //         + x  Size of the content (summary + content)
938 51
        $data .= pack('V', $dataSection_Content_Offset);
939
        // offset: $secOffset+4; size: 4; property count
940 51
        $data .= pack('V', $dataSection_NumProps);
941
        // Section Summary
942 51
        $data .= $dataSection_Summary;
943
        // Section Content
944 51
        $data .= $dataSection_Content;
945
946 51
        return $data;
947
    }
948
}
949