Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401] [Versions 401 and 402] [Versions 401 and 403]

   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\HashTable;
   8  use PhpOffice\PhpSpreadsheet\Spreadsheet;
   9  use PhpOffice\PhpSpreadsheet\Style\Borders;
  10  use PhpOffice\PhpSpreadsheet\Style\Conditional;
  11  use PhpOffice\PhpSpreadsheet\Style\Fill;
  12  use PhpOffice\PhpSpreadsheet\Style\Font;
  13  use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
  14  use PhpOffice\PhpSpreadsheet\Worksheet\BaseDrawing;
  15  use PhpOffice\PhpSpreadsheet\Worksheet\Drawing as WorksheetDrawing;
  16  use PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing;
  17  use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException;
  18  use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Chart;
  19  use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Comments;
  20  use PhpOffice\PhpSpreadsheet\Writer\Xlsx\ContentTypes;
  21  use PhpOffice\PhpSpreadsheet\Writer\Xlsx\DocProps;
  22  use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Drawing;
  23  use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Rels;
  24  use PhpOffice\PhpSpreadsheet\Writer\Xlsx\RelsRibbon;
  25  use PhpOffice\PhpSpreadsheet\Writer\Xlsx\RelsVBA;
  26  use PhpOffice\PhpSpreadsheet\Writer\Xlsx\StringTable;
  27  use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Style;
  28  use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Table;
  29  use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Theme;
  30  use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Workbook;
  31  use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Worksheet;
  32  use ZipArchive;
  33  use ZipStream\Exception\OverflowException;
  34  use ZipStream\Option\Archive;
  35  use ZipStream\ZipStream;
  36  
  37  class Xlsx extends BaseWriter
  38  {
  39      /**
  40       * Office2003 compatibility.
  41       *
  42       * @var bool
  43       */
  44      private $office2003compatibility = false;
  45  
  46      /**
  47       * Private Spreadsheet.
  48       *
  49       * @var Spreadsheet
  50       */
  51      private $spreadSheet;
  52  
  53      /**
  54       * Private string table.
  55       *
  56       * @var string[]
  57       */
  58      private $stringTable = [];
  59  
  60      /**
  61       * Private unique Conditional HashTable.
  62       *
  63       * @var HashTable<Conditional>
  64       */
  65      private $stylesConditionalHashTable;
  66  
  67      /**
  68       * Private unique Style HashTable.
  69       *
  70       * @var HashTable<\PhpOffice\PhpSpreadsheet\Style\Style>
  71       */
  72      private $styleHashTable;
  73  
  74      /**
  75       * Private unique Fill HashTable.
  76       *
  77       * @var HashTable<Fill>
  78       */
  79      private $fillHashTable;
  80  
  81      /**
  82       * Private unique \PhpOffice\PhpSpreadsheet\Style\Font HashTable.
  83       *
  84       * @var HashTable<Font>
  85       */
  86      private $fontHashTable;
  87  
  88      /**
  89       * Private unique Borders HashTable.
  90       *
  91       * @var HashTable<Borders>
  92       */
  93      private $bordersHashTable;
  94  
  95      /**
  96       * Private unique NumberFormat HashTable.
  97       *
  98       * @var HashTable<NumberFormat>
  99       */
 100      private $numFmtHashTable;
 101  
 102      /**
 103       * Private unique \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet\BaseDrawing HashTable.
 104       *
 105       * @var HashTable<BaseDrawing>
 106       */
 107      private $drawingHashTable;
 108  
 109      /**
 110       * Private handle for zip stream.
 111       *
 112       * @var ZipStream
 113       */
 114      private $zip;
 115  
 116      /**
 117       * @var Chart
 118       */
 119      private $writerPartChart;
 120  
 121      /**
 122       * @var Comments
 123       */
 124      private $writerPartComments;
 125  
 126      /**
 127       * @var ContentTypes
 128       */
 129      private $writerPartContentTypes;
 130  
 131      /**
 132       * @var DocProps
 133       */
 134      private $writerPartDocProps;
 135  
 136      /**
 137       * @var Drawing
 138       */
 139      private $writerPartDrawing;
 140  
 141      /**
 142       * @var Rels
 143       */
 144      private $writerPartRels;
 145  
 146      /**
 147       * @var RelsRibbon
 148       */
 149      private $writerPartRelsRibbon;
 150  
 151      /**
 152       * @var RelsVBA
 153       */
 154      private $writerPartRelsVBA;
 155  
 156      /**
 157       * @var StringTable
 158       */
 159      private $writerPartStringTable;
 160  
 161      /**
 162       * @var Style
 163       */
 164      private $writerPartStyle;
 165  
 166      /**
 167       * @var Theme
 168       */
 169      private $writerPartTheme;
 170  
 171      /**
 172       * @var Table
 173       */
 174      private $writerPartTable;
 175  
 176      /**
 177       * @var Workbook
 178       */
 179      private $writerPartWorkbook;
 180  
 181      /**
 182       * @var Worksheet
 183       */
 184      private $writerPartWorksheet;
 185  
 186      /**
 187       * Create a new Xlsx Writer.
 188       */
 189      public function __construct(Spreadsheet $spreadsheet)
 190      {
 191          // Assign PhpSpreadsheet
 192          $this->setSpreadsheet($spreadsheet);
 193  
 194          $this->writerPartChart = new Chart($this);
 195          $this->writerPartComments = new Comments($this);
 196          $this->writerPartContentTypes = new ContentTypes($this);
 197          $this->writerPartDocProps = new DocProps($this);
 198          $this->writerPartDrawing = new Drawing($this);
 199          $this->writerPartRels = new Rels($this);
 200          $this->writerPartRelsRibbon = new RelsRibbon($this);
 201          $this->writerPartRelsVBA = new RelsVBA($this);
 202          $this->writerPartStringTable = new StringTable($this);
 203          $this->writerPartStyle = new Style($this);
 204          $this->writerPartTheme = new Theme($this);
 205          $this->writerPartTable = new Table($this);
 206          $this->writerPartWorkbook = new Workbook($this);
 207          $this->writerPartWorksheet = new Worksheet($this);
 208  
 209          // Set HashTable variables
 210          // @phpstan-ignore-next-line
 211          $this->bordersHashTable = new HashTable();
 212          // @phpstan-ignore-next-line
 213          $this->drawingHashTable = new HashTable();
 214          // @phpstan-ignore-next-line
 215          $this->fillHashTable = new HashTable();
 216          // @phpstan-ignore-next-line
 217          $this->fontHashTable = new HashTable();
 218          // @phpstan-ignore-next-line
 219          $this->numFmtHashTable = new HashTable();
 220          // @phpstan-ignore-next-line
 221          $this->styleHashTable = new HashTable();
 222          // @phpstan-ignore-next-line
 223          $this->stylesConditionalHashTable = new HashTable();
 224      }
 225  
 226      public function getWriterPartChart(): Chart
 227      {
 228          return $this->writerPartChart;
 229      }
 230  
 231      public function getWriterPartComments(): Comments
 232      {
 233          return $this->writerPartComments;
 234      }
 235  
 236      public function getWriterPartContentTypes(): ContentTypes
 237      {
 238          return $this->writerPartContentTypes;
 239      }
 240  
 241      public function getWriterPartDocProps(): DocProps
 242      {
 243          return $this->writerPartDocProps;
 244      }
 245  
 246      public function getWriterPartDrawing(): Drawing
 247      {
 248          return $this->writerPartDrawing;
 249      }
 250  
 251      public function getWriterPartRels(): Rels
 252      {
 253          return $this->writerPartRels;
 254      }
 255  
 256      public function getWriterPartRelsRibbon(): RelsRibbon
 257      {
 258          return $this->writerPartRelsRibbon;
 259      }
 260  
 261      public function getWriterPartRelsVBA(): RelsVBA
 262      {
 263          return $this->writerPartRelsVBA;
 264      }
 265  
 266      public function getWriterPartStringTable(): StringTable
 267      {
 268          return $this->writerPartStringTable;
 269      }
 270  
 271      public function getWriterPartStyle(): Style
 272      {
 273          return $this->writerPartStyle;
 274      }
 275  
 276      public function getWriterPartTheme(): Theme
 277      {
 278          return $this->writerPartTheme;
 279      }
 280  
 281      public function getWriterPartTable(): Table
 282      {
 283          return $this->writerPartTable;
 284      }
 285  
 286      public function getWriterPartWorkbook(): Workbook
 287      {
 288          return $this->writerPartWorkbook;
 289      }
 290  
 291      public function getWriterPartWorksheet(): Worksheet
 292      {
 293          return $this->writerPartWorksheet;
 294      }
 295  
 296      /**
 297       * Save PhpSpreadsheet to file.
 298       *
 299       * @param resource|string $filename
 300       */
 301      public function save($filename, int $flags = 0): void
 302      {
 303          $this->processFlags($flags);
 304  
 305          // garbage collect
 306          $this->pathNames = [];
 307          $this->spreadSheet->garbageCollect();
 308  
 309          $saveDebugLog = Calculation::getInstance($this->spreadSheet)->getDebugLog()->getWriteDebugLog();
 310          Calculation::getInstance($this->spreadSheet)->getDebugLog()->setWriteDebugLog(false);
 311          $saveDateReturnType = Functions::getReturnDateType();
 312          Functions::setReturnDateType(Functions::RETURNDATE_EXCEL);
 313  
 314          // Create string lookup table
 315          $this->stringTable = [];
 316          for ($i = 0; $i < $this->spreadSheet->getSheetCount(); ++$i) {
 317              $this->stringTable = $this->getWriterPartStringTable()->createStringTable($this->spreadSheet->getSheet($i), $this->stringTable);
 318          }
 319  
 320          // Create styles dictionaries
 321          $this->styleHashTable->addFromSource($this->getWriterPartStyle()->allStyles($this->spreadSheet));
 322          $this->stylesConditionalHashTable->addFromSource($this->getWriterPartStyle()->allConditionalStyles($this->spreadSheet));
 323          $this->fillHashTable->addFromSource($this->getWriterPartStyle()->allFills($this->spreadSheet));
 324          $this->fontHashTable->addFromSource($this->getWriterPartStyle()->allFonts($this->spreadSheet));
 325          $this->bordersHashTable->addFromSource($this->getWriterPartStyle()->allBorders($this->spreadSheet));
 326          $this->numFmtHashTable->addFromSource($this->getWriterPartStyle()->allNumberFormats($this->spreadSheet));
 327  
 328          // Create drawing dictionary
 329          $this->drawingHashTable->addFromSource($this->getWriterPartDrawing()->allDrawings($this->spreadSheet));
 330  
 331          $zipContent = [];
 332          // Add [Content_Types].xml to ZIP file
 333          $zipContent['[Content_Types].xml'] = $this->getWriterPartContentTypes()->writeContentTypes($this->spreadSheet, $this->includeCharts);
 334  
 335          //if hasMacros, add the vbaProject.bin file, Certificate file(if exists)
 336          if ($this->spreadSheet->hasMacros()) {
 337              $macrosCode = $this->spreadSheet->getMacrosCode();
 338              if ($macrosCode !== null) {
 339                  // we have the code ?
 340                  $zipContent['xl/vbaProject.bin'] = $macrosCode; //allways in 'xl', allways named vbaProject.bin
 341                  if ($this->spreadSheet->hasMacrosCertificate()) {
 342                      //signed macros ?
 343                      // Yes : add the certificate file and the related rels file
 344                      $zipContent['xl/vbaProjectSignature.bin'] = $this->spreadSheet->getMacrosCertificate();
 345                      $zipContent['xl/_rels/vbaProject.bin.rels'] = $this->getWriterPartRelsVBA()->writeVBARelationships($this->spreadSheet);
 346                  }
 347              }
 348          }
 349          //a custom UI in this workbook ? add it ("base" xml and additional objects (pictures) and rels)
 350          if ($this->spreadSheet->hasRibbon()) {
 351              $tmpRibbonTarget = $this->spreadSheet->getRibbonXMLData('target');
 352              $tmpRibbonTarget = is_string($tmpRibbonTarget) ? $tmpRibbonTarget : '';
 353              $zipContent[$tmpRibbonTarget] = $this->spreadSheet->getRibbonXMLData('data');
 354              if ($this->spreadSheet->hasRibbonBinObjects()) {
 355                  $tmpRootPath = dirname($tmpRibbonTarget) . '/';
 356                  $ribbonBinObjects = $this->spreadSheet->getRibbonBinObjects('data'); //the files to write
 357                  if (is_array($ribbonBinObjects)) {
 358                      foreach ($ribbonBinObjects as $aPath => $aContent) {
 359                          $zipContent[$tmpRootPath . $aPath] = $aContent;
 360                      }
 361                  }
 362                  //the rels for files
 363                  $zipContent[$tmpRootPath . '_rels/' . basename($tmpRibbonTarget) . '.rels'] = $this->getWriterPartRelsRibbon()->writeRibbonRelationships($this->spreadSheet);
 364              }
 365          }
 366  
 367          // Add relationships to ZIP file
 368          $zipContent['_rels/.rels'] = $this->getWriterPartRels()->writeRelationships($this->spreadSheet);
 369          $zipContent['xl/_rels/workbook.xml.rels'] = $this->getWriterPartRels()->writeWorkbookRelationships($this->spreadSheet);
 370  
 371          // Add document properties to ZIP file
 372          $zipContent['docProps/app.xml'] = $this->getWriterPartDocProps()->writeDocPropsApp($this->spreadSheet);
 373          $zipContent['docProps/core.xml'] = $this->getWriterPartDocProps()->writeDocPropsCore($this->spreadSheet);
 374          $customPropertiesPart = $this->getWriterPartDocProps()->writeDocPropsCustom($this->spreadSheet);
 375          if ($customPropertiesPart !== null) {
 376              $zipContent['docProps/custom.xml'] = $customPropertiesPart;
 377          }
 378  
 379          // Add theme to ZIP file
 380          $zipContent['xl/theme/theme1.xml'] = $this->getWriterPartTheme()->writeTheme($this->spreadSheet);
 381  
 382          // Add string table to ZIP file
 383          $zipContent['xl/sharedStrings.xml'] = $this->getWriterPartStringTable()->writeStringTable($this->stringTable);
 384  
 385          // Add styles to ZIP file
 386          $zipContent['xl/styles.xml'] = $this->getWriterPartStyle()->writeStyles($this->spreadSheet);
 387  
 388          // Add workbook to ZIP file
 389          $zipContent['xl/workbook.xml'] = $this->getWriterPartWorkbook()->writeWorkbook($this->spreadSheet, $this->preCalculateFormulas);
 390  
 391          $chartCount = 0;
 392          // Add worksheets
 393          for ($i = 0; $i < $this->spreadSheet->getSheetCount(); ++$i) {
 394              $zipContent['xl/worksheets/sheet' . ($i + 1) . '.xml'] = $this->getWriterPartWorksheet()->writeWorksheet($this->spreadSheet->getSheet($i), $this->stringTable, $this->includeCharts);
 395              if ($this->includeCharts) {
 396                  $charts = $this->spreadSheet->getSheet($i)->getChartCollection();
 397                  if (count($charts) > 0) {
 398                      foreach ($charts as $chart) {
 399                          $zipContent['xl/charts/chart' . ($chartCount + 1) . '.xml'] = $this->getWriterPartChart()->writeChart($chart, $this->preCalculateFormulas);
 400                          ++$chartCount;
 401                      }
 402                  }
 403              }
 404          }
 405  
 406          $chartRef1 = 0;
 407          $tableRef1 = 1;
 408          // Add worksheet relationships (drawings, ...)
 409          for ($i = 0; $i < $this->spreadSheet->getSheetCount(); ++$i) {
 410              // Add relationships
 411              $zipContent['xl/worksheets/_rels/sheet' . ($i + 1) . '.xml.rels'] = $this->getWriterPartRels()->writeWorksheetRelationships($this->spreadSheet->getSheet($i), ($i + 1), $this->includeCharts, $tableRef1);
 412  
 413              // Add unparsedLoadedData
 414              $sheetCodeName = $this->spreadSheet->getSheet($i)->getCodeName();
 415              $unparsedLoadedData = $this->spreadSheet->getUnparsedLoadedData();
 416              if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['ctrlProps'])) {
 417                  foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['ctrlProps'] as $ctrlProp) {
 418                      $zipContent[$ctrlProp['filePath']] = $ctrlProp['content'];
 419                  }
 420              }
 421              if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['printerSettings'])) {
 422                  foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['printerSettings'] as $ctrlProp) {
 423                      $zipContent[$ctrlProp['filePath']] = $ctrlProp['content'];
 424                  }
 425              }
 426  
 427              $drawings = $this->spreadSheet->getSheet($i)->getDrawingCollection();
 428              $drawingCount = count($drawings);
 429              if ($this->includeCharts) {
 430                  $chartCount = $this->spreadSheet->getSheet($i)->getChartCount();
 431              }
 432  
 433              // Add drawing and image relationship parts
 434              if (($drawingCount > 0) || ($chartCount > 0)) {
 435                  // Drawing relationships
 436                  $zipContent['xl/drawings/_rels/drawing' . ($i + 1) . '.xml.rels'] = $this->getWriterPartRels()->writeDrawingRelationships($this->spreadSheet->getSheet($i), $chartRef1, $this->includeCharts);
 437  
 438                  // Drawings
 439                  $zipContent['xl/drawings/drawing' . ($i + 1) . '.xml'] = $this->getWriterPartDrawing()->writeDrawings($this->spreadSheet->getSheet($i), $this->includeCharts);
 440              } elseif (isset($unparsedLoadedData['sheets'][$sheetCodeName]['drawingAlternateContents'])) {
 441                  // Drawings
 442                  $zipContent['xl/drawings/drawing' . ($i + 1) . '.xml'] = $this->getWriterPartDrawing()->writeDrawings($this->spreadSheet->getSheet($i), $this->includeCharts);
 443              }
 444  
 445              // Add unparsed drawings
 446              if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['Drawings'])) {
 447                  foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['Drawings'] as $relId => $drawingXml) {
 448                      $drawingFile = array_search($relId, $unparsedLoadedData['sheets'][$sheetCodeName]['drawingOriginalIds']);
 449                      if ($drawingFile !== false) {
 450                          //$drawingFile = ltrim($drawingFile, '.');
 451                          //$zipContent['xl' . $drawingFile] = $drawingXml;
 452                          $zipContent['xl/drawings/drawing' . ($i + 1) . '.xml'] = $drawingXml;
 453                      }
 454                  }
 455              }
 456  
 457              // Add comment relationship parts
 458              if (count($this->spreadSheet->getSheet($i)->getComments()) > 0) {
 459                  // VML Comments relationships
 460                  $zipContent['xl/drawings/_rels/vmlDrawing' . ($i + 1) . '.vml.rels'] = $this->getWriterPartRels()->writeVMLDrawingRelationships($this->spreadSheet->getSheet($i));
 461  
 462                  // VML Comments
 463                  $zipContent['xl/drawings/vmlDrawing' . ($i + 1) . '.vml'] = $this->getWriterPartComments()->writeVMLComments($this->spreadSheet->getSheet($i));
 464  
 465                  // Comments
 466                  $zipContent['xl/comments' . ($i + 1) . '.xml'] = $this->getWriterPartComments()->writeComments($this->spreadSheet->getSheet($i));
 467  
 468                  // Media
 469                  foreach ($this->spreadSheet->getSheet($i)->getComments() as $comment) {
 470                      if ($comment->hasBackgroundImage()) {
 471                          $image = $comment->getBackgroundImage();
 472                          $zipContent['xl/media/' . $image->getMediaFilename()] = $this->processDrawing($image);
 473                      }
 474                  }
 475              }
 476  
 477              // Add unparsed relationship parts
 478              if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['vmlDrawings'])) {
 479                  foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['vmlDrawings'] as $vmlDrawing) {
 480                      $zipContent[$vmlDrawing['filePath']] = $vmlDrawing['content'];
 481                  }
 482              }
 483  
 484              // Add header/footer relationship parts
 485              if (count($this->spreadSheet->getSheet($i)->getHeaderFooter()->getImages()) > 0) {
 486                  // VML Drawings
 487                  $zipContent['xl/drawings/vmlDrawingHF' . ($i + 1) . '.vml'] = $this->getWriterPartDrawing()->writeVMLHeaderFooterImages($this->spreadSheet->getSheet($i));
 488  
 489                  // VML Drawing relationships
 490                  $zipContent['xl/drawings/_rels/vmlDrawingHF' . ($i + 1) . '.vml.rels'] = $this->getWriterPartRels()->writeHeaderFooterDrawingRelationships($this->spreadSheet->getSheet($i));
 491  
 492                  // Media
 493                  foreach ($this->spreadSheet->getSheet($i)->getHeaderFooter()->getImages() as $image) {
 494                      $zipContent['xl/media/' . $image->getIndexedFilename()] = file_get_contents($image->getPath());
 495                  }
 496              }
 497  
 498              // Add Table parts
 499              $tables = $this->spreadSheet->getSheet($i)->getTableCollection();
 500              foreach ($tables as $table) {
 501                  $zipContent['xl/tables/table' . $tableRef1 . '.xml'] = $this->getWriterPartTable()->writeTable($table, $tableRef1++);
 502              }
 503          }
 504  
 505          // Add media
 506          for ($i = 0; $i < $this->getDrawingHashTable()->count(); ++$i) {
 507              if ($this->getDrawingHashTable()->getByIndex($i) instanceof WorksheetDrawing) {
 508                  $imageContents = null;
 509                  $imagePath = $this->getDrawingHashTable()->getByIndex($i)->getPath();
 510                  if (strpos($imagePath, 'zip://') !== false) {
 511                      $imagePath = substr($imagePath, 6);
 512                      $imagePathSplitted = explode('#', $imagePath);
 513  
 514                      $imageZip = new ZipArchive();
 515                      $imageZip->open($imagePathSplitted[0]);
 516                      $imageContents = $imageZip->getFromName($imagePathSplitted[1]);
 517                      $imageZip->close();
 518                      unset($imageZip);
 519                  } else {
 520                      $imageContents = file_get_contents($imagePath);
 521                  }
 522  
 523                  $zipContent['xl/media/' . $this->getDrawingHashTable()->getByIndex($i)->getIndexedFilename()] = $imageContents;
 524              } elseif ($this->getDrawingHashTable()->getByIndex($i) instanceof MemoryDrawing) {
 525                  ob_start();
 526                  /** @var callable */
 527                  $callable = $this->getDrawingHashTable()->getByIndex($i)->getRenderingFunction();
 528                  call_user_func(
 529                      $callable,
 530                      $this->getDrawingHashTable()->getByIndex($i)->getImageResource()
 531                  );
 532                  $imageContents = ob_get_contents();
 533                  ob_end_clean();
 534  
 535                  $zipContent['xl/media/' . $this->getDrawingHashTable()->getByIndex($i)->getIndexedFilename()] = $imageContents;
 536              }
 537          }
 538  
 539          Functions::setReturnDateType($saveDateReturnType);
 540          Calculation::getInstance($this->spreadSheet)->getDebugLog()->setWriteDebugLog($saveDebugLog);
 541  
 542          $this->openFileHandle($filename);
 543  
 544          $options = new Archive();
 545          $options->setEnableZip64(false);
 546          $options->setOutputStream($this->fileHandle);
 547  
 548          $this->zip = new ZipStream(null, $options);
 549  
 550          $this->addZipFiles($zipContent);
 551  
 552          // Close file
 553          try {
 554              $this->zip->finish();
 555          } catch (OverflowException $e) {
 556              throw new WriterException('Could not close resource.');
 557          }
 558  
 559          $this->maybeCloseFileHandle();
 560      }
 561  
 562      /**
 563       * Get Spreadsheet object.
 564       *
 565       * @return Spreadsheet
 566       */
 567      public function getSpreadsheet()
 568      {
 569          return $this->spreadSheet;
 570      }
 571  
 572      /**
 573       * Set Spreadsheet object.
 574       *
 575       * @param Spreadsheet $spreadsheet PhpSpreadsheet object
 576       *
 577       * @return $this
 578       */
 579      public function setSpreadsheet(Spreadsheet $spreadsheet)
 580      {
 581          $this->spreadSheet = $spreadsheet;
 582  
 583          return $this;
 584      }
 585  
 586      /**
 587       * Get string table.
 588       *
 589       * @return string[]
 590       */
 591      public function getStringTable()
 592      {
 593          return $this->stringTable;
 594      }
 595  
 596      /**
 597       * Get Style HashTable.
 598       *
 599       * @return HashTable<\PhpOffice\PhpSpreadsheet\Style\Style>
 600       */
 601      public function getStyleHashTable()
 602      {
 603          return $this->styleHashTable;
 604      }
 605  
 606      /**
 607       * Get Conditional HashTable.
 608       *
 609       * @return HashTable<Conditional>
 610       */
 611      public function getStylesConditionalHashTable()
 612      {
 613          return $this->stylesConditionalHashTable;
 614      }
 615  
 616      /**
 617       * Get Fill HashTable.
 618       *
 619       * @return HashTable<Fill>
 620       */
 621      public function getFillHashTable()
 622      {
 623          return $this->fillHashTable;
 624      }
 625  
 626      /**
 627       * Get \PhpOffice\PhpSpreadsheet\Style\Font HashTable.
 628       *
 629       * @return HashTable<Font>
 630       */
 631      public function getFontHashTable()
 632      {
 633          return $this->fontHashTable;
 634      }
 635  
 636      /**
 637       * Get Borders HashTable.
 638       *
 639       * @return HashTable<Borders>
 640       */
 641      public function getBordersHashTable()
 642      {
 643          return $this->bordersHashTable;
 644      }
 645  
 646      /**
 647       * Get NumberFormat HashTable.
 648       *
 649       * @return HashTable<NumberFormat>
 650       */
 651      public function getNumFmtHashTable()
 652      {
 653          return $this->numFmtHashTable;
 654      }
 655  
 656      /**
 657       * Get \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet\BaseDrawing HashTable.
 658       *
 659       * @return HashTable<BaseDrawing>
 660       */
 661      public function getDrawingHashTable()
 662      {
 663          return $this->drawingHashTable;
 664      }
 665  
 666      /**
 667       * Get Office2003 compatibility.
 668       *
 669       * @return bool
 670       */
 671      public function getOffice2003Compatibility()
 672      {
 673          return $this->office2003compatibility;
 674      }
 675  
 676      /**
 677       * Set Office2003 compatibility.
 678       *
 679       * @param bool $office2003compatibility Office2003 compatibility?
 680       *
 681       * @return $this
 682       */
 683      public function setOffice2003Compatibility($office2003compatibility)
 684      {
 685          $this->office2003compatibility = $office2003compatibility;
 686  
 687          return $this;
 688      }
 689  
 690      /** @var array */
 691      private $pathNames = [];
 692  
 693      private function addZipFile(string $path, string $content): void
 694      {
 695          if (!in_array($path, $this->pathNames)) {
 696              $this->pathNames[] = $path;
 697              $this->zip->addFile($path, $content);
 698          }
 699      }
 700  
 701      private function addZipFiles(array $zipContent): void
 702      {
 703          foreach ($zipContent as $path => $content) {
 704              $this->addZipFile($path, $content);
 705          }
 706      }
 707  
 708      /**
 709       * @return mixed
 710       */
 711      private function processDrawing(WorksheetDrawing $drawing)
 712      {
 713          $data = null;
 714          $filename = $drawing->getPath();
 715          $imageData = getimagesize($filename);
 716  
 717          if (is_array($imageData)) {
 718              switch ($imageData[2]) {
 719                  case 1: // GIF, not supported by BIFF8, we convert to PNG
 720                      $image = imagecreatefromgif($filename);
 721                      if ($image !== false) {
 722                          ob_start();
 723                          imagepng($image);
 724                          $data = ob_get_contents();
 725                          ob_end_clean();
 726                      }
 727  
 728                      break;
 729  
 730                  case 2: // JPEG
 731                      $data = file_get_contents($filename);
 732  
 733                      break;
 734  
 735                  case 3: // PNG
 736                      $data = file_get_contents($filename);
 737  
 738                      break;
 739  
 740                  case 6: // Windows DIB (BMP), we convert to PNG
 741                      $image = imagecreatefrombmp($filename);
 742                      if ($image !== false) {
 743                          ob_start();
 744                          imagepng($image);
 745                          $data = ob_get_contents();
 746                          ob_end_clean();
 747                      }
 748  
 749                      break;
 750              }
 751          }
 752  
 753          return $data;
 754      }
 755  }