Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

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

   1  <?php
   2  
   3  namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
   4  
   5  use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
   6  use PhpOffice\PhpSpreadsheet\Spreadsheet;
   7  use PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing;
   8  use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException;
   9  
  10  class Rels extends WriterPart
  11  {
  12      /**
  13       * Write relationships to XML format.
  14       *
  15       * @return string XML Output
  16       */
  17      public function writeRelationships(Spreadsheet $spreadsheet)
  18      {
  19          // Create XML writer
  20          $objWriter = null;
  21          if ($this->getParentWriter()->getUseDiskCaching()) {
  22              $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
  23          } else {
  24              $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
  25          }
  26  
  27          // XML header
  28          $objWriter->startDocument('1.0', 'UTF-8', 'yes');
  29  
  30          // Relationships
  31          $objWriter->startElement('Relationships');
  32          $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships');
  33  
  34          $customPropertyList = $spreadsheet->getProperties()->getCustomProperties();
  35          if (!empty($customPropertyList)) {
  36              // Relationship docProps/app.xml
  37              $this->writeRelationship(
  38                  $objWriter,
  39                  4,
  40                  'http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties',
  41                  'docProps/custom.xml'
  42              );
  43          }
  44  
  45          // Relationship docProps/app.xml
  46          $this->writeRelationship(
  47              $objWriter,
  48              3,
  49              'http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties',
  50              'docProps/app.xml'
  51          );
  52  
  53          // Relationship docProps/core.xml
  54          $this->writeRelationship(
  55              $objWriter,
  56              2,
  57              'http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties',
  58              'docProps/core.xml'
  59          );
  60  
  61          // Relationship xl/workbook.xml
  62          $this->writeRelationship(
  63              $objWriter,
  64              1,
  65              'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument',
  66              'xl/workbook.xml'
  67          );
  68          // a custom UI in workbook ?
  69          if ($spreadsheet->hasRibbon()) {
  70              $this->writeRelationShip(
  71                  $objWriter,
  72                  5,
  73                  'http://schemas.microsoft.com/office/2006/relationships/ui/extensibility',
  74                  $spreadsheet->getRibbonXMLData('target')
  75              );
  76          }
  77  
  78          $objWriter->endElement();
  79  
  80          return $objWriter->getData();
  81      }
  82  
  83      /**
  84       * Write workbook relationships to XML format.
  85       *
  86       * @return string XML Output
  87       */
  88      public function writeWorkbookRelationships(Spreadsheet $spreadsheet)
  89      {
  90          // Create XML writer
  91          $objWriter = null;
  92          if ($this->getParentWriter()->getUseDiskCaching()) {
  93              $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
  94          } else {
  95              $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
  96          }
  97  
  98          // XML header
  99          $objWriter->startDocument('1.0', 'UTF-8', 'yes');
 100  
 101          // Relationships
 102          $objWriter->startElement('Relationships');
 103          $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships');
 104  
 105          // Relationship styles.xml
 106          $this->writeRelationship(
 107              $objWriter,
 108              1,
 109              'http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles',
 110              'styles.xml'
 111          );
 112  
 113          // Relationship theme/theme1.xml
 114          $this->writeRelationship(
 115              $objWriter,
 116              2,
 117              'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme',
 118              'theme/theme1.xml'
 119          );
 120  
 121          // Relationship sharedStrings.xml
 122          $this->writeRelationship(
 123              $objWriter,
 124              3,
 125              'http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings',
 126              'sharedStrings.xml'
 127          );
 128  
 129          // Relationships with sheets
 130          $sheetCount = $spreadsheet->getSheetCount();
 131          for ($i = 0; $i < $sheetCount; ++$i) {
 132              $this->writeRelationship(
 133                  $objWriter,
 134                  ($i + 1 + 3),
 135                  'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet',
 136                  'worksheets/sheet' . ($i + 1) . '.xml'
 137              );
 138          }
 139          // Relationships for vbaProject if needed
 140          // id : just after the last sheet
 141          if ($spreadsheet->hasMacros()) {
 142              $this->writeRelationShip(
 143                  $objWriter,
 144                  ($i + 1 + 3),
 145                  'http://schemas.microsoft.com/office/2006/relationships/vbaProject',
 146                  'vbaProject.bin'
 147              );
 148              ++$i; //increment i if needed for an another relation
 149          }
 150  
 151          $objWriter->endElement();
 152  
 153          return $objWriter->getData();
 154      }
 155  
 156      /**
 157       * Write worksheet relationships to XML format.
 158       *
 159       * Numbering is as follows:
 160       *     rId1                 - Drawings
 161       *  rId_hyperlink_x     - Hyperlinks
 162       *
 163       * @param int $pWorksheetId
 164       * @param bool $includeCharts Flag indicating if we should write charts
 165       *
 166       * @return string XML Output
 167       */
 168      public function writeWorksheetRelationships(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet, $pWorksheetId = 1, $includeCharts = false)
 169      {
 170          // Create XML writer
 171          $objWriter = null;
 172          if ($this->getParentWriter()->getUseDiskCaching()) {
 173              $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
 174          } else {
 175              $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
 176          }
 177  
 178          // XML header
 179          $objWriter->startDocument('1.0', 'UTF-8', 'yes');
 180  
 181          // Relationships
 182          $objWriter->startElement('Relationships');
 183          $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships');
 184  
 185          // Write drawing relationships?
 186          $drawingOriginalIds = [];
 187          $unparsedLoadedData = $pWorksheet->getParent()->getUnparsedLoadedData();
 188          if (isset($unparsedLoadedData['sheets'][$pWorksheet->getCodeName()]['drawingOriginalIds'])) {
 189              $drawingOriginalIds = $unparsedLoadedData['sheets'][$pWorksheet->getCodeName()]['drawingOriginalIds'];
 190          }
 191  
 192          if ($includeCharts) {
 193              $charts = $pWorksheet->getChartCollection();
 194          } else {
 195              $charts = [];
 196          }
 197  
 198          if (($pWorksheet->getDrawingCollection()->count() > 0) || (count($charts) > 0) || $drawingOriginalIds) {
 199              $rId = 1;
 200  
 201              // Use original $relPath to get original $rId.
 202              // Take first. In future can be overwritten.
 203              // (! synchronize with \PhpOffice\PhpSpreadsheet\Writer\Xlsx\Worksheet::writeDrawings)
 204              reset($drawingOriginalIds);
 205              $relPath = key($drawingOriginalIds);
 206              if (isset($drawingOriginalIds[$relPath])) {
 207                  $rId = (int) (substr($drawingOriginalIds[$relPath], 3));
 208              }
 209  
 210              // Generate new $relPath to write drawing relationship
 211              $relPath = '../drawings/drawing' . $pWorksheetId . '.xml';
 212              $this->writeRelationship(
 213                  $objWriter,
 214                  $rId,
 215                  'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing',
 216                  $relPath
 217              );
 218          }
 219  
 220          // Write hyperlink relationships?
 221          $i = 1;
 222          foreach ($pWorksheet->getHyperlinkCollection() as $hyperlink) {
 223              if (!$hyperlink->isInternal()) {
 224                  $this->writeRelationship(
 225                      $objWriter,
 226                      '_hyperlink_' . $i,
 227                      'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink',
 228                      $hyperlink->getUrl(),
 229                      'External'
 230                  );
 231  
 232                  ++$i;
 233              }
 234          }
 235  
 236          // Write comments relationship?
 237          $i = 1;
 238          if (count($pWorksheet->getComments()) > 0) {
 239              $this->writeRelationship(
 240                  $objWriter,
 241                  '_comments_vml' . $i,
 242                  'http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing',
 243                  '../drawings/vmlDrawing' . $pWorksheetId . '.vml'
 244              );
 245  
 246              $this->writeRelationship(
 247                  $objWriter,
 248                  '_comments' . $i,
 249                  'http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments',
 250                  '../comments' . $pWorksheetId . '.xml'
 251              );
 252          }
 253  
 254          // Write header/footer relationship?
 255          $i = 1;
 256          if (count($pWorksheet->getHeaderFooter()->getImages()) > 0) {
 257              $this->writeRelationship(
 258                  $objWriter,
 259                  '_headerfooter_vml' . $i,
 260                  'http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing',
 261                  '../drawings/vmlDrawingHF' . $pWorksheetId . '.vml'
 262              );
 263          }
 264  
 265          $this->writeUnparsedRelationship($pWorksheet, $objWriter, 'ctrlProps', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/ctrlProp');
 266          $this->writeUnparsedRelationship($pWorksheet, $objWriter, 'vmlDrawings', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing');
 267          $this->writeUnparsedRelationship($pWorksheet, $objWriter, 'printerSettings', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/printerSettings');
 268  
 269          $objWriter->endElement();
 270  
 271          return $objWriter->getData();
 272      }
 273  
 274      private function writeUnparsedRelationship(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet, XMLWriter $objWriter, $relationship, $type): void
 275      {
 276          $unparsedLoadedData = $pWorksheet->getParent()->getUnparsedLoadedData();
 277          if (!isset($unparsedLoadedData['sheets'][$pWorksheet->getCodeName()][$relationship])) {
 278              return;
 279          }
 280  
 281          foreach ($unparsedLoadedData['sheets'][$pWorksheet->getCodeName()][$relationship] as $rId => $value) {
 282              $this->writeRelationship(
 283                  $objWriter,
 284                  $rId,
 285                  $type,
 286                  $value['relFilePath']
 287              );
 288          }
 289      }
 290  
 291      /**
 292       * Write drawing relationships to XML format.
 293       *
 294       * @param int &$chartRef Chart ID
 295       * @param bool $includeCharts Flag indicating if we should write charts
 296       *
 297       * @return string XML Output
 298       */
 299      public function writeDrawingRelationships(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet, &$chartRef, $includeCharts = false)
 300      {
 301          // Create XML writer
 302          $objWriter = null;
 303          if ($this->getParentWriter()->getUseDiskCaching()) {
 304              $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
 305          } else {
 306              $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
 307          }
 308  
 309          // XML header
 310          $objWriter->startDocument('1.0', 'UTF-8', 'yes');
 311  
 312          // Relationships
 313          $objWriter->startElement('Relationships');
 314          $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships');
 315  
 316          // Loop through images and write relationships
 317          $i = 1;
 318          $iterator = $pWorksheet->getDrawingCollection()->getIterator();
 319          while ($iterator->valid()) {
 320              if (
 321                  $iterator->current() instanceof \PhpOffice\PhpSpreadsheet\Worksheet\Drawing
 322                  || $iterator->current() instanceof MemoryDrawing
 323              ) {
 324                  // Write relationship for image drawing
 325                  /** @var \PhpOffice\PhpSpreadsheet\Worksheet\Drawing $drawing */
 326                  $drawing = $iterator->current();
 327                  $this->writeRelationship(
 328                      $objWriter,
 329                      $i,
 330                      'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image',
 331                      '../media/' . str_replace(' ', '', $drawing->getIndexedFilename())
 332                  );
 333  
 334                  $i = $this->writeDrawingHyperLink($objWriter, $drawing, $i);
 335              }
 336  
 337              $iterator->next();
 338              ++$i;
 339          }
 340  
 341          if ($includeCharts) {
 342              // Loop through charts and write relationships
 343              $chartCount = $pWorksheet->getChartCount();
 344              if ($chartCount > 0) {
 345                  for ($c = 0; $c < $chartCount; ++$c) {
 346                      $this->writeRelationship(
 347                          $objWriter,
 348                          $i++,
 349                          'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart',
 350                          '../charts/chart' . ++$chartRef . '.xml'
 351                      );
 352                  }
 353              }
 354          }
 355  
 356          $objWriter->endElement();
 357  
 358          return $objWriter->getData();
 359      }
 360  
 361      /**
 362       * Write header/footer drawing relationships to XML format.
 363       *
 364       * @return string XML Output
 365       */
 366      public function writeHeaderFooterDrawingRelationships(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet)
 367      {
 368          // Create XML writer
 369          $objWriter = null;
 370          if ($this->getParentWriter()->getUseDiskCaching()) {
 371              $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
 372          } else {
 373              $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
 374          }
 375  
 376          // XML header
 377          $objWriter->startDocument('1.0', 'UTF-8', 'yes');
 378  
 379          // Relationships
 380          $objWriter->startElement('Relationships');
 381          $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships');
 382  
 383          // Loop through images and write relationships
 384          foreach ($pWorksheet->getHeaderFooter()->getImages() as $key => $value) {
 385              // Write relationship for image drawing
 386              $this->writeRelationship(
 387                  $objWriter,
 388                  $key,
 389                  'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image',
 390                  '../media/' . $value->getIndexedFilename()
 391              );
 392          }
 393  
 394          $objWriter->endElement();
 395  
 396          return $objWriter->getData();
 397      }
 398  
 399      /**
 400       * Write Override content type.
 401       *
 402       * @param XMLWriter $objWriter XML Writer
 403       * @param int $pId Relationship ID. rId will be prepended!
 404       * @param string $pType Relationship type
 405       * @param string $pTarget Relationship target
 406       * @param string $pTargetMode Relationship target mode
 407       */
 408      private function writeRelationship(XMLWriter $objWriter, $pId, $pType, $pTarget, $pTargetMode = ''): void
 409      {
 410          if ($pType != '' && $pTarget != '') {
 411              // Write relationship
 412              $objWriter->startElement('Relationship');
 413              $objWriter->writeAttribute('Id', 'rId' . $pId);
 414              $objWriter->writeAttribute('Type', $pType);
 415              $objWriter->writeAttribute('Target', $pTarget);
 416  
 417              if ($pTargetMode != '') {
 418                  $objWriter->writeAttribute('TargetMode', $pTargetMode);
 419              }
 420  
 421              $objWriter->endElement();
 422          } else {
 423              throw new WriterException('Invalid parameters passed.');
 424          }
 425      }
 426  
 427      /**
 428       * @param $objWriter
 429       * @param \PhpOffice\PhpSpreadsheet\Worksheet\Drawing $drawing
 430       * @param $i
 431       *
 432       * @return int
 433       */
 434      private function writeDrawingHyperLink($objWriter, $drawing, $i)
 435      {
 436          if ($drawing->getHyperlink() === null) {
 437              return $i;
 438          }
 439  
 440          ++$i;
 441          $this->writeRelationship(
 442              $objWriter,
 443              $i,
 444              'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink',
 445              $drawing->getHyperlink()->getUrl(),
 446              $drawing->getHyperlink()->getTypeHyperlink()
 447          );
 448  
 449          return $i;
 450      }
 451  }