ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
Xls.php
Go to the documentation of this file.
1 <?php
2 
4 
29 
30 class 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 
98  private $writerWorkbook;
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 }
writeSummaryProp(string $dataProp, int &$dataSection_NumProps, array &$dataSection, int $sumdata, int $typdata)
Definition: Xls.php:785
__construct(Spreadsheet $spreadsheet)
Create a new Xls Writer.
Definition: Xls.php:110
$style
Definition: example_012.php:70
save($pFilename)
Save Spreadsheet to file.
Definition: Xls.php:122
static getReturnDateType()
Return the current Return Date Format for functions that return a date/time (Excel, PHP Serialized Numeric or PHP Object).
Definition: Functions.php:133
static ascToUcs($ascii)
Utility function to transform ASCII text to Unicode.
Definition: OLE.php:478
writeDocumentSummaryInformation()
Build the OLE Part for DocumentSummary Information.
Definition: Xls.php:561
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
$time
Definition: cron.php:21
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
writeSummaryInformation()
Build the OLE Part for Summary Information.
Definition: Xls.php:803
processBaseDrawing(BstoreContainer &$bstoreContainer, BaseDrawing $drawing)
Definition: Xls.php:473
static localDateToOLE($date)
Utility function Returns a string for the OLE container with the date given.
Definition: OLE.php:498
writeSummaryPropOle($dataProp, int &$dataSection_NumProps, array &$dataSection, int $sumdata, int $typdata)
Definition: Xls.php:772
$root
Definition: sabredav.php:45
$filename
Definition: buildRTE.php:89
static getInstance(?Spreadsheet $spreadsheet=null)
Get an instance of this class.
static rangeBoundaries($pRange)
Calculate range boundaries.
Definition: Coordinate.php:187
$i
Definition: disco.tpl.php:19
buildWorkbookEscher()
Build the Escher object corresponding to the MSODRAWINGGROUP record.
Definition: Xls.php:500
processDrawing(BstoreContainer &$bstoreContainer, Drawing $drawing)
Definition: Xls.php:425
processMemoryDrawing(BstoreContainer &$bstoreContainer, MemoryDrawing $drawing, string $renderingFunctionx)
Definition: Xls.php:395
static stringFromColumnIndex($columnIndex)
String from column index.
Definition: Coordinate.php:313
Class for creating Root PPS&#39;s for OLE containers.
Definition: Root.php:31
openFileHandle($filename)
Open file handle.
Definition: BaseWriter.php:102
buildWorksheetEschers()
Build the Worksheet Escher objects.
Definition: Xls.php:236
maybeCloseFileHandle()
Close file handle only if we opened it ourselves.
Definition: BaseWriter.php:123
$data
Definition: bench.php:6