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