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\Cell\Coordinate;
   6  use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
   7  use PhpOffice\PhpSpreadsheet\Spreadsheet;
   8  use PhpOffice\PhpSpreadsheet\Worksheet\BaseDrawing;
   9  use PhpOffice\PhpSpreadsheet\Worksheet\HeaderFooterDrawing;
  10  use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException;
  11  
  12  class Drawing extends WriterPart
  13  {
  14      /**
  15       * Write drawings to XML format.
  16       *
  17       * @param \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet
  18       * @param bool $includeCharts Flag indicating if we should include drawing details for charts
  19       *
  20       * @throws WriterException
  21       *
  22       * @return string XML Output
  23       */
  24      public function writeDrawings(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet, $includeCharts = false)
  25      {
  26          // Create XML writer
  27          $objWriter = null;
  28          if ($this->getParentWriter()->getUseDiskCaching()) {
  29              $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
  30          } else {
  31              $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
  32          }
  33  
  34          // XML header
  35          $objWriter->startDocument('1.0', 'UTF-8', 'yes');
  36  
  37          // xdr:wsDr
  38          $objWriter->startElement('xdr:wsDr');
  39          $objWriter->writeAttribute('xmlns:xdr', 'http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing');
  40          $objWriter->writeAttribute('xmlns:a', 'http://schemas.openxmlformats.org/drawingml/2006/main');
  41  
  42          // Loop through images and write drawings
  43          $i = 1;
  44          $iterator = $pWorksheet->getDrawingCollection()->getIterator();
  45          while ($iterator->valid()) {
  46              /** @var BaseDrawing $pDrawing */
  47              $pDrawing = $iterator->current();
  48              $pRelationId = $i;
  49              $hlinkClickId = $pDrawing->getHyperlink() === null ? null : ++$i;
  50  
  51              $this->writeDrawing($objWriter, $pDrawing, $pRelationId, $hlinkClickId);
  52  
  53              $iterator->next();
  54              ++$i;
  55          }
  56  
  57          if ($includeCharts) {
  58              $chartCount = $pWorksheet->getChartCount();
  59              // Loop through charts and write the chart position
  60              if ($chartCount > 0) {
  61                  for ($c = 0; $c < $chartCount; ++$c) {
  62                      $this->writeChart($objWriter, $pWorksheet->getChartByIndex($c), $c + $i);
  63                  }
  64              }
  65          }
  66  
  67          // unparsed AlternateContent
  68          $unparsedLoadedData = $pWorksheet->getParent()->getUnparsedLoadedData();
  69          if (isset($unparsedLoadedData['sheets'][$pWorksheet->getCodeName()]['drawingAlternateContents'])) {
  70              foreach ($unparsedLoadedData['sheets'][$pWorksheet->getCodeName()]['drawingAlternateContents'] as $drawingAlternateContent) {
  71                  $objWriter->writeRaw($drawingAlternateContent);
  72              }
  73          }
  74  
  75          $objWriter->endElement();
  76  
  77          // Return
  78          return $objWriter->getData();
  79      }
  80  
  81      /**
  82       * Write drawings to XML format.
  83       *
  84       * @param XMLWriter $objWriter XML Writer
  85       * @param \PhpOffice\PhpSpreadsheet\Chart\Chart $pChart
  86       * @param int $pRelationId
  87       */
  88      public function writeChart(XMLWriter $objWriter, \PhpOffice\PhpSpreadsheet\Chart\Chart $pChart, $pRelationId = -1)
  89      {
  90          $tl = $pChart->getTopLeftPosition();
  91          $tl['colRow'] = Coordinate::coordinateFromString($tl['cell']);
  92          $br = $pChart->getBottomRightPosition();
  93          $br['colRow'] = Coordinate::coordinateFromString($br['cell']);
  94  
  95          $objWriter->startElement('xdr:twoCellAnchor');
  96  
  97          $objWriter->startElement('xdr:from');
  98          $objWriter->writeElement('xdr:col', Coordinate::columnIndexFromString($tl['colRow'][0]) - 1);
  99          $objWriter->writeElement('xdr:colOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($tl['xOffset']));
 100          $objWriter->writeElement('xdr:row', $tl['colRow'][1] - 1);
 101          $objWriter->writeElement('xdr:rowOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($tl['yOffset']));
 102          $objWriter->endElement();
 103          $objWriter->startElement('xdr:to');
 104          $objWriter->writeElement('xdr:col', Coordinate::columnIndexFromString($br['colRow'][0]) - 1);
 105          $objWriter->writeElement('xdr:colOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($br['xOffset']));
 106          $objWriter->writeElement('xdr:row', $br['colRow'][1] - 1);
 107          $objWriter->writeElement('xdr:rowOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($br['yOffset']));
 108          $objWriter->endElement();
 109  
 110          $objWriter->startElement('xdr:graphicFrame');
 111          $objWriter->writeAttribute('macro', '');
 112          $objWriter->startElement('xdr:nvGraphicFramePr');
 113          $objWriter->startElement('xdr:cNvPr');
 114          $objWriter->writeAttribute('name', 'Chart ' . $pRelationId);
 115          $objWriter->writeAttribute('id', 1025 * $pRelationId);
 116          $objWriter->endElement();
 117          $objWriter->startElement('xdr:cNvGraphicFramePr');
 118          $objWriter->startElement('a:graphicFrameLocks');
 119          $objWriter->endElement();
 120          $objWriter->endElement();
 121          $objWriter->endElement();
 122  
 123          $objWriter->startElement('xdr:xfrm');
 124          $objWriter->startElement('a:off');
 125          $objWriter->writeAttribute('x', '0');
 126          $objWriter->writeAttribute('y', '0');
 127          $objWriter->endElement();
 128          $objWriter->startElement('a:ext');
 129          $objWriter->writeAttribute('cx', '0');
 130          $objWriter->writeAttribute('cy', '0');
 131          $objWriter->endElement();
 132          $objWriter->endElement();
 133  
 134          $objWriter->startElement('a:graphic');
 135          $objWriter->startElement('a:graphicData');
 136          $objWriter->writeAttribute('uri', 'http://schemas.openxmlformats.org/drawingml/2006/chart');
 137          $objWriter->startElement('c:chart');
 138          $objWriter->writeAttribute('xmlns:c', 'http://schemas.openxmlformats.org/drawingml/2006/chart');
 139          $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships');
 140          $objWriter->writeAttribute('r:id', 'rId' . $pRelationId);
 141          $objWriter->endElement();
 142          $objWriter->endElement();
 143          $objWriter->endElement();
 144          $objWriter->endElement();
 145  
 146          $objWriter->startElement('xdr:clientData');
 147          $objWriter->endElement();
 148  
 149          $objWriter->endElement();
 150      }
 151  
 152      /**
 153       * Write drawings to XML format.
 154       *
 155       * @param XMLWriter $objWriter XML Writer
 156       * @param BaseDrawing $pDrawing
 157       * @param int $pRelationId
 158       * @param null|int $hlinkClickId
 159       *
 160       * @throws WriterException
 161       */
 162      public function writeDrawing(XMLWriter $objWriter, BaseDrawing $pDrawing, $pRelationId = -1, $hlinkClickId = null)
 163      {
 164          if ($pRelationId >= 0) {
 165              // xdr:oneCellAnchor
 166              $objWriter->startElement('xdr:oneCellAnchor');
 167              // Image location
 168              $aCoordinates = Coordinate::coordinateFromString($pDrawing->getCoordinates());
 169              $aCoordinates[0] = Coordinate::columnIndexFromString($aCoordinates[0]);
 170  
 171              // xdr:from
 172              $objWriter->startElement('xdr:from');
 173              $objWriter->writeElement('xdr:col', $aCoordinates[0] - 1);
 174              $objWriter->writeElement('xdr:colOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($pDrawing->getOffsetX()));
 175              $objWriter->writeElement('xdr:row', $aCoordinates[1] - 1);
 176              $objWriter->writeElement('xdr:rowOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($pDrawing->getOffsetY()));
 177              $objWriter->endElement();
 178  
 179              // xdr:ext
 180              $objWriter->startElement('xdr:ext');
 181              $objWriter->writeAttribute('cx', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($pDrawing->getWidth()));
 182              $objWriter->writeAttribute('cy', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($pDrawing->getHeight()));
 183              $objWriter->endElement();
 184  
 185              // xdr:pic
 186              $objWriter->startElement('xdr:pic');
 187  
 188              // xdr:nvPicPr
 189              $objWriter->startElement('xdr:nvPicPr');
 190  
 191              // xdr:cNvPr
 192              $objWriter->startElement('xdr:cNvPr');
 193              $objWriter->writeAttribute('id', $pRelationId);
 194              $objWriter->writeAttribute('name', $pDrawing->getName());
 195              $objWriter->writeAttribute('descr', $pDrawing->getDescription());
 196  
 197              //a:hlinkClick
 198              $this->writeHyperLinkDrawing($objWriter, $hlinkClickId);
 199  
 200              $objWriter->endElement();
 201  
 202              // xdr:cNvPicPr
 203              $objWriter->startElement('xdr:cNvPicPr');
 204  
 205              // a:picLocks
 206              $objWriter->startElement('a:picLocks');
 207              $objWriter->writeAttribute('noChangeAspect', '1');
 208              $objWriter->endElement();
 209  
 210              $objWriter->endElement();
 211  
 212              $objWriter->endElement();
 213  
 214              // xdr:blipFill
 215              $objWriter->startElement('xdr:blipFill');
 216  
 217              // a:blip
 218              $objWriter->startElement('a:blip');
 219              $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships');
 220              $objWriter->writeAttribute('r:embed', 'rId' . $pRelationId);
 221              $objWriter->endElement();
 222  
 223              // a:stretch
 224              $objWriter->startElement('a:stretch');
 225              $objWriter->writeElement('a:fillRect', null);
 226              $objWriter->endElement();
 227  
 228              $objWriter->endElement();
 229  
 230              // xdr:spPr
 231              $objWriter->startElement('xdr:spPr');
 232  
 233              // a:xfrm
 234              $objWriter->startElement('a:xfrm');
 235              $objWriter->writeAttribute('rot', \PhpOffice\PhpSpreadsheet\Shared\Drawing::degreesToAngle($pDrawing->getRotation()));
 236              $objWriter->endElement();
 237  
 238              // a:prstGeom
 239              $objWriter->startElement('a:prstGeom');
 240              $objWriter->writeAttribute('prst', 'rect');
 241  
 242              // a:avLst
 243              $objWriter->writeElement('a:avLst', null);
 244  
 245              $objWriter->endElement();
 246  
 247              if ($pDrawing->getShadow()->getVisible()) {
 248                  // a:effectLst
 249                  $objWriter->startElement('a:effectLst');
 250  
 251                  // a:outerShdw
 252                  $objWriter->startElement('a:outerShdw');
 253                  $objWriter->writeAttribute('blurRad', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($pDrawing->getShadow()->getBlurRadius()));
 254                  $objWriter->writeAttribute('dist', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($pDrawing->getShadow()->getDistance()));
 255                  $objWriter->writeAttribute('dir', \PhpOffice\PhpSpreadsheet\Shared\Drawing::degreesToAngle($pDrawing->getShadow()->getDirection()));
 256                  $objWriter->writeAttribute('algn', $pDrawing->getShadow()->getAlignment());
 257                  $objWriter->writeAttribute('rotWithShape', '0');
 258  
 259                  // a:srgbClr
 260                  $objWriter->startElement('a:srgbClr');
 261                  $objWriter->writeAttribute('val', $pDrawing->getShadow()->getColor()->getRGB());
 262  
 263                  // a:alpha
 264                  $objWriter->startElement('a:alpha');
 265                  $objWriter->writeAttribute('val', $pDrawing->getShadow()->getAlpha() * 1000);
 266                  $objWriter->endElement();
 267  
 268                  $objWriter->endElement();
 269  
 270                  $objWriter->endElement();
 271  
 272                  $objWriter->endElement();
 273              }
 274              $objWriter->endElement();
 275  
 276              $objWriter->endElement();
 277  
 278              // xdr:clientData
 279              $objWriter->writeElement('xdr:clientData', null);
 280  
 281              $objWriter->endElement();
 282          } else {
 283              throw new WriterException('Invalid parameters passed.');
 284          }
 285      }
 286  
 287      /**
 288       * Write VML header/footer images to XML format.
 289       *
 290       * @param \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet
 291       *
 292       * @throws WriterException
 293       *
 294       * @return string XML Output
 295       */
 296      public function writeVMLHeaderFooterImages(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet)
 297      {
 298          // Create XML writer
 299          $objWriter = null;
 300          if ($this->getParentWriter()->getUseDiskCaching()) {
 301              $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
 302          } else {
 303              $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
 304          }
 305  
 306          // XML header
 307          $objWriter->startDocument('1.0', 'UTF-8', 'yes');
 308  
 309          // Header/footer images
 310          $images = $pWorksheet->getHeaderFooter()->getImages();
 311  
 312          // xml
 313          $objWriter->startElement('xml');
 314          $objWriter->writeAttribute('xmlns:v', 'urn:schemas-microsoft-com:vml');
 315          $objWriter->writeAttribute('xmlns:o', 'urn:schemas-microsoft-com:office:office');
 316          $objWriter->writeAttribute('xmlns:x', 'urn:schemas-microsoft-com:office:excel');
 317  
 318          // o:shapelayout
 319          $objWriter->startElement('o:shapelayout');
 320          $objWriter->writeAttribute('v:ext', 'edit');
 321  
 322          // o:idmap
 323          $objWriter->startElement('o:idmap');
 324          $objWriter->writeAttribute('v:ext', 'edit');
 325          $objWriter->writeAttribute('data', '1');
 326          $objWriter->endElement();
 327  
 328          $objWriter->endElement();
 329  
 330          // v:shapetype
 331          $objWriter->startElement('v:shapetype');
 332          $objWriter->writeAttribute('id', '_x0000_t75');
 333          $objWriter->writeAttribute('coordsize', '21600,21600');
 334          $objWriter->writeAttribute('o:spt', '75');
 335          $objWriter->writeAttribute('o:preferrelative', 't');
 336          $objWriter->writeAttribute('path', 'm@4@5l@4@11@9@11@9@5xe');
 337          $objWriter->writeAttribute('filled', 'f');
 338          $objWriter->writeAttribute('stroked', 'f');
 339  
 340          // v:stroke
 341          $objWriter->startElement('v:stroke');
 342          $objWriter->writeAttribute('joinstyle', 'miter');
 343          $objWriter->endElement();
 344  
 345          // v:formulas
 346          $objWriter->startElement('v:formulas');
 347  
 348          // v:f
 349          $objWriter->startElement('v:f');
 350          $objWriter->writeAttribute('eqn', 'if lineDrawn pixelLineWidth 0');
 351          $objWriter->endElement();
 352  
 353          // v:f
 354          $objWriter->startElement('v:f');
 355          $objWriter->writeAttribute('eqn', 'sum @0 1 0');
 356          $objWriter->endElement();
 357  
 358          // v:f
 359          $objWriter->startElement('v:f');
 360          $objWriter->writeAttribute('eqn', 'sum 0 0 @1');
 361          $objWriter->endElement();
 362  
 363          // v:f
 364          $objWriter->startElement('v:f');
 365          $objWriter->writeAttribute('eqn', 'prod @2 1 2');
 366          $objWriter->endElement();
 367  
 368          // v:f
 369          $objWriter->startElement('v:f');
 370          $objWriter->writeAttribute('eqn', 'prod @3 21600 pixelWidth');
 371          $objWriter->endElement();
 372  
 373          // v:f
 374          $objWriter->startElement('v:f');
 375          $objWriter->writeAttribute('eqn', 'prod @3 21600 pixelHeight');
 376          $objWriter->endElement();
 377  
 378          // v:f
 379          $objWriter->startElement('v:f');
 380          $objWriter->writeAttribute('eqn', 'sum @0 0 1');
 381          $objWriter->endElement();
 382  
 383          // v:f
 384          $objWriter->startElement('v:f');
 385          $objWriter->writeAttribute('eqn', 'prod @6 1 2');
 386          $objWriter->endElement();
 387  
 388          // v:f
 389          $objWriter->startElement('v:f');
 390          $objWriter->writeAttribute('eqn', 'prod @7 21600 pixelWidth');
 391          $objWriter->endElement();
 392  
 393          // v:f
 394          $objWriter->startElement('v:f');
 395          $objWriter->writeAttribute('eqn', 'sum @8 21600 0');
 396          $objWriter->endElement();
 397  
 398          // v:f
 399          $objWriter->startElement('v:f');
 400          $objWriter->writeAttribute('eqn', 'prod @7 21600 pixelHeight');
 401          $objWriter->endElement();
 402  
 403          // v:f
 404          $objWriter->startElement('v:f');
 405          $objWriter->writeAttribute('eqn', 'sum @10 21600 0');
 406          $objWriter->endElement();
 407  
 408          $objWriter->endElement();
 409  
 410          // v:path
 411          $objWriter->startElement('v:path');
 412          $objWriter->writeAttribute('o:extrusionok', 'f');
 413          $objWriter->writeAttribute('gradientshapeok', 't');
 414          $objWriter->writeAttribute('o:connecttype', 'rect');
 415          $objWriter->endElement();
 416  
 417          // o:lock
 418          $objWriter->startElement('o:lock');
 419          $objWriter->writeAttribute('v:ext', 'edit');
 420          $objWriter->writeAttribute('aspectratio', 't');
 421          $objWriter->endElement();
 422  
 423          $objWriter->endElement();
 424  
 425          // Loop through images
 426          foreach ($images as $key => $value) {
 427              $this->writeVMLHeaderFooterImage($objWriter, $key, $value);
 428          }
 429  
 430          $objWriter->endElement();
 431  
 432          // Return
 433          return $objWriter->getData();
 434      }
 435  
 436      /**
 437       * Write VML comment to XML format.
 438       *
 439       * @param XMLWriter $objWriter XML Writer
 440       * @param string $pReference Reference
 441       * @param HeaderFooterDrawing $pImage Image
 442       */
 443      private function writeVMLHeaderFooterImage(XMLWriter $objWriter, $pReference, HeaderFooterDrawing $pImage)
 444      {
 445          // Calculate object id
 446          preg_match('{(\d+)}', md5($pReference), $m);
 447          $id = 1500 + (substr($m[1], 0, 2) * 1);
 448  
 449          // Calculate offset
 450          $width = $pImage->getWidth();
 451          $height = $pImage->getHeight();
 452          $marginLeft = $pImage->getOffsetX();
 453          $marginTop = $pImage->getOffsetY();
 454  
 455          // v:shape
 456          $objWriter->startElement('v:shape');
 457          $objWriter->writeAttribute('id', $pReference);
 458          $objWriter->writeAttribute('o:spid', '_x0000_s' . $id);
 459          $objWriter->writeAttribute('type', '#_x0000_t75');
 460          $objWriter->writeAttribute('style', "position:absolute;margin-left:{$marginLeft}px;margin-top:{$marginTop}px;width:{$width}px;height:{$height}px;z-index:1");
 461  
 462          // v:imagedata
 463          $objWriter->startElement('v:imagedata');
 464          $objWriter->writeAttribute('o:relid', 'rId' . $pReference);
 465          $objWriter->writeAttribute('o:title', $pImage->getName());
 466          $objWriter->endElement();
 467  
 468          // o:lock
 469          $objWriter->startElement('o:lock');
 470          $objWriter->writeAttribute('v:ext', 'edit');
 471          $objWriter->writeAttribute('textRotation', 't');
 472          $objWriter->endElement();
 473  
 474          $objWriter->endElement();
 475      }
 476  
 477      /**
 478       * Get an array of all drawings.
 479       *
 480       * @param Spreadsheet $spreadsheet
 481       *
 482       * @return \PhpOffice\PhpSpreadsheet\Worksheet\Drawing[] All drawings in PhpSpreadsheet
 483       */
 484      public function allDrawings(Spreadsheet $spreadsheet)
 485      {
 486          // Get an array of all drawings
 487          $aDrawings = [];
 488  
 489          // Loop through PhpSpreadsheet
 490          $sheetCount = $spreadsheet->getSheetCount();
 491          for ($i = 0; $i < $sheetCount; ++$i) {
 492              // Loop through images and add to array
 493              $iterator = $spreadsheet->getSheet($i)->getDrawingCollection()->getIterator();
 494              while ($iterator->valid()) {
 495                  $aDrawings[] = $iterator->current();
 496  
 497                  $iterator->next();
 498              }
 499          }
 500  
 501          return $aDrawings;
 502      }
 503  
 504      /**
 505       * @param XMLWriter $objWriter
 506       * @param null|int $hlinkClickId
 507       */
 508      private function writeHyperLinkDrawing(XMLWriter $objWriter, $hlinkClickId)
 509      {
 510          if ($hlinkClickId === null) {
 511              return;
 512          }
 513  
 514          $objWriter->startElement('a:hlinkClick');
 515          $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships');
 516          $objWriter->writeAttribute('r:id', 'rId' . $hlinkClickId);
 517          $objWriter->endElement();
 518      }
 519  }