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;
   4  
   5  use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
   6  use PhpOffice\PhpSpreadsheet\Spreadsheet;
   7  
   8  class Csv extends BaseWriter
   9  {
  10      /**
  11       * PhpSpreadsheet object.
  12       *
  13       * @var Spreadsheet
  14       */
  15      private $spreadsheet;
  16  
  17      /**
  18       * Delimiter.
  19       *
  20       * @var string
  21       */
  22      private $delimiter = ',';
  23  
  24      /**
  25       * Enclosure.
  26       *
  27       * @var string
  28       */
  29      private $enclosure = '"';
  30  
  31      /**
  32       * Line ending.
  33       *
  34       * @var string
  35       */
  36      private $lineEnding = PHP_EOL;
  37  
  38      /**
  39       * Sheet index to write.
  40       *
  41       * @var int
  42       */
  43      private $sheetIndex = 0;
  44  
  45      /**
  46       * Whether to write a BOM (for UTF8).
  47       *
  48       * @var bool
  49       */
  50      private $useBOM = false;
  51  
  52      /**
  53       * Whether to write a Separator line as the first line of the file
  54       *     sep=x.
  55       *
  56       * @var bool
  57       */
  58      private $includeSeparatorLine = false;
  59  
  60      /**
  61       * Whether to write a fully Excel compatible CSV file.
  62       *
  63       * @var bool
  64       */
  65      private $excelCompatibility = false;
  66  
  67      /**
  68       * Create a new CSV.
  69       *
  70       * @param Spreadsheet $spreadsheet Spreadsheet object
  71       */
  72      public function __construct(Spreadsheet $spreadsheet)
  73      {
  74          $this->spreadsheet = $spreadsheet;
  75      }
  76  
  77      /**
  78       * Save PhpSpreadsheet to file.
  79       *
  80       * @param resource|string $pFilename
  81       */
  82      public function save($pFilename): void
  83      {
  84          // Fetch sheet
  85          $sheet = $this->spreadsheet->getSheet($this->sheetIndex);
  86  
  87          $saveDebugLog = Calculation::getInstance($this->spreadsheet)->getDebugLog()->getWriteDebugLog();
  88          Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog(false);
  89          $saveArrayReturnType = Calculation::getArrayReturnType();
  90          Calculation::setArrayReturnType(Calculation::RETURN_ARRAY_AS_VALUE);
  91  
  92          // Open file
  93          $this->openFileHandle($pFilename);
  94  
  95          if ($this->excelCompatibility) {
  96              $this->setUseBOM(true); //  Enforce UTF-8 BOM Header
  97              $this->setIncludeSeparatorLine(true); //  Set separator line
  98              $this->setEnclosure('"'); //  Set enclosure to "
  99              $this->setDelimiter(';'); //  Set delimiter to a semi-colon
 100              $this->setLineEnding("\r\n");
 101          }
 102  
 103          if ($this->useBOM) {
 104              // Write the UTF-8 BOM code if required
 105              fwrite($this->fileHandle, "\xEF\xBB\xBF");
 106          }
 107  
 108          if ($this->includeSeparatorLine) {
 109              // Write the separator line if required
 110              fwrite($this->fileHandle, 'sep=' . $this->getDelimiter() . $this->lineEnding);
 111          }
 112  
 113          //    Identify the range that we need to extract from the worksheet
 114          $maxCol = $sheet->getHighestDataColumn();
 115          $maxRow = $sheet->getHighestDataRow();
 116  
 117          // Write rows to file
 118          for ($row = 1; $row <= $maxRow; ++$row) {
 119              // Convert the row to an array...
 120              $cellsArray = $sheet->rangeToArray('A' . $row . ':' . $maxCol . $row, '', $this->preCalculateFormulas);
 121              // ... and write to the file
 122              $this->writeLine($this->fileHandle, $cellsArray[0]);
 123          }
 124  
 125          $this->maybeCloseFileHandle();
 126          Calculation::setArrayReturnType($saveArrayReturnType);
 127          Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog($saveDebugLog);
 128      }
 129  
 130      /**
 131       * Get delimiter.
 132       *
 133       * @return string
 134       */
 135      public function getDelimiter()
 136      {
 137          return $this->delimiter;
 138      }
 139  
 140      /**
 141       * Set delimiter.
 142       *
 143       * @param string $pValue Delimiter, defaults to ','
 144       *
 145       * @return $this
 146       */
 147      public function setDelimiter($pValue)
 148      {
 149          $this->delimiter = $pValue;
 150  
 151          return $this;
 152      }
 153  
 154      /**
 155       * Get enclosure.
 156       *
 157       * @return string
 158       */
 159      public function getEnclosure()
 160      {
 161          return $this->enclosure;
 162      }
 163  
 164      /**
 165       * Set enclosure.
 166       *
 167       * @param string $pValue Enclosure, defaults to "
 168       *
 169       * @return $this
 170       */
 171      public function setEnclosure($pValue = '"')
 172      {
 173          $this->enclosure = $pValue;
 174  
 175          return $this;
 176      }
 177  
 178      /**
 179       * Get line ending.
 180       *
 181       * @return string
 182       */
 183      public function getLineEnding()
 184      {
 185          return $this->lineEnding;
 186      }
 187  
 188      /**
 189       * Set line ending.
 190       *
 191       * @param string $pValue Line ending, defaults to OS line ending (PHP_EOL)
 192       *
 193       * @return $this
 194       */
 195      public function setLineEnding($pValue)
 196      {
 197          $this->lineEnding = $pValue;
 198  
 199          return $this;
 200      }
 201  
 202      /**
 203       * Get whether BOM should be used.
 204       *
 205       * @return bool
 206       */
 207      public function getUseBOM()
 208      {
 209          return $this->useBOM;
 210      }
 211  
 212      /**
 213       * Set whether BOM should be used.
 214       *
 215       * @param bool $pValue Use UTF-8 byte-order mark? Defaults to false
 216       *
 217       * @return $this
 218       */
 219      public function setUseBOM($pValue)
 220      {
 221          $this->useBOM = $pValue;
 222  
 223          return $this;
 224      }
 225  
 226      /**
 227       * Get whether a separator line should be included.
 228       *
 229       * @return bool
 230       */
 231      public function getIncludeSeparatorLine()
 232      {
 233          return $this->includeSeparatorLine;
 234      }
 235  
 236      /**
 237       * Set whether a separator line should be included as the first line of the file.
 238       *
 239       * @param bool $pValue Use separator line? Defaults to false
 240       *
 241       * @return $this
 242       */
 243      public function setIncludeSeparatorLine($pValue)
 244      {
 245          $this->includeSeparatorLine = $pValue;
 246  
 247          return $this;
 248      }
 249  
 250      /**
 251       * Get whether the file should be saved with full Excel Compatibility.
 252       *
 253       * @return bool
 254       */
 255      public function getExcelCompatibility()
 256      {
 257          return $this->excelCompatibility;
 258      }
 259  
 260      /**
 261       * Set whether the file should be saved with full Excel Compatibility.
 262       *
 263       * @param bool $pValue Set the file to be written as a fully Excel compatible csv file
 264       *                                Note that this overrides other settings such as useBOM, enclosure and delimiter
 265       *
 266       * @return $this
 267       */
 268      public function setExcelCompatibility($pValue)
 269      {
 270          $this->excelCompatibility = $pValue;
 271  
 272          return $this;
 273      }
 274  
 275      /**
 276       * Get sheet index.
 277       *
 278       * @return int
 279       */
 280      public function getSheetIndex()
 281      {
 282          return $this->sheetIndex;
 283      }
 284  
 285      /**
 286       * Set sheet index.
 287       *
 288       * @param int $pValue Sheet index
 289       *
 290       * @return $this
 291       */
 292      public function setSheetIndex($pValue)
 293      {
 294          $this->sheetIndex = $pValue;
 295  
 296          return $this;
 297      }
 298  
 299      private $enclosureRequired = true;
 300  
 301      public function setEnclosureRequired(bool $value): self
 302      {
 303          $this->enclosureRequired = $value;
 304  
 305          return $this;
 306      }
 307  
 308      public function getEnclosureRequired(): bool
 309      {
 310          return $this->enclosureRequired;
 311      }
 312  
 313      /**
 314       * Write line to CSV file.
 315       *
 316       * @param resource $pFileHandle PHP filehandle
 317       * @param array $pValues Array containing values in a row
 318       */
 319      private function writeLine($pFileHandle, array $pValues): void
 320      {
 321          // No leading delimiter
 322          $delimiter = '';
 323  
 324          // Build the line
 325          $line = '';
 326  
 327          foreach ($pValues as $element) {
 328              // Add delimiter
 329              $line .= $delimiter;
 330              $delimiter = $this->delimiter;
 331              // Escape enclosures
 332              $enclosure = $this->enclosure;
 333              if ($enclosure) {
 334                  // If enclosure is not required, use enclosure only if
 335                  // element contains newline, delimiter, or enclosure.
 336                  if (!$this->enclosureRequired && strpbrk($element, "$delimiter$enclosure\n") === false) {
 337                      $enclosure = '';
 338                  } else {
 339                      $element = str_replace($enclosure, $enclosure . $enclosure, $element);
 340                  }
 341              }
 342              // Add enclosed string
 343              $line .= $enclosure . $element . $enclosure;
 344          }
 345  
 346          // Add line ending
 347          $line .= $this->lineEnding;
 348  
 349          // Write to file
 350          fwrite($pFileHandle, $line);
 351      }
 352  }