Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

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

   1  <?php
   2  
   3  namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
   4  
   5  use PhpOffice\PhpSpreadsheet\Shared\File;
   6  use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
   7  use PhpOffice\PhpSpreadsheet\Spreadsheet;
   8  use PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing;
   9  use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException;
  10  
  11  class ContentTypes extends WriterPart
  12  {
  13      /**
  14       * Write content types to XML format.
  15       *
  16       * @param Spreadsheet $spreadsheet
  17       * @param bool $includeCharts Flag indicating if we should include drawing details for charts
  18       *
  19       * @throws WriterException
  20       *
  21       * @return string XML Output
  22       */
  23      public function writeContentTypes(Spreadsheet $spreadsheet, $includeCharts = false)
  24      {
  25          // Create XML writer
  26          $objWriter = null;
  27          if ($this->getParentWriter()->getUseDiskCaching()) {
  28              $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
  29          } else {
  30              $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
  31          }
  32  
  33          // XML header
  34          $objWriter->startDocument('1.0', 'UTF-8', 'yes');
  35  
  36          // Types
  37          $objWriter->startElement('Types');
  38          $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/content-types');
  39  
  40          // Theme
  41          $this->writeOverrideContentType($objWriter, '/xl/theme/theme1.xml', 'application/vnd.openxmlformats-officedocument.theme+xml');
  42  
  43          // Styles
  44          $this->writeOverrideContentType($objWriter, '/xl/styles.xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml');
  45  
  46          // Rels
  47          $this->writeDefaultContentType($objWriter, 'rels', 'application/vnd.openxmlformats-package.relationships+xml');
  48  
  49          // XML
  50          $this->writeDefaultContentType($objWriter, 'xml', 'application/xml');
  51  
  52          // VML
  53          $this->writeDefaultContentType($objWriter, 'vml', 'application/vnd.openxmlformats-officedocument.vmlDrawing');
  54  
  55          // Workbook
  56          if ($spreadsheet->hasMacros()) { //Macros in workbook ?
  57              // Yes : not standard content but "macroEnabled"
  58              $this->writeOverrideContentType($objWriter, '/xl/workbook.xml', 'application/vnd.ms-excel.sheet.macroEnabled.main+xml');
  59              //... and define a new type for the VBA project
  60              // Better use Override, because we can use 'bin' also for xl\printerSettings\printerSettings1.bin
  61              $this->writeOverrideContentType($objWriter, '/xl/vbaProject.bin', 'application/vnd.ms-office.vbaProject');
  62              if ($spreadsheet->hasMacrosCertificate()) {
  63                  // signed macros ?
  64                  // Yes : add needed information
  65                  $this->writeOverrideContentType($objWriter, '/xl/vbaProjectSignature.bin', 'application/vnd.ms-office.vbaProjectSignature');
  66              }
  67          } else {
  68              // no macros in workbook, so standard type
  69              $this->writeOverrideContentType($objWriter, '/xl/workbook.xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml');
  70          }
  71  
  72          // DocProps
  73          $this->writeOverrideContentType($objWriter, '/docProps/app.xml', 'application/vnd.openxmlformats-officedocument.extended-properties+xml');
  74  
  75          $this->writeOverrideContentType($objWriter, '/docProps/core.xml', 'application/vnd.openxmlformats-package.core-properties+xml');
  76  
  77          $customPropertyList = $spreadsheet->getProperties()->getCustomProperties();
  78          if (!empty($customPropertyList)) {
  79              $this->writeOverrideContentType($objWriter, '/docProps/custom.xml', 'application/vnd.openxmlformats-officedocument.custom-properties+xml');
  80          }
  81  
  82          // Worksheets
  83          $sheetCount = $spreadsheet->getSheetCount();
  84          for ($i = 0; $i < $sheetCount; ++$i) {
  85              $this->writeOverrideContentType($objWriter, '/xl/worksheets/sheet' . ($i + 1) . '.xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml');
  86          }
  87  
  88          // Shared strings
  89          $this->writeOverrideContentType($objWriter, '/xl/sharedStrings.xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml');
  90  
  91          // Add worksheet relationship content types
  92          $unparsedLoadedData = $spreadsheet->getUnparsedLoadedData();
  93          $chart = 1;
  94          for ($i = 0; $i < $sheetCount; ++$i) {
  95              $drawings = $spreadsheet->getSheet($i)->getDrawingCollection();
  96              $drawingCount = count($drawings);
  97              $chartCount = ($includeCharts) ? $spreadsheet->getSheet($i)->getChartCount() : 0;
  98              $hasUnparsedDrawing = isset($unparsedLoadedData['sheets'][$spreadsheet->getSheet($i)->getCodeName()]['drawingOriginalIds']);
  99  
 100              //    We need a drawing relationship for the worksheet if we have either drawings or charts
 101              if (($drawingCount > 0) || ($chartCount > 0) || $hasUnparsedDrawing) {
 102                  $this->writeOverrideContentType($objWriter, '/xl/drawings/drawing' . ($i + 1) . '.xml', 'application/vnd.openxmlformats-officedocument.drawing+xml');
 103              }
 104  
 105              //    If we have charts, then we need a chart relationship for every individual chart
 106              if ($chartCount > 0) {
 107                  for ($c = 0; $c < $chartCount; ++$c) {
 108                      $this->writeOverrideContentType($objWriter, '/xl/charts/chart' . $chart++ . '.xml', 'application/vnd.openxmlformats-officedocument.drawingml.chart+xml');
 109                  }
 110              }
 111          }
 112  
 113          // Comments
 114          for ($i = 0; $i < $sheetCount; ++$i) {
 115              if (count($spreadsheet->getSheet($i)->getComments()) > 0) {
 116                  $this->writeOverrideContentType($objWriter, '/xl/comments' . ($i + 1) . '.xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml');
 117              }
 118          }
 119  
 120          // Add media content-types
 121          $aMediaContentTypes = [];
 122          $mediaCount = $this->getParentWriter()->getDrawingHashTable()->count();
 123          for ($i = 0; $i < $mediaCount; ++$i) {
 124              $extension = '';
 125              $mimeType = '';
 126  
 127              if ($this->getParentWriter()->getDrawingHashTable()->getByIndex($i) instanceof \PhpOffice\PhpSpreadsheet\Worksheet\Drawing) {
 128                  $extension = strtolower($this->getParentWriter()->getDrawingHashTable()->getByIndex($i)->getExtension());
 129                  $mimeType = $this->getImageMimeType($this->getParentWriter()->getDrawingHashTable()->getByIndex($i)->getPath());
 130              } elseif ($this->getParentWriter()->getDrawingHashTable()->getByIndex($i) instanceof MemoryDrawing) {
 131                  $extension = strtolower($this->getParentWriter()->getDrawingHashTable()->getByIndex($i)->getMimeType());
 132                  $extension = explode('/', $extension);
 133                  $extension = $extension[1];
 134  
 135                  $mimeType = $this->getParentWriter()->getDrawingHashTable()->getByIndex($i)->getMimeType();
 136              }
 137  
 138              if (!isset($aMediaContentTypes[$extension])) {
 139                  $aMediaContentTypes[$extension] = $mimeType;
 140  
 141                  $this->writeDefaultContentType($objWriter, $extension, $mimeType);
 142              }
 143          }
 144          if ($spreadsheet->hasRibbonBinObjects()) {
 145              // Some additional objects in the ribbon ?
 146              // we need to write "Extension" but not already write for media content
 147              $tabRibbonTypes = array_diff($spreadsheet->getRibbonBinObjects('types'), array_keys($aMediaContentTypes));
 148              foreach ($tabRibbonTypes as $aRibbonType) {
 149                  $mimeType = 'image/.' . $aRibbonType; //we wrote $mimeType like customUI Editor
 150                  $this->writeDefaultContentType($objWriter, $aRibbonType, $mimeType);
 151              }
 152          }
 153          $sheetCount = $spreadsheet->getSheetCount();
 154          for ($i = 0; $i < $sheetCount; ++$i) {
 155              if (count($spreadsheet->getSheet($i)->getHeaderFooter()->getImages()) > 0) {
 156                  foreach ($spreadsheet->getSheet($i)->getHeaderFooter()->getImages() as $image) {
 157                      if (!isset($aMediaContentTypes[strtolower($image->getExtension())])) {
 158                          $aMediaContentTypes[strtolower($image->getExtension())] = $this->getImageMimeType($image->getPath());
 159  
 160                          $this->writeDefaultContentType($objWriter, strtolower($image->getExtension()), $aMediaContentTypes[strtolower($image->getExtension())]);
 161                      }
 162                  }
 163              }
 164          }
 165  
 166          // unparsed defaults
 167          if (isset($unparsedLoadedData['default_content_types'])) {
 168              foreach ($unparsedLoadedData['default_content_types'] as $extName => $contentType) {
 169                  $this->writeDefaultContentType($objWriter, $extName, $contentType);
 170              }
 171          }
 172  
 173          // unparsed overrides
 174          if (isset($unparsedLoadedData['override_content_types'])) {
 175              foreach ($unparsedLoadedData['override_content_types'] as $partName => $overrideType) {
 176                  $this->writeOverrideContentType($objWriter, $partName, $overrideType);
 177              }
 178          }
 179  
 180          $objWriter->endElement();
 181  
 182          // Return
 183          return $objWriter->getData();
 184      }
 185  
 186      /**
 187       * Get image mime type.
 188       *
 189       * @param string $pFile Filename
 190       *
 191       * @throws WriterException
 192       *
 193       * @return string Mime Type
 194       */
 195      private function getImageMimeType($pFile)
 196      {
 197          if (File::fileExists($pFile)) {
 198              $image = getimagesize($pFile);
 199  
 200              return image_type_to_mime_type($image[2]);
 201          }
 202  
 203          throw new WriterException("File $pFile does not exist");
 204      }
 205  
 206      /**
 207       * Write Default content type.
 208       *
 209       * @param XMLWriter $objWriter XML Writer
 210       * @param string $pPartname Part name
 211       * @param string $pContentType Content type
 212       *
 213       * @throws WriterException
 214       */
 215      private function writeDefaultContentType(XMLWriter $objWriter, $pPartname, $pContentType)
 216      {
 217          if ($pPartname != '' && $pContentType != '') {
 218              // Write content type
 219              $objWriter->startElement('Default');
 220              $objWriter->writeAttribute('Extension', $pPartname);
 221              $objWriter->writeAttribute('ContentType', $pContentType);
 222              $objWriter->endElement();
 223          } else {
 224              throw new WriterException('Invalid parameters passed.');
 225          }
 226      }
 227  
 228      /**
 229       * Write Override content type.
 230       *
 231       * @param XMLWriter $objWriter XML Writer
 232       * @param string $pPartname Part name
 233       * @param string $pContentType Content type
 234       *
 235       * @throws WriterException
 236       */
 237      private function writeOverrideContentType(XMLWriter $objWriter, $pPartname, $pContentType)
 238      {
 239          if ($pPartname != '' && $pContentType != '') {
 240              // Write content type
 241              $objWriter->startElement('Override');
 242              $objWriter->writeAttribute('PartName', $pPartname);
 243              $objWriter->writeAttribute('ContentType', $pContentType);
 244              $objWriter->endElement();
 245          } else {
 246              throw new WriterException('Invalid parameters passed.');
 247          }
 248      }
 249  }