PluginProbe
TablePress – Tables in WordPress made easy / 3.4
TablePress – Tables in WordPress made easy v3.4
3.4 3.3.4 3.3.3 3.3.2 3.3.1 trunk 1.12 1.14 1.9.2 2.0.4 2.1.7 2.1.8 2.2 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.3 2.3.1 2.3.2 2.4 2.4.1 2.4.2 2.4.3 All 45 releases
tablepress / libraries / vendor / PhpSpreadsheet / Reader / Csv.php

Csv.php in TablePress – Tables in WordPress made easy 3.4, at libraries/vendor/PhpSpreadsheet/Reader/Csv.php

809 lines 21.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace TablePress\PhpOffice\PhpSpreadsheet\Reader;
4
5 use TablePress\PhpOffice\PhpSpreadsheet\Calculation\Calculation;
6 use TablePress\PhpOffice\PhpSpreadsheet\Cell\Cell;
7 use TablePress\PhpOffice\PhpSpreadsheet\Cell\Coordinate;
8 use TablePress\PhpOffice\PhpSpreadsheet\Reader\Csv\Delimiter;
9 use TablePress\PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException;
10 use TablePress\PhpOffice\PhpSpreadsheet\Shared\StringHelper;
11 use TablePress\PhpOffice\PhpSpreadsheet\Spreadsheet;
12 use TablePress\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
13 use Throwable;
14
15 class Csv extends BaseReader
16 {
17 const DEFAULT_FALLBACK_ENCODING = 'CP1252';
18 const GUESS_ENCODING = 'guess';
19 const UTF8_BOM = "\xEF\xBB\xBF";
20 const UTF8_BOM_LEN = 3;
21 const UTF16BE_BOM = "\xfe\xff";
22 const UTF16BE_BOM_LEN = 2;
23 const UTF16BE_LF = "\x00\x0a";
24 const UTF16LE_BOM = "\xff\xfe";
25 const UTF16LE_BOM_LEN = 2;
26 const UTF16LE_LF = "\x0a\x00";
27 const UTF32BE_BOM = "\x00\x00\xfe\xff";
28 const UTF32BE_BOM_LEN = 4;
29 const UTF32BE_LF = "\x00\x00\x00\x0a";
30 const UTF32LE_BOM = "\xff\xfe\x00\x00";
31 const UTF32LE_BOM_LEN = 4;
32 const UTF32LE_LF = "\x0a\x00\x00\x00";
33
34 /**
35 * Input encoding.
36 */
37 protected string $inputEncoding = 'UTF-8';
38
39 /**
40 * Fallback encoding if guess strikes out.
41 */
42 protected string $fallbackEncoding = self::DEFAULT_FALLBACK_ENCODING;
43
44 /**
45 * Delimiter.
46 */
47 protected ?string $delimiter = null;
48
49 /**
50 * Enclosure.
51 */
52 protected string $enclosure = '"';
53
54 /**
55 * Sheet index to read.
56 */
57 protected int $sheetIndex = 0;
58
59 /**
60 * Load rows contiguously.
61 */
62 protected bool $contiguous = false;
63
64 /**
65 * The character that can escape the enclosure.
66 * This will probably become unsupported in Php 9.
67 * Not yet ready to mark deprecated in order to give users
68 * a migration path.
69 * Strongly encourage that this value be set to null-string.
70 */
71 protected ?string $escapeCharacter = null;
72
73 /**
74 * Callback for setting defaults in construction.
75 *
76 * @var ?callable
77 */
78 protected static $constructorCallback;
79
80 /** Changed from true to false in release 4.0.0 */
81 public const DEFAULT_TEST_AUTODETECT = false;
82
83 /**
84 * Attempt autodetect line endings (deprecated after PHP8.1)?
85 */
86 protected bool $testAutodetect = self::DEFAULT_TEST_AUTODETECT;
87
88 protected bool $castFormattedNumberToNumeric = false;
89
90 protected bool $preserveNumericFormatting = false;
91
92 protected bool $preserveNullString = false;
93
94 protected bool $sheetNameIsFileName = false;
95
96 protected ?string $getTrue = null;
97
98 protected ?string $getFalse = null;
99
100 protected ?string $thousandsSeparator = null;
101
102 protected ?string $decimalSeparator = null;
103
104 /**
105 * Create a new CSV Reader instance.
106 */
107 public function __construct()
108 {
109 parent::__construct();
110 $callback = static::$constructorCallback;
111 if ($callback !== null) {
112 $callback($this);
113 }
114 }
115
116 /**
117 * Set a callback to change the defaults.
118 *
119 * The callback must accept the Csv Reader object as the first parameter,
120 * and it should return void.
121 */
122 public static function setConstructorCallback(?callable $callback): void
123 {
124 static::$constructorCallback = $callback;
125 }
126
127 public static function getConstructorCallback(): ?callable
128 {
129 return static::$constructorCallback;
130 }
131
132 public function setInputEncoding(string $encoding): self
133 {
134 $this->inputEncoding = $encoding;
135
136 return $this;
137 }
138
139 public function getInputEncoding(): string
140 {
141 return $this->inputEncoding;
142 }
143
144 public function setFallbackEncoding(string $fallbackEncoding): self
145 {
146 $this->fallbackEncoding = $fallbackEncoding;
147
148 return $this;
149 }
150
151 public function getFallbackEncoding(): string
152 {
153 return $this->fallbackEncoding;
154 }
155
156 /**
157 * Move filepointer past any BOM marker.
158 */
159 protected function skipBOM(): void
160 {
161 rewind($this->fileHandle);
162
163 if (fgets($this->fileHandle, self::UTF8_BOM_LEN + 1) !== self::UTF8_BOM) {
164 rewind($this->fileHandle);
165 }
166 }
167
168 /**
169 * Identify any separator that is explicitly set in the file.
170 */
171 protected function checkSeparator(): void
172 {
173 $line = fgets($this->fileHandle);
174 if ($line === false) {
175 return;
176 }
177
178 if ((strlen(trim($line, "\r\n")) == 5) && (stripos($line, 'sep=') === 0)) {
179 $this->delimiter = (string) substr($line, 4, 1);
180
181 return;
182 }
183
184 $this->skipBOM();
185 }
186
187 /**
188 * Infer the separator if it isn't explicitly set in the file or specified by the user.
189 */
190 protected function inferSeparator(): void
191 {
192 $temp = $this->delimiter;
193 if ($temp !== null) {
194 return;
195 }
196
197 $inferenceEngine = new Delimiter($this->fileHandle, $this->getEscapeCharacter(), $this->enclosure);
198
199 // If number of lines is 0, nothing to infer : fall back to the default
200 if ($inferenceEngine->linesCounted() === 0) {
201 $this->delimiter = $inferenceEngine->getDefaultDelimiter();
202 $this->skipBOM();
203
204 return;
205 }
206
207 $this->delimiter = $inferenceEngine->infer();
208
209 // If no delimiter could be detected, fall back to the default
210 if ($this->delimiter === null) {
211 $this->delimiter = $inferenceEngine->getDefaultDelimiter();
212 }
213
214 $this->skipBOM();
215 }
216
217 /**
218 * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns).
219 *
220 * @return array<int, array{worksheetName: string, lastColumnLetter: string, lastColumnIndex: int, totalRows: int, totalColumns: int, sheetState: string}>
221 */
222 public function listWorksheetInfo(string $filename): array
223 {
224 // Open file
225 $this->openFileOrMemory($filename);
226 $fileHandle = $this->fileHandle;
227
228 // Skip BOM, if any
229 $this->skipBOM();
230 $this->checkSeparator();
231 $this->inferSeparator();
232
233 $worksheetInfo = [
234 [
235 'worksheetName' => 'Worksheet',
236 'lastColumnLetter' => 'A',
237 'lastColumnIndex' => 0,
238 'totalRows' => 0,
239 'totalColumns' => 0,
240 ],
241 ];
242 $delimiter = $this->delimiter ?? '';
243
244 // Loop through each line of the file in turn
245 $rowData = self::getCsv($fileHandle, 0, $delimiter, $this->enclosure, $this->escapeCharacter);
246 while (is_array($rowData)) {
247 ++$worksheetInfo[0]['totalRows'];
248 $worksheetInfo[0]['lastColumnIndex'] = max($worksheetInfo[0]['lastColumnIndex'], count($rowData) - 1);
249 $rowData = self::getCsv($fileHandle, 0, $delimiter, $this->enclosure, $this->escapeCharacter);
250 }
251
252 $worksheetInfo[0]['lastColumnLetter'] = Coordinate::stringFromColumnIndex($worksheetInfo[0]['lastColumnIndex'] + 1, true);
253 $worksheetInfo[0]['totalColumns'] = $worksheetInfo[0]['lastColumnIndex'] + 1;
254 $worksheetInfo[0]['sheetState'] = Worksheet::SHEETSTATE_VISIBLE;
255
256 // Close file
257 fclose($fileHandle);
258
259 return $worksheetInfo;
260 }
261
262 /**
263 * Loads Spreadsheet from file.
264 */
265 protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
266 {
267 $spreadsheet = $this->newSpreadsheet();
268 $spreadsheet->setValueBinder($this->valueBinder);
269
270 // Load into this instance
271 return $this->loadIntoExisting($filename, $spreadsheet);
272 }
273
274 /**
275 * Loads Spreadsheet from string.
276 */
277 public function loadSpreadsheetFromString(string $contents): Spreadsheet
278 {
279 $spreadsheet = $this->newSpreadsheet();
280 $spreadsheet->setValueBinder($this->valueBinder);
281
282 // Load into this instance
283 return $this->loadStringOrFile('data://text/plain,' . urlencode($contents), $spreadsheet, true);
284 }
285
286 private function openFileOrMemory(string $filename): void
287 {
288 // Open file
289 $fhandle = $this->canRead($filename);
290 if (!$fhandle) {
291 throw new ReaderException($filename . ' is an Invalid Spreadsheet file.');
292 }
293 if ($this->inputEncoding === 'UTF-8') {
294 $encoding = self::guessEncodingBom($filename);
295 if ($encoding !== '') {
296 $this->inputEncoding = $encoding;
297 }
298 }
299 if ($this->inputEncoding === self::GUESS_ENCODING) {
300 $this->inputEncoding = self::guessEncoding($filename, $this->fallbackEncoding);
301 }
302 $this->openFile($filename);
303 if ($this->inputEncoding !== 'UTF-8') {
304 $this->convertNonUtf8($filename);
305 }
306 }
307
308 protected function convertNonUtf8(string $filename): void
309 {
310 fclose($this->fileHandle);
311 $entireFile = file_get_contents($filename);
312 if ($entireFile === false) {
313 throw new ReaderException("Unable to get contents of $filename"); // @codeCoverageIgnore
314 }
315 $fileHandle = fopen('php://memory', 'r+b');
316 if ($fileHandle === false) {
317 throw new ReaderException('Unable to open php://memory'); // @codeCoverageIgnore
318 }
319 $this->fileHandle = $fileHandle;
320 $data = StringHelper::convertEncoding($entireFile, 'UTF-8', $this->inputEncoding);
321 fwrite($this->fileHandle, $data);
322 $this->skipBOM();
323 }
324
325 public function setTestAutoDetect(bool $value): self
326 {
327 $this->testAutodetect = $value;
328
329 return $this;
330 }
331
332 private function setAutoDetect(?string $value, int $version = PHP_VERSION_ID): ?string
333 {
334 $retVal = null;
335 if ($value !== null && $this->testAutodetect && $version < 90000) {
336 $retVal2 = @ini_set('auto_detect_line_endings', $value);
337 if (is_string($retVal2)) {
338 $retVal = $retVal2;
339 }
340 }
341
342 return $retVal;
343 }
344
345 public function castFormattedNumberToNumeric(
346 bool $castFormattedNumberToNumeric,
347 bool $preserveNumericFormatting = false
348 ): void {
349 $this->castFormattedNumberToNumeric = $castFormattedNumberToNumeric;
350 $this->preserveNumericFormatting = $preserveNumericFormatting;
351 }
352
353 /**
354 * Open data uri for reading.
355 */
356 private function openDataUri(string $filename): void
357 {
358 $fileHandle = fopen($filename, 'rb');
359 if ($fileHandle === false) {
360 // @codeCoverageIgnoreStart
361 throw new ReaderException('Could not open file ' . $filename . ' for reading.');
362 // @codeCoverageIgnoreEnd
363 }
364
365 $this->fileHandle = $fileHandle;
366 }
367
368 /**
369 * Loads PhpSpreadsheet from file into PhpSpreadsheet instance.
370 */
371 public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet): Spreadsheet
372 {
373 return $this->loadStringOrFile($filename, $spreadsheet, false);
374 }
375
376 /**
377 * Loads PhpSpreadsheet from file into PhpSpreadsheet instance.
378 */
379 private function loadStringOrFile(string $filename, Spreadsheet $spreadsheet, bool $dataUri): Spreadsheet
380 {
381 // Deprecated in Php8.1
382 $iniset = $this->setAutoDetect('1');
383
384 try {
385 $this->loadStringOrFile2($filename, $spreadsheet, $dataUri);
386 $this->setAutoDetect($iniset);
387 } catch (Throwable $e) {
388 $this->setAutoDetect($iniset);
389
390 throw $e;
391 }
392
393 return $spreadsheet;
394 }
395
396 private function loadStringOrFile2(string $filename, Spreadsheet $spreadsheet, bool $dataUri): void
397 {
398
399 // Open file
400 if ($dataUri) {
401 $this->openDataUri($filename);
402 } else {
403 $this->openFileOrMemory($filename);
404 }
405 $fileHandle = $this->fileHandle;
406
407 // Skip BOM, if any
408 $this->skipBOM();
409 $this->checkSeparator();
410 $this->inferSeparator();
411
412 // Create new PhpSpreadsheet object
413 while ($spreadsheet->getSheetCount() <= $this->sheetIndex) {
414 $spreadsheet->createSheet();
415 }
416 $sheet = $spreadsheet->setActiveSheetIndex($this->sheetIndex);
417 if ($this->sheetNameIsFileName) {
418 $sheet->setTitle(substr(basename($filename, '.csv'), 0, Worksheet::SHEET_TITLE_MAXIMUM_LENGTH));
419 }
420
421 // Set our starting row based on whether we're in contiguous mode or not
422 $currentRow = 1;
423 $outRow = 0;
424
425 // Loop through each line of the file in turn
426 $delimiter = $this->delimiter ?? '';
427 $rowData = self::getCsv($fileHandle, 0, $delimiter, $this->enclosure, $this->escapeCharacter);
428 $valueBinder = $this->valueBinder ?? Cell::getValueBinder();
429 $preserveBooleanString = method_exists($valueBinder, 'getBooleanConversion') && $valueBinder->getBooleanConversion();
430 $this->getTrue ??= Calculation::getTRUE();
431 $this->getFalse ??= Calculation::getFALSE();
432 $this->thousandsSeparator ??= StringHelper::getThousandsSeparator();
433 $this->decimalSeparator ??= StringHelper::getDecimalSeparator();
434 while (is_array($rowData)) {
435 $noOutputYet = true;
436 $columnLetter = 'A';
437 foreach ($rowData as $rowDatum) {
438 if ($preserveBooleanString) {
439 $rowDatum = $rowDatum ?? '';
440 } else {
441 $this->convertBoolean($rowDatum);
442 }
443 $numberFormatMask = $this->castFormattedNumberToNumeric ? $this->convertFormattedNumber($rowDatum) : '';
444 if (($rowDatum !== '' || $this->preserveNullString) && $this->readFilter->readCell($columnLetter, $currentRow)) {
445 if ($this->contiguous) {
446 if ($noOutputYet) {
447 $noOutputYet = false;
448 ++$outRow;
449 }
450 } else {
451 $outRow = $currentRow;
452 }
453 // Set basic styling for the value (Note that this could be overloaded by styling in a value binder)
454 if ($numberFormatMask !== '') {
455 $sheet->getStyle($columnLetter . $outRow)
456 ->getNumberFormat()
457 ->setFormatCode($numberFormatMask);
458 }
459 // Set cell value
460 $sheet->getCell($columnLetter . $outRow)->setValue($rowDatum);
461 }
462 StringHelper::stringIncrement($columnLetter);
463 }
464 $rowData = self::getCsv($fileHandle, 0, $delimiter, $this->enclosure, $this->escapeCharacter);
465 ++$currentRow;
466 }
467
468 // Close file
469 fclose($fileHandle);
470 }
471
472 /**
473 * Convert string true/false to boolean, and null to null-string.
474 * @param mixed $rowDatum
475 */
476 private function convertBoolean(&$rowDatum): void
477 {
478 if (is_string($rowDatum)) {
479 if (strcasecmp((string) $this->getTrue, $rowDatum) === 0 || strcasecmp('true', $rowDatum) === 0) {
480 $rowDatum = true;
481 } elseif (strcasecmp((string) $this->getFalse, $rowDatum) === 0 || strcasecmp('false', $rowDatum) === 0) {
482 $rowDatum = false;
483 }
484 } else {
485 $rowDatum = $rowDatum ?? '';
486 }
487 }
488
489 /**
490 * Convert numeric strings to int or float values.
491 * @param mixed $rowDatum
492 */
493 private function convertFormattedNumber(&$rowDatum): string
494 {
495 $numberFormatMask = '';
496 if ($this->castFormattedNumberToNumeric === true && is_string($rowDatum)) {
497 $numeric = str_replace(
498 [(string) $this->thousandsSeparator, (string) $this->decimalSeparator],
499 ['', '.'],
500 $rowDatum
501 );
502
503 if (is_numeric($numeric)) {
504 $decimalPos = strpos($rowDatum, (string) $this->decimalSeparator);
505 if ($this->preserveNumericFormatting === true) {
506 $numberFormatMask = (str_contains($rowDatum, (string) $this->thousandsSeparator))
507 ? '#,##0' : '0';
508 if ($decimalPos !== false) {
509 $decimals = strlen($rowDatum) - $decimalPos - 1;
510 $numberFormatMask .= '.' . str_repeat('0', min($decimals, 6));
511 }
512 }
513
514 $rowDatum = ($decimalPos !== false) ? (float) $numeric : (int) $numeric;
515 }
516 }
517
518 return $numberFormatMask;
519 }
520
521 public function getDelimiter(): ?string
522 {
523 return $this->delimiter;
524 }
525
526 public function setDelimiter(?string $delimiter): self
527 {
528 $this->delimiter = $delimiter;
529
530 return $this;
531 }
532
533 public function getEnclosure(): string
534 {
535 return $this->enclosure;
536 }
537
538 public function setEnclosure(string $enclosure): self
539 {
540 if ($enclosure == '') {
541 $enclosure = '"';
542 }
543 $this->enclosure = $enclosure;
544
545 return $this;
546 }
547
548 public function getSheetIndex(): int
549 {
550 return $this->sheetIndex;
551 }
552
553 public function setSheetIndex(int $indexValue): self
554 {
555 $this->sheetIndex = $indexValue;
556
557 return $this;
558 }
559
560 public function setContiguous(bool $contiguous): self
561 {
562 $this->contiguous = $contiguous;
563
564 return $this;
565 }
566
567 public function getContiguous(): bool
568 {
569 return $this->contiguous;
570 }
571
572 /**
573 * Php9 intends to drop support for this parameter in fgetcsv.
574 * Not yet ready to mark deprecated in order to give users
575 * a migration path.
576 */
577 public function setEscapeCharacter(string $escapeCharacter, int $version = PHP_VERSION_ID): self
578 {
579 if ($version >= 90000 && $escapeCharacter !== '') {
580 throw new ReaderException('Escape character must be null string for Php9+');
581 }
582
583 $this->escapeCharacter = $escapeCharacter;
584
585 return $this;
586 }
587
588 public function getEscapeCharacter(int $version = PHP_VERSION_ID): string
589 {
590 return $this->escapeCharacter ?? self::getDefaultEscapeCharacter($version);
591 }
592
593 /**
594 * Can the current IReader read the file?
595 */
596 public function canRead(string $filename): bool
597 {
598 // Check if file exists
599 try {
600 $this->openFile($filename);
601 } catch (ReaderException $exception) {
602 return false;
603 }
604
605 fclose($this->fileHandle);
606
607 // Trust file extension if any
608 $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
609 if (in_array($extension, ['csv', 'tsv'])) {
610 return true;
611 }
612
613 // TablePress: Add a `function_exists()` check around `mime_type_content()` as the PHP fileinfo extension might not be loaded.
614 if ( function_exists( 'mime_content_type' ) ) {
615 // Attempt to guess mimetype
616 $type = mime_content_type($filename);
617 $supportedTypes = [
618 'application/csv',
619 'text/csv',
620 'text/plain',
621 'inode/x-empty',
622 'application/x-empty', // has now replaced previous
623 'text/html',
624 ];
625
626 return in_array($type, $supportedTypes, true);
627 } else {
628 return false;
629 }
630
631 }
632
633 private static function guessEncodingTestNoBom(string &$encoding, string &$contents, string $compare, string $setEncoding): void
634 {
635 if ($encoding === '') {
636 $pos = strpos($contents, $compare);
637 if ($pos !== false && $pos % strlen($compare) === 0) {
638 $encoding = $setEncoding;
639 }
640 }
641 }
642
643 private static function guessEncodingNoBom(string $filename): string
644 {
645 $encoding = '';
646 $contents = (string) file_get_contents($filename);
647 self::guessEncodingTestNoBom($encoding, $contents, self::UTF32BE_LF, 'UTF-32BE');
648 self::guessEncodingTestNoBom($encoding, $contents, self::UTF32LE_LF, 'UTF-32LE');
649 self::guessEncodingTestNoBom($encoding, $contents, self::UTF16BE_LF, 'UTF-16BE');
650 self::guessEncodingTestNoBom($encoding, $contents, self::UTF16LE_LF, 'UTF-16LE');
651 if ($encoding === '' && preg_match('//u', $contents) === 1) {
652 $encoding = 'UTF-8';
653 }
654
655 return $encoding;
656 }
657
658 private static function guessEncodingTestBom(string &$encoding, string $first4, string $compare, string $setEncoding): void
659 {
660 if ($encoding === '') {
661 if (str_starts_with($first4, $compare)) {
662 $encoding = $setEncoding;
663 }
664 }
665 }
666
667 public static function guessEncodingBom(string $filename, ?string $convertString = null): string
668 {
669 $encoding = '';
670 $first4 = $convertString ?? (string) file_get_contents($filename, false, null, 0, 4);
671 self::guessEncodingTestBom($encoding, $first4, self::UTF8_BOM, 'UTF-8');
672 self::guessEncodingTestBom($encoding, $first4, self::UTF16BE_BOM, 'UTF-16BE');
673 self::guessEncodingTestBom($encoding, $first4, self::UTF32BE_BOM, 'UTF-32BE');
674 self::guessEncodingTestBom($encoding, $first4, self::UTF32LE_BOM, 'UTF-32LE');
675 self::guessEncodingTestBom($encoding, $first4, self::UTF16LE_BOM, 'UTF-16LE');
676
677 return $encoding;
678 }
679
680 public static function guessEncoding(string $filename, string $dflt = self::DEFAULT_FALLBACK_ENCODING): string
681 {
682 $encoding = self::guessEncodingBom($filename);
683 if ($encoding === '') {
684 $encoding = self::guessEncodingNoBom($filename);
685 }
686
687 return ($encoding === '') ? $dflt : $encoding;
688 }
689
690 public function setPreserveNullString(bool $value): self
691 {
692 $this->preserveNullString = $value;
693
694 return $this;
695 }
696
697 public function getPreserveNullString(): bool
698 {
699 return $this->preserveNullString;
700 }
701
702 public function setSheetNameIsFileName(bool $sheetNameIsFileName): self
703 {
704 $this->sheetNameIsFileName = $sheetNameIsFileName;
705
706 return $this;
707 }
708
709 /**
710 * Php8.4 deprecates use of anything other than null string
711 * as escape Character.
712 *
713 * @param resource $stream
714 * @param null|int<0, max> $length
715 *
716 * @return mixed[]|false
717 */
718 private static function getCsv(
719 $stream,
720 ?int $length = null,
721 string $separator = ',',
722 string $enclosure = '"',
723 ?string $escape = null,
724 int $version = PHP_VERSION_ID
725 ) {
726 $escape = $escape ?? self::getDefaultEscapeCharacter();
727 if ($version >= 80400 && $escape !== '') {
728 return @fgetcsv($stream, $length, $separator, $enclosure, $escape);
729 }
730
731 return fgetcsv($stream, $length, $separator, $enclosure, $escape);
732 }
733
734 public static function affectedByPhp9(
735 string $filename,
736 string $inputEncoding = 'UTF-8',
737 ?string $delimiter = null,
738 string $enclosure = '"',
739 string $escapeCharacter = '\\',
740 int $version = PHP_VERSION_ID
741 ): bool {
742 if ($version < 70400 || $version >= 90000) {
743 throw new ReaderException('Function valid only for Php7.4 or Php8');
744 }
745 $reader1 = new self();
746 $reader1->setInputEncoding($inputEncoding)
747 ->setTestAutoDetect(true)
748 ->setEscapeCharacter($escapeCharacter)
749 ->setDelimiter($delimiter)
750 ->setEnclosure($enclosure);
751 $spreadsheet1 = $reader1->load($filename);
752 $sheet1 = $spreadsheet1->getActiveSheet();
753 $array1 = $sheet1->toArray(null, false, false);
754 $spreadsheet1->disconnectWorksheets();
755
756 $reader2 = new self();
757 $reader2->setInputEncoding($inputEncoding)
758 ->setTestAutoDetect(false)
759 ->setEscapeCharacter('')
760 ->setDelimiter($delimiter)
761 ->setEnclosure($enclosure);
762 $spreadsheet2 = $reader2->load($filename);
763 $sheet2 = $spreadsheet2->getActiveSheet();
764 $array2 = $sheet2->toArray(null, false, false);
765 $spreadsheet2->disconnectWorksheets();
766
767 return $array1 !== $array2;
768 }
769
770 /**
771 * The character that will be supplied to fgetcsv
772 * when escapeCharacter is null.
773 * It is anticipated that it will conditionally be set
774 * to null-string for Php9 and above.
775 */
776 private static function getDefaultEscapeCharacter(int $version = PHP_VERSION_ID): string
777 {
778 return $version < 90000 ? '\\' : '';
779 }
780
781 public function setGetTrue(?string $getTrue): self
782 {
783 $this->getTrue = $getTrue;
784
785 return $this;
786 }
787
788 public function setGetFalse(?string $getFalse): self
789 {
790 $this->getFalse = $getFalse;
791
792 return $this;
793 }
794
795 public function setDecimalSeparator(?string $decimalSeparator): self
796 {
797 $this->decimalSeparator = $decimalSeparator;
798
799 return $this;
800 }
801
802 public function setThousandsSeparator(?string $thousandsSeparator): self
803 {
804 $this->thousandsSeparator = $thousandsSeparator;
805
806 return $this;
807 }
808 }
809