Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 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 310 and 311] [Versions 310 and 400] [Versions 310 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 $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          // Set headers
 127          $this->globalFunctionsHelper->header('Content-Type: ' . static::$headerContentType);
 128          $this->globalFunctionsHelper->header('Content-Disposition: attachment; filename="' . $this->outputFilePath . '"');
 129  
 130          /*
 131           * When forcing the download of a file over SSL,IE8 and lower browsers fail
 132           * if the Cache-Control and Pragma headers are not set.
 133           *
 134           * @see http://support.microsoft.com/KB/323308
 135           * @see https://github.com/liuggio/ExcelBundle/issues/45
 136           */
 137          $this->globalFunctionsHelper->header('Cache-Control: max-age=0');
 138          $this->globalFunctionsHelper->header('Pragma: public');
 139  
 140          $this->openWriter();
 141          $this->isWriterOpened = true;
 142  
 143          return $this;
 144      }
 145  
 146      /**
 147       * Checks if the pointer to the file/stream to write to is available.
 148       * Will throw an exception if not available.
 149       *
 150       * @throws IOException If the pointer is not available
 151       * @return void
 152       */
 153      protected function throwIfFilePointerIsNotAvailable()
 154      {
 155          if (!$this->filePointer) {
 156              throw new IOException('File pointer has not be opened');
 157          }
 158      }
 159  
 160      /**
 161       * Checks if the writer has already been opened, since some actions must be done before it gets opened.
 162       * Throws an exception if already opened.
 163       *
 164       * @param string $message Error message
 165       * @throws WriterAlreadyOpenedException If the writer was already opened and must not be.
 166       * @return void
 167       */
 168      protected function throwIfWriterAlreadyOpened($message)
 169      {
 170          if ($this->isWriterOpened) {
 171              throw new WriterAlreadyOpenedException($message);
 172          }
 173      }
 174  
 175      /**
 176       * {@inheritdoc}
 177       */
 178      public function addRow(Row $row)
 179      {
 180          if ($this->isWriterOpened) {
 181              try {
 182                  $this->addRowToWriter($row);
 183              } catch (SpoutException $e) {
 184                  // if an exception occurs while writing data,
 185                  // close the writer and remove all files created so far.
 186                  $this->closeAndAttemptToCleanupAllFiles();
 187  
 188                  // re-throw the exception to alert developers of the error
 189                  throw $e;
 190              }
 191          } else {
 192              throw new WriterNotOpenedException('The writer needs to be opened before adding row.');
 193          }
 194  
 195          return $this;
 196      }
 197  
 198      /**
 199       * {@inheritdoc}
 200       */
 201      public function addRows(array $rows)
 202      {
 203          foreach ($rows as $row) {
 204              if (!$row instanceof Row) {
 205                  $this->closeAndAttemptToCleanupAllFiles();
 206                  throw new InvalidArgumentException('The input should be an array of Row');
 207              }
 208  
 209              $this->addRow($row);
 210          }
 211  
 212          return $this;
 213      }
 214  
 215      /**
 216       * {@inheritdoc}
 217       */
 218      public function close()
 219      {
 220          if (!$this->isWriterOpened) {
 221              return;
 222          }
 223  
 224          $this->closeWriter();
 225  
 226          if (is_resource($this->filePointer)) {
 227              $this->globalFunctionsHelper->fclose($this->filePointer);
 228          }
 229  
 230          $this->isWriterOpened = false;
 231      }
 232  
 233      /**
 234       * Closes the writer and attempts to cleanup all files that were
 235       * created during the writing process (temp files & final file).
 236       *
 237       * @return void
 238       */
 239      private function closeAndAttemptToCleanupAllFiles()
 240      {
 241          // close the writer, which should remove all temp files
 242          $this->close();
 243  
 244          // remove output file if it was created
 245          if ($this->globalFunctionsHelper->file_exists($this->outputFilePath)) {
 246              $outputFolderPath = dirname($this->outputFilePath);
 247              $fileSystemHelper = $this->helperFactory->createFileSystemHelper($outputFolderPath);
 248              $fileSystemHelper->deleteFile($this->outputFilePath);
 249          }
 250      }
 251  }