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 39 and 401]

   1  <?php
   2  
   3  namespace Box\Spout\Writer;
   4  
   5  use Box\Spout\Common\Creator\HelperFactory;
   6  use Box\Spout\Common\Entity\Row;
   7  use Box\Spout\Common\Entity\Style\Style;
   8  use Box\Spout\Common\Exception\InvalidArgumentException;
   9  use Box\Spout\Common\Exception\IOException;
  10  use Box\Spout\Common\Exception\SpoutException;
  11  use Box\Spout\Common\Helper\GlobalFunctionsHelper;
  12  use Box\Spout\Common\Manager\OptionsManagerInterface;
  13  use Box\Spout\Writer\Common\Entity\Options;
  14  use Box\Spout\Writer\Exception\WriterAlreadyOpenedException;
  15  use Box\Spout\Writer\Exception\WriterNotOpenedException;
  16  
  17  /**
  18   * Class WriterAbstract
  19   *
  20   * @abstract
  21   */
  22  abstract class WriterAbstract implements WriterInterface
  23  {
  24      /** @var string Path to the output file */
  25      protected $outputFilePath;
  26  
  27      /** @var resource Pointer to the file/stream we will write to */
  28      protected $filePointer;
  29  
  30      /** @var bool Indicates whether the writer has been opened or not */
  31      protected $isWriterOpened = false;
  32  
  33      /** @var GlobalFunctionsHelper Helper to work with global functions */
  34      protected $globalFunctionsHelper;
  35  
  36      /** @var HelperFactory */
  37      protected $helperFactory;
  38  
  39      /** @var OptionsManagerInterface Writer options manager */
  40      protected $optionsManager;
  41  
  42      /** @var string Content-Type value for the header - to be defined by child class */
  43      protected static $headerContentType;
  44  
  45      /**
  46       * @param OptionsManagerInterface $optionsManager
  47       * @param GlobalFunctionsHelper $globalFunctionsHelper
  48       * @param HelperFactory $helperFactory
  49       */
  50      public function __construct(
  51          OptionsManagerInterface $optionsManager,
  52          GlobalFunctionsHelper $globalFunctionsHelper,
  53          HelperFactory $helperFactory
  54      ) {
  55          $this->optionsManager = $optionsManager;
  56          $this->globalFunctionsHelper = $globalFunctionsHelper;
  57          $this->helperFactory = $helperFactory;
  58      }
  59  
  60      /**
  61       * Opens the streamer and makes it ready to accept data.
  62       *
  63       * @throws IOException If the writer cannot be opened
  64       * @return void
  65       */
  66      abstract protected function openWriter();
  67  
  68      /**
  69       * Adds a row to the currently opened writer.
  70       *
  71       * @param Row $row The row containing cells and styles
  72       * @throws WriterNotOpenedException If the workbook is not created yet
  73       * @throws IOException If unable to write data
  74       * @return void
  75       */
  76      abstract protected function addRowToWriter(Row $row);
  77  
  78      /**
  79       * Closes the streamer, preventing any additional writing.
  80       *
  81       * @return void
  82       */
  83      abstract protected function closeWriter();
  84  
  85      /**
  86       * {@inheritdoc}
  87       */
  88      public function setDefaultRowStyle(Style $defaultStyle)
  89      {
  90          $this->optionsManager->setOption(Options::DEFAULT_ROW_STYLE, $defaultStyle);
  91  
  92          return $this;
  93      }
  94  
  95      /**
  96       * {@inheritdoc}
  97       */
  98      public function openToFile($outputFilePath)
  99      {
 100          $this->outputFilePath = $outputFilePath;
 101  
 102          $this->filePointer = $this->globalFunctionsHelper->fopen($this->outputFilePath, 'wb+');
 103          $this->throwIfFilePointerIsNotAvailable();
 104  
 105          $this->openWriter();
 106          $this->isWriterOpened = true;
 107  
 108          return $this;
 109      }
 110  
 111      /**
 112       * @codeCoverageIgnore
 113       * {@inheritdoc}
 114       */
 115      public function openToBrowser($outputFileName)
 116      {
 117          $this->outputFilePath = $this->globalFunctionsHelper->basename($outputFileName);
 118  
 119          $this->filePointer = $this->globalFunctionsHelper->fopen('php://output', 'w');
 120          $this->throwIfFilePointerIsNotAvailable();
 121  
 122          // Clear any previous output (otherwise the generated file will be corrupted)
 123          // @see https://github.com/box/spout/issues/241
 124          $this->globalFunctionsHelper->ob_end_clean();
 125  
 126          /*
 127           * Set headers
 128           *
 129           * For newer browsers such as Firefox, Chrome, Opera, Safari, etc., they all support and use `filename*`
 130           * specified by the new standard, even if they do not automatically decode filename; it does not matter;
 131           * and for older versions of Internet Explorer, they are not recognized `filename*`, will automatically
 132           * ignore it and use the old `filename` (the only minor flaw is that there must be an English suffix name).
 133           * In this way, the multi-browser multi-language compatibility problem is perfectly solved, which does not
 134           * require UA judgment and is more in line with the standard.
 135           *
 136           * @see https://github.com/box/spout/issues/745
 137           * @see https://tools.ietf.org/html/rfc6266
 138           * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition
 139           */
 140          $this->globalFunctionsHelper->header('Content-Type: ' . static::$headerContentType);
 141          $this->globalFunctionsHelper->header(
 142              'Content-Disposition: attachment; ' .
 143              'filename="' . rawurlencode($this->outputFilePath) . '"; ' .
 144              'filename*=UTF-8\'\'' . rawurlencode($this->outputFilePath)
 145          );
 146  
 147          /*
 148           * When forcing the download of a file over SSL,IE8 and lower browsers fail
 149           * if the Cache-Control and Pragma headers are not set.
 150           *
 151           * @see http://support.microsoft.com/KB/323308
 152           * @see https://github.com/liuggio/ExcelBundle/issues/45
 153           */
 154          $this->globalFunctionsHelper->header('Cache-Control: max-age=0');
 155          $this->globalFunctionsHelper->header('Pragma: public');
 156  
 157          $this->openWriter();
 158          $this->isWriterOpened = true;
 159  
 160          return $this;
 161      }
 162  
 163      /**
 164       * Checks if the pointer to the file/stream to write to is available.
 165       * Will throw an exception if not available.
 166       *
 167       * @throws IOException If the pointer is not available
 168       * @return void
 169       */
 170      protected function throwIfFilePointerIsNotAvailable()
 171      {
 172          if (!$this->filePointer) {
 173              throw new IOException('File pointer has not be opened');
 174          }
 175      }
 176  
 177      /**
 178       * Checks if the writer has already been opened, since some actions must be done before it gets opened.
 179       * Throws an exception if already opened.
 180       *
 181       * @param string $message Error message
 182       * @throws WriterAlreadyOpenedException If the writer was already opened and must not be.
 183       * @return void
 184       */
 185      protected function throwIfWriterAlreadyOpened($message)
 186      {
 187          if ($this->isWriterOpened) {
 188              throw new WriterAlreadyOpenedException($message);
 189          }
 190      }
 191  
 192      /**
 193       * {@inheritdoc}
 194       */
 195      public function addRow(Row $row)
 196      {
 197          if ($this->isWriterOpened) {
 198              try {
 199                  $this->addRowToWriter($row);
 200              } catch (SpoutException $e) {
 201                  // if an exception occurs while writing data,
 202                  // close the writer and remove all files created so far.
 203                  $this->closeAndAttemptToCleanupAllFiles();
 204  
 205                  // re-throw the exception to alert developers of the error
 206                  throw $e;
 207              }
 208          } else {
 209              throw new WriterNotOpenedException('The writer needs to be opened before adding row.');
 210          }
 211  
 212          return $this;
 213      }
 214  
 215      /**
 216       * {@inheritdoc}
 217       */
 218      public function addRows(array $rows)
 219      {
 220          foreach ($rows as $row) {
 221              if (!$row instanceof Row) {
 222                  $this->closeAndAttemptToCleanupAllFiles();
 223                  throw new InvalidArgumentException('The input should be an array of Row');
 224              }
 225  
 226              $this->addRow($row);
 227          }
 228  
 229          return $this;
 230      }
 231  
 232      /**
 233       * {@inheritdoc}
 234       */
 235      public function close()
 236      {
 237          if (!$this->isWriterOpened) {
 238              return;
 239          }
 240  
 241          $this->closeWriter();
 242  
 243          if (\is_resource($this->filePointer)) {
 244              $this->globalFunctionsHelper->fclose($this->filePointer);
 245          }
 246  
 247          $this->isWriterOpened = false;
 248      }
 249  
 250      /**
 251       * Closes the writer and attempts to cleanup all files that were
 252       * created during the writing process (temp files & final file).
 253       *
 254       * @return void
 255       */
 256      private function closeAndAttemptToCleanupAllFiles()
 257      {
 258          // close the writer, which should remove all temp files
 259          $this->close();
 260  
 261          // remove output file if it was created
 262          if ($this->globalFunctionsHelper->file_exists($this->outputFilePath)) {
 263              $outputFolderPath = \dirname($this->outputFilePath);
 264              $fileSystemHelper = $this->helperFactory->createFileSystemHelper($outputFolderPath);
 265              $fileSystemHelper->deleteFile($this->outputFilePath);
 266          }
 267      }
 268  }