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