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