Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.

Differences Between: [Versions 310 and 400] [Versions 311 and 400] [Versions 39 and 400] [Versions 400 and 402] [Versions 400 and 403]

   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       * Output encoding.
  69       *
  70       * @var string
  71       */
  72      private $outputEncoding = '';
  73  
  74      /**
  75       * Create a new CSV.
  76       *
  77       * @param Spreadsheet $spreadsheet Spreadsheet object
  78       */
  79      public function __construct(Spreadsheet $spreadsheet)
  80      {
  81          $this->spreadsheet = $spreadsheet;
  82      }
  83  
  84      /**
  85       * Save PhpSpreadsheet to file.
  86       *
  87       * @param resource|string $filename
  88       */
  89      public function save($filename, int $flags = 0): void
  90      {
  91          $this->processFlags($flags);
  92  
  93          // Fetch sheet
  94          $sheet = $this->spreadsheet->getSheet($this->sheetIndex);
  95  
  96          $saveDebugLog = Calculation::getInstance($this->spreadsheet)->getDebugLog()->getWriteDebugLog();
  97          Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog(false);
  98          $saveArrayReturnType = Calculation::getArrayReturnType();
  99          Calculation::setArrayReturnType(Calculation::RETURN_ARRAY_AS_VALUE);
 100  
 101          // Open file
 102          $this->openFileHandle($filename);
 103  
 104          if ($this->excelCompatibility) {
 105              $this->setUseBOM(true); //  Enforce UTF-8 BOM Header
 106              $this->setIncludeSeparatorLine(true); //  Set separator line
 107              $this->setEnclosure('"'); //  Set enclosure to "
 108              $this->setDelimiter(';'); //  Set delimiter to a semi-colon
 109              $this->setLineEnding("\r\n");
 110          }
 111  
 112          if ($this->useBOM) {
 113              // Write the UTF-8 BOM code if required
 114              fwrite($this->fileHandle, "\xEF\xBB\xBF");
 115          }
 116  
 117          if ($this->includeSeparatorLine) {
 118              // Write the separator line if required
 119              fwrite($this->fileHandle, 'sep=' . $this->getDelimiter() . $this->lineEnding);
 120          }
 121  
 122          //    Identify the range that we need to extract from the worksheet
 123          $maxCol = $sheet->getHighestDataColumn();
 124          $maxRow = $sheet->getHighestDataRow();
 125  
 126          // Write rows to file
 127          for ($row = 1; $row <= $maxRow; ++$row) {
 128              // Convert the row to an array...
 129              $cellsArray = $sheet->rangeToArray('A' . $row . ':' . $maxCol . $row, '', $this->preCalculateFormulas);
 130              // ... and write to the file
 131              $this->writeLine($this->fileHandle, $cellsArray[0]);
 132          }
 133  
 134          $this->maybeCloseFileHandle();
 135          Calculation::setArrayReturnType($saveArrayReturnType);
 136          Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog($saveDebugLog);
 137      }
 138  
 139      /**
 140       * Get delimiter.
 141       *
 142       * @return string
 143       */
 144      public function getDelimiter()
 145      {
 146          return $this->delimiter;
 147      }
 148  
 149      /**
 150       * Set delimiter.
 151       *
 152       * @param string $delimiter Delimiter, defaults to ','
 153       *
 154       * @return $this
 155       */
 156      public function setDelimiter($delimiter)
 157      {
 158          $this->delimiter = $delimiter;
 159  
 160          return $this;
 161      }
 162  
 163      /**
 164       * Get enclosure.
 165       *
 166       * @return string
 167       */
 168      public function getEnclosure()
 169      {
 170          return $this->enclosure;
 171      }
 172  
 173      /**
 174       * Set enclosure.
 175       *
 176       * @param string $enclosure Enclosure, defaults to "
 177       *
 178       * @return $this
 179       */
 180      public function setEnclosure($enclosure = '"')
 181      {
 182          $this->enclosure = $enclosure;
 183  
 184          return $this;
 185      }
 186  
 187      /**
 188       * Get line ending.
 189       *
 190       * @return string
 191       */
 192      public function getLineEnding()
 193      {
 194          return $this->lineEnding;
 195      }
 196  
 197      /**
 198       * Set line ending.
 199       *
 200       * @param string $lineEnding Line ending, defaults to OS line ending (PHP_EOL)
 201       *
 202       * @return $this
 203       */
 204      public function setLineEnding($lineEnding)
 205      {
 206          $this->lineEnding = $lineEnding;
 207  
 208          return $this;
 209      }
 210  
 211      /**
 212       * Get whether BOM should be used.
 213       *
 214       * @return bool
 215       */
 216      public function getUseBOM()
 217      {
 218          return $this->useBOM;
 219      }
 220  
 221      /**
 222       * Set whether BOM should be used.
 223       *
 224       * @param bool $useBOM Use UTF-8 byte-order mark? Defaults to false
 225       *
 226       * @return $this
 227       */
 228      public function setUseBOM($useBOM)
 229      {
 230          $this->useBOM = $useBOM;
 231  
 232          return $this;
 233      }
 234  
 235      /**
 236       * Get whether a separator line should be included.
 237       *
 238       * @return bool
 239       */
 240      public function getIncludeSeparatorLine()
 241      {
 242          return $this->includeSeparatorLine;
 243      }
 244  
 245      /**
 246       * Set whether a separator line should be included as the first line of the file.
 247       *
 248       * @param bool $includeSeparatorLine Use separator line? Defaults to false
 249       *
 250       * @return $this
 251       */
 252      public function setIncludeSeparatorLine($includeSeparatorLine)
 253      {
 254          $this->includeSeparatorLine = $includeSeparatorLine;
 255  
 256          return $this;
 257      }
 258  
 259      /**
 260       * Get whether the file should be saved with full Excel Compatibility.
 261       *
 262       * @return bool
 263       */
 264      public function getExcelCompatibility()
 265      {
 266          return $this->excelCompatibility;
 267      }
 268  
 269      /**
 270       * Set whether the file should be saved with full Excel Compatibility.
 271       *
 272       * @param bool $excelCompatibility Set the file to be written as a fully Excel compatible csv file
 273       *                                Note that this overrides other settings such as useBOM, enclosure and delimiter
 274       *
 275       * @return $this
 276       */
 277      public function setExcelCompatibility($excelCompatibility)
 278      {
 279          $this->excelCompatibility = $excelCompatibility;
 280  
 281          return $this;
 282      }
 283  
 284      /**
 285       * Get sheet index.
 286       *
 287       * @return int
 288       */
 289      public function getSheetIndex()
 290      {
 291          return $this->sheetIndex;
 292      }
 293  
 294      /**
 295       * Set sheet index.
 296       *
 297       * @param int $sheetIndex Sheet index
 298       *
 299       * @return $this
 300       */
 301      public function setSheetIndex($sheetIndex)
 302      {
 303          $this->sheetIndex = $sheetIndex;
 304  
 305          return $this;
 306      }
 307  
 308      /**
 309       * Get output encoding.
 310       *
 311       * @return string
 312       */
 313      public function getOutputEncoding()
 314      {
 315          return $this->outputEncoding;
 316      }
 317  
 318      /**
 319       * Set output encoding.
 320       *
 321       * @param string $outputEnconding Output encoding
 322       *
 323       * @return $this
 324       */
 325      public function setOutputEncoding($outputEnconding)
 326      {
 327          $this->outputEncoding = $outputEnconding;
 328  
 329          return $this;
 330      }
 331  
 332      /** @var bool */
 333      private $enclosureRequired = true;
 334  
 335      public function setEnclosureRequired(bool $value): self
 336      {
 337          $this->enclosureRequired = $value;
 338  
 339          return $this;
 340      }
 341  
 342      public function getEnclosureRequired(): bool
 343      {
 344          return $this->enclosureRequired;
 345      }
 346  
 347      /**
 348       * Convert boolean to TRUE/FALSE; otherwise return element cast to string.
 349       *
 350       * @param mixed $element
 351       */
 352      private static function elementToString($element): string
 353      {
 354          if (is_bool($element)) {
 355              return $element ? 'TRUE' : 'FALSE';
 356          }
 357  
 358          return (string) $element;
 359      }
 360  
 361      /**
 362       * Write line to CSV file.
 363       *
 364       * @param resource $fileHandle PHP filehandle
 365       * @param array $values Array containing values in a row
 366       */
 367      private function writeLine($fileHandle, array $values): void
 368      {
 369          // No leading delimiter
 370          $delimiter = '';
 371  
 372          // Build the line
 373          $line = '';
 374  
 375          foreach ($values as $element) {
 376              $element = self::elementToString($element);
 377              // Add delimiter
 378              $line .= $delimiter;
 379              $delimiter = $this->delimiter;
 380              // Escape enclosures
 381              $enclosure = $this->enclosure;
 382              if ($enclosure) {
 383                  // If enclosure is not required, use enclosure only if
 384                  // element contains newline, delimiter, or enclosure.
 385                  if (!$this->enclosureRequired && strpbrk($element, "$delimiter$enclosure\n") === false) {
 386                      $enclosure = '';
 387                  } else {
 388                      $element = str_replace($enclosure, $enclosure . $enclosure, $element);
 389                  }
 390              }
 391              // Add enclosed string
 392              $line .= $enclosure . $element . $enclosure;
 393          }
 394  
 395          // Add line ending
 396          $line .= $this->lineEnding;
 397  
 398          // Write to file
 399          if ($this->outputEncoding != '') {
 400              $line = mb_convert_encoding($line, $this->outputEncoding);
 401          }
 402          fwrite($fileHandle, $line);
 403      }
 404  }