Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

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