Passed
Push — master ( da0adb...60edbf )
by Thomas
17:35 queued 31s
created

ExcelImportExport::excelToAssocArray()   C

Complexity

Conditions 16
Paths 129

Size

Total Lines 67
Code Lines 46

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 1 Features 1
Metric Value
cc 16
eloc 46
c 4
b 1
f 1
nc 129
nop 3
dl 0
loc 67
rs 5.325

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace LeKoala\ExcelImportExport;
4
5
use Exception;
6
use Generator;
7
use LeKoala\SpreadCompat\Common\Options;
8
use LeKoala\SpreadCompat\SpreadCompat;
0 ignored issues
show
Bug introduced by
The type LeKoala\SpreadCompat\SpreadCompat was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
9
use SilverStripe\Forms\Form;
10
use SilverStripe\Core\ClassInfo;
11
use SilverStripe\ORM\DataObject;
12
use SilverStripe\Control\Controller;
13
use SilverStripe\Core\Config\Config;
14
use SilverStripe\Control\HTTPResponse;
15
use PhpOffice\PhpSpreadsheet\IOFactory;
16
use SilverStripe\Dev\BulkLoader_Result;
17
use PhpOffice\PhpSpreadsheet\Reader\Csv;
18
use PhpOffice\PhpSpreadsheet\Shared\Date;
19
use PhpOffice\PhpSpreadsheet\Spreadsheet;
20
use SilverStripe\Core\Config\Configurable;
21
use PhpOffice\PhpSpreadsheet\Reader\IReader;
22
use PhpOffice\PhpSpreadsheet\Writer\IWriter;
23
use SilverStripe\Assets\FileNameFilter;
24
25
/**
26
 * Support class for the module
27
 *
28
 * @author Koala
29
 */
30
class ExcelImportExport
31
{
32
    use Configurable;
33
34
    /**
35
     * Setting this to false improve performance but may lead to skipped cells
36
     * @var bool
37
     */
38
    public static $iterate_only_existing_cells = false;
39
40
    /**
41
     * Useful if importing only one sheet or if computation fails
42
     * @var bool
43
     */
44
    public static $use_old_calculated_value = false;
45
46
    /**
47
     * Some excel sheets need extra processing
48
     * @var boolean
49
     */
50
    public static $process_headers = false;
51
52
    /**
53
     * @var string
54
     */
55
    public static $default_tmp_reader = 'Xlsx';
56
57
    /**
58
     * @var integer
59
     */
60
    public static $limit_exports = 1000;
61
62
    /**
63
     * You can override the static var with yml config if needed
64
     * @return int
65
     */
66
    public static function getExportLimit()
67
    {
68
        $v = self::config()->export_limit;
69
        if ($v) {
70
            return $v;
71
        }
72
        return self::$limit_exports;
73
    }
74
75
    /**
76
     * Get all db fields for a given dataobject class
77
     *
78
     * @param string $class
79
     * @return array<string,string>
80
     */
81
    public static function allFieldsForClass($class)
82
    {
83
        $dataClasses = ClassInfo::dataClassesFor($class);
84
        $fields      = [];
85
        $dataObjectSchema = DataObject::getSchema();
86
        foreach ($dataClasses as $dataClass) {
87
            $dataFields = $dataObjectSchema->databaseFields($dataClass);
88
            $fields = array_merge($fields, array_keys($dataFields));
89
        }
90
        return array_combine($fields, $fields);
91
    }
92
93
    /**
94
     * Get fields that should be exported by default for a class
95
     *
96
     * @param string $class
97
     * @return array<int|string,mixed>
98
     */
99
    public static function exportFieldsForClass($class)
100
    {
101
        $singl = singleton($class);
102
        if ($singl->hasMethod('exportedFields')) {
103
            return $singl->exportedFields();
104
        }
105
        $exportedFields = Config::inst()->get($class, 'exported_fields');
106
107
        if (!$exportedFields) {
108
            $exportedFields = array_keys(self::allFieldsForClass($class));
109
        }
110
111
        $unexportedFields = Config::inst()->get($class, 'unexported_fields');
112
113
        if ($unexportedFields) {
114
            $exportedFields = array_diff($exportedFields, $unexportedFields);
115
        }
116
117
        $fields = [];
118
        foreach ($exportedFields as $key => $value) {
119
            if (is_int($key)) {
120
                $key = $value;
121
            }
122
            $fields[$key] = $value;
123
        }
124
125
        return $fields;
126
    }
127
128
    /**
129
     * Get fields that can be imported by default for a class
130
     *
131
     * @param string $class
132
     * @return array<string,string>
133
     */
134
    public static function importFieldsForClass($class)
135
    {
136
        $singl = singleton($class);
137
        if ($singl->hasMethod('importedFields')) {
138
            return $singl->importedFields();
139
        }
140
        $importedFields = Config::inst()->get($class, 'imported_fields');
141
142
        if (!$importedFields) {
143
            $importedFields = array_keys(self::allFieldsForClass($class));
144
        }
145
146
        $unimportedFields = Config::inst()->get($class, 'unimported_Fields');
147
148
        if ($unimportedFields) {
149
            $importedFields = array_diff($importedFields, $unimportedFields);
150
        }
151
        return array_combine($importedFields, $importedFields);
152
    }
153
154
    public static function createDownloadSampleLink($importer = '')
155
    {
156
        /** @var \SilverStripe\Admin\ModelAdmin $owner */
157
        $owner = Controller::curr();
158
        $class = $owner->getModelClass();
159
        $downloadSampleLink = $owner->Link(str_replace('\\', '-', $class) . '/downloadsample?importer=' . urlencode($importer));
160
        $downloadSample = '<a href="' . $downloadSampleLink . '" class="no-ajax" target="_blank">' . _t(
161
            'ExcelImportExport.DownloadSample',
162
            'Download sample file'
163
        ) . '</a>';
164
        return $downloadSample;
165
    }
166
167
    public static function createSampleFile($data, $fileName)
168
    {
169
        $ext = self::getDefaultExtension();
170
        $fileNameExtension = pathinfo($fileName, PATHINFO_EXTENSION);
171
        if (!$fileNameExtension) {
172
            $fileName .= ".$ext";
173
        }
174
        $options = new Options();
175
        $options->creator = ExcelImportExport::config()->default_creator;
176
        SpreadCompat::output($data, $fileName, $options);
177
        exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
178
    }
179
180
    /**
181
     * Output a sample file for a class
182
     *
183
     * A custom file can be provided with a custom sampleExcelFile method
184
     * either as a file path or as a Excel instance
185
     *
186
     * @param string $class
187
     * @return void
188
     */
189
    public static function sampleFileForClass($class)
190
    {
191
        $ext = self::getDefaultExtension();
192
        $filter = new FileNameFilter();
193
        $fileName = $filter->filter("sample-file-for-$class.$ext");
194
        $spreadsheet = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $spreadsheet is dead and can be removed.
Loading history...
195
196
        $sng = singleton($class);
197
198
        // Deprecated
199
        if ($sng->hasMethod('sampleExcelFile')) {
200
            $spreadsheet = $sng->sampleExcelFile();
201
            // PHPSpreadsheet is required for this
202
            $writer = self::getDefaultWriter($spreadsheet);
203
            self::outputHeaders($fileName);
204
            $writer->save('php://output');
205
            exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
206
        }
207
208
        if ($sng->hasMethod('sampleImportData')) {
209
            $data = $sng->sampleImportData();
210
        } else {
211
            // Simply output the default headers
212
            $data = [ExcelImportExport::importFieldsForClass($class)];
213
        }
214
215
        if (!is_iterable($data)) {
216
            throw new Exception("`sampleImportData` must return an iterable");
217
        }
218
219
        self::createSampleFile($data, $fileName);
220
    }
221
222
    /**
223
     * @param \SilverStripe\Admin\ModelAdmin $controller
224
     * @return bool
225
     */
226
    public static function checkImportForm($controller)
227
    {
228
        if (!$controller->showImportForm) {
229
            return false;
230
        }
231
        $modelClass = $controller->getModelClass();
232
        if (is_array($controller->showImportForm)) {
0 ignored issues
show
introduced by
The condition is_array($controller->showImportForm) is always false.
Loading history...
233
            /** @var array<string> $valid */
234
            $valid = $controller->showImportForm;
235
            if (!in_array($modelClass, $valid)) {
236
                return false;
237
            }
238
        }
239
        return true;
240
    }
241
242
    /**
243
     * @param string $handler
244
     * @param Form $form
245
     * @param Controller $controller
246
     * @return HTTPResponse
247
     */
248
    public static function useCustomHandler($handler, Form $form, Controller $controller)
249
    {
250
        if (!$handler || !method_exists($handler, "load")) {
251
            $form->sessionMessage("Invalid handler: $handler", 'bad');
252
            return $controller->redirectBack();
253
        }
254
        $file = $_FILES['_CsvFile']['tmp_name'];
255
        $name = $_FILES['_CsvFile']['name'];
256
        $inst = new $handler();
257
258
        if (!empty($_POST['OnlyUpdateRecords']) && method_exists($inst, 'setOnlyUpdate')) {
259
            $inst->setOnlyUpdate(true);
260
        }
261
        /** @var ExcelLoaderInterface $inst */
262
        try {
263
            $results = $inst->load($file, $name);
264
        } catch (Exception $e) {
265
            $form->sessionMessage($e->getMessage(), 'bad');
266
            return $controller->redirectBack();
267
        }
268
269
        $message = '';
270
        if ($results instanceof BulkLoader_Result) {
271
            if ($results->CreatedCount()) {
272
                $message .= _t(
273
                    'ModelAdmin.IMPORTEDRECORDS',
274
                    "Imported {count} records.",
275
                    ['count' => $results->CreatedCount()]
276
                );
277
            }
278
            if ($results->UpdatedCount()) {
279
                $message .= _t(
280
                    'ModelAdmin.UPDATEDRECORDS',
281
                    "Updated {count} records.",
282
                    ['count' => $results->UpdatedCount()]
283
                );
284
            }
285
            if ($results->DeletedCount()) {
286
                $message .= _t(
287
                    'ModelAdmin.DELETEDRECORDS',
288
                    "Deleted {count} records.",
289
                    ['count' => $results->DeletedCount()]
290
                );
291
            }
292
            if (!$results->CreatedCount() && !$results->UpdatedCount()) {
293
                $message .= _t('ModelAdmin.NOIMPORT', "Nothing to import");
294
            }
295
        } else {
296
            // Or we have a simple result
297
            $message = $results;
298
        }
299
300
        $form->sessionMessage($message, 'good');
301
        return $controller->redirectBack();
302
    }
303
304
    /**
305
     * @return string
306
     */
307
    public static function getDefaultExtension()
308
    {
309
        return self::config()->default_extension ?? 'xlsx';
310
    }
311
312
    /**
313
     * Get default writer for PHPSpreadsheet if installed
314
     *
315
     * @param Spreadsheet $spreadsheet
316
     * @return IWriter
317
     */
318
    public static function getDefaultWriter(Spreadsheet $spreadsheet): IWriter
319
    {
320
        if (!self::isPhpSpreadsheetAvailable()) {
321
            throw new Exception("PHPSpreadsheet is not installed");
322
        }
323
        $writer = ucfirst(self::getDefaultExtension());
324
        return IOFactory::createWriter($spreadsheet, $writer);
325
    }
326
327
    /**
328
     * Get default reader for PHPSpreadsheet if installed
329
     *
330
     * @return IReader
331
     */
332
    public static function getDefaultReader(): IReader
333
    {
334
        if (!self::isPhpSpreadsheetAvailable()) {
335
            throw new Exception("PHPSpreadsheet is not installed");
336
        }
337
        $writer = ucfirst(self::getDefaultExtension());
338
        return IOFactory::createReader($writer);
339
    }
340
341
    /**
342
     * Output excel headers
343
     *
344
     * @param string $fileName
345
     * @return void
346
     */
347
    public static function outputHeaders($fileName)
348
    {
349
        $ext = pathinfo($fileName, PATHINFO_EXTENSION);
350
        switch ($ext) {
351
            case 'csv':
352
                header('Content-Type: text/csv');
353
                break;
354
            case 'xlsx':
355
                header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
356
                break;
357
            default:
358
                header('Content-type: application/vnd.ms-excel');
359
                break;
360
        }
361
362
        header('Content-Disposition: attachment; filename="' . $fileName . '"');
363
        header('Cache-Control: max-age=0');
364
        ob_clean();
365
    }
366
367
    /**
368
     * Generate a default import file with all field name
369
     * @deprecated
370
     * @param string $class
371
     * @return string
372
     */
373
    public static function generateDefaultSampleFile($class)
374
    {
375
        $opts = [
376
            'creator' => "SilverStripe"
377
        ];
378
        $allFields = ExcelImportExport::importFieldsForClass($class);
379
        $tmpname = SpreadCompat::getTempFilename();
380
        SpreadCompat::write([
381
            $allFields
382
        ], $tmpname, ...$opts);
383
        return $tmpname;
384
    }
385
386
    /**
387
     * Show valid extensions helper (for uploaders)
388
     *
389
     * @return string
390
     */
391
    public static function getValidExtensionsText()
392
    {
393
        return _t(
394
            'ExcelImportExport.VALIDEXTENSIONS',
395
            "Allowed extensions: {extensions}",
396
            array('extensions' => implode(', ', self::getValidExtensions()))
397
        );
398
    }
399
400
    /**
401
     * Extracted from PHPSpreadsheet
402
     *
403
     * @param string $ext
404
     * @return string
405
     */
406
    public static function getReaderForExtension($ext)
407
    {
408
        switch (strtolower($ext)) {
409
            case 'xlsx': // Excel (OfficeOpenXML) Spreadsheet
410
            case 'xlsm': // Excel (OfficeOpenXML) Macro Spreadsheet (macros will be discarded)
411
            case 'xltx': // Excel (OfficeOpenXML) Template
412
            case 'xltm': // Excel (OfficeOpenXML) Macro Template (macros will be discarded)
413
                return 'Xlsx';
414
            case 'xls': // Excel (BIFF) Spreadsheet
415
            case 'xlt': // Excel (BIFF) Template
416
                return 'Xls';
417
            case 'ods': // Open/Libre Offic Calc
418
            case 'ots': // Open/Libre Offic Calc Template
419
                return 'Ods';
420
            case 'slk':
421
                return 'Slk';
422
            case 'xml': // Excel 2003 SpreadSheetML
423
                return 'Xml';
424
            case 'gnumeric':
425
                return 'Gnumeric';
426
            case 'htm':
427
            case 'html':
428
                return 'Html';
429
            case 'csv':
430
                return 'Csv';
431
            case 'tmp': // Useful when importing uploaded files
432
                return self::$default_tmp_reader;
433
            default:
434
                throw new Exception("Unsupported file type : $ext");
435
        }
436
    }
437
438
    /**
439
     * @return boolean
440
     */
441
    public static function isPhpSpreadsheetAvailable()
442
    {
443
        return class_exists(\PhpOffice\PhpSpreadsheet\Spreadsheet::class);
444
    }
445
446
    /**
447
     * If you exported separated files, you can merge them in one big file
448
     * Requires PHPSpreadsheet
449
     * @param array<string> $files
450
     * @return Spreadsheet
451
     */
452
    public static function mergeExcelFiles($files)
453
    {
454
        if (!self::isPhpSpreadsheetAvailable()) {
455
            throw new Exception("PHPSpreadsheet is not installed");
456
        }
457
        $merged = new Spreadsheet;
458
        $merged->removeSheetByIndex(0);
459
        foreach ($files as $filename) {
460
            $remoteExcel = IOFactory::load($filename);
461
            $merged->addExternalSheet($remoteExcel->getActiveSheet());
462
        }
463
        return $merged;
464
    }
465
466
    /**
467
     * Get valid extensions
468
     *
469
     * @return array<string>
470
     */
471
    public static function getValidExtensions()
472
    {
473
        $v = self::config()->allowed_extensions;
474
        if (!$v || !is_array($v)) {
475
            return [];
476
        }
477
        return $v;
478
    }
479
480
    /**
481
     * Save content of an array to a file
482
     *
483
     * @param iterable<array<mixed>> $data
484
     * @param string $filepath
485
     * @return void
486
     */
487
    public static function arrayToFile($data, $filepath)
488
    {
489
        SpreadCompat::write($data, $filepath);
490
    }
491
492
    /**
493
     * Fast saving to csv
494
     *
495
     * @param array<mixed> $data
496
     * @param string $filepath
497
     * @param string $delimiter
498
     * @param string $enclosure
499
     * @param string $escapeChar
500
     * @return void
501
     */
502
    public static function arrayToCsv($data, $filepath, $delimiter = ',', $enclosure = '"', $escapeChar = '\\')
503
    {
504
        if (is_file($filepath)) {
505
            unlink($filepath);
506
        }
507
        $options = new Options();
508
        $options->separator = $delimiter;
509
        $options->enclosure = $enclosure;
510
        $options->escape = $escapeChar;
511
        SpreadCompat::write($data, $filepath, $options);
512
    }
513
514
    public static function excelColumnRange(string $lower = 'A', string $upper = 'ZZ'): Generator
515
    {
516
        ++$upper;
517
        for ($i = $lower; $i !== $upper; ++$i) {
518
            yield $i;
519
        }
520
    }
521
522
    /**
523
     * String from column index.
524
     *
525
     * @param int $index Column index (1 = A)
526
     * @param string $fallback
527
     * @return string
528
     */
529
    public static function getLetter($index, $fallback = 'A')
530
    {
531
        foreach (self::excelColumnRange() as $letter) {
532
            $index--;
533
            if ($index <= 0) {
534
                return $letter;
535
            }
536
        }
537
        return $fallback;
538
    }
539
540
    /**
541
     * Convert a file to an array
542
     *
543
     * @param string $filepath
544
     * @param string $delimiter (csv only)
545
     * @param string $enclosure (csv only)
546
     * @param string $ext if extension cannot be deducted from filepath (eg temp files)
547
     * @return array<mixed>
548
     */
549
    public static function fileToArray($filepath, $delimiter = ';', $enclosure = '', $ext = null)
550
    {
551
        if ($ext === null) {
552
            $ext = pathinfo($filepath, PATHINFO_EXTENSION);
553
        }
554
555
        $data = iterator_to_array(
556
            SpreadCompat::read(
557
                $filepath,
558
                extension: $ext,
559
                separator: $delimiter,
560
                enclosure: $enclosure
561
            )
562
        );
563
        return $data;
564
    }
565
566
    /**
567
     * Convert an excel file to an array
568
     *
569
     * @param string $filepath
570
     * @param string $sheetname Load a specific worksheet by name
571
     * @param bool $onlyExisting Avoid reading empty columns
572
     * @param string $ext if extension cannot be deducted from filepath (eg temp files)
573
     * @return array<mixed>
574
     */
575
    public static function excelToArray($filepath, $sheetname = null, $onlyExisting = true, $ext = null)
576
    {
577
        if (!self::isPhpSpreadsheetAvailable()) {
578
            throw new Exception("PHPSpreadsheet is not installed");
579
        }
580
        if ($ext === null) {
581
            $ext = pathinfo($filepath, PATHINFO_EXTENSION);
0 ignored issues
show
Unused Code introduced by
The assignment to $ext is dead and can be removed.
Loading history...
582
        }
583
        $ext = pathinfo($filepath, PATHINFO_EXTENSION);
584
        $readerType = self::getReaderForExtension($ext);
0 ignored issues
show
Bug introduced by
It seems like $ext can also be of type array; however, parameter $ext of LeKoala\ExcelImportExpor...getReaderForExtension() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

584
        $readerType = self::getReaderForExtension(/** @scrutinizer ignore-type */ $ext);
Loading history...
585
        $reader = IOFactory::createReader($readerType);
586
        $reader->setReadDataOnly(true);
587
        if ($sheetname) {
588
            $reader->setLoadSheetsOnly($sheetname);
589
        }
590
        $data = [];
591
        if ($reader->canRead($filepath)) {
592
            $excel = $reader->load($filepath);
593
            if ($onlyExisting) {
594
                $data = [];
595
                foreach ($excel->getActiveSheet()->getRowIterator() as $row) {
596
                    $cellIterator = $row->getCellIterator();
597
                    if (self::$iterate_only_existing_cells) {
598
                        $cellIterator->setIterateOnlyExistingCells(true);
599
                    }
600
                    $cells = [];
601
                    foreach ($cellIterator as $cell) {
602
                        if (self::$use_old_calculated_value) {
603
                            $cells[] = $cell->getOldCalculatedValue();
0 ignored issues
show
Bug introduced by
The method getOldCalculatedValue() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

603
                            /** @scrutinizer ignore-call */ 
604
                            $cells[] = $cell->getOldCalculatedValue();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
604
                        } else {
605
                            $cells[] = $cell->getFormattedValue();
606
                        }
607
                    }
608
                    $data[] = $cells;
609
                }
610
            } else {
611
                $data = $excel->getActiveSheet()->toArray(null, true, false, false);
612
            }
613
        } else {
614
            throw new Exception("Cannot read $filepath");
615
        }
616
        return $data;
617
    }
618
619
    /**
620
     * @link https://stackoverflow.com/questions/44304795/how-to-retrieve-date-from-table-cell-using-phpspreadsheet#44304796
621
     * @param int|string|null|float $v
622
     * @return string
623
     */
624
    public static function convertExcelDate($v)
625
    {
626
        if ($v === null || !is_numeric($v)) {
627
            return '';
628
        }
629
        if (!self::isPhpSpreadsheetAvailable()) {
630
            throw new Exception("PHPSpreadsheet is not installed");
631
        }
632
        if (is_string($v)) {
633
            $v = intval($v);
634
        }
635
        return date('Y-m-d', Date::excelToTimestamp($v));
636
    }
637
638
    /**
639
     * Convert an excel file to an associative array
640
     *
641
     * Suppose the first line are the headers of the file
642
     * Headers are trimmed in case you have crappy whitespace in your files
643
     *
644
     * @param string $filepath
645
     * @param string $sheetname Load a specific worksheet by name
646
     * @param string $ext if extension cannot be deducted from filepath (eg temp files)
647
     * @return array<mixed>
648
     */
649
    public static function excelToAssocArray($filepath, $sheetname = null, $ext = null)
650
    {
651
        if (!self::isPhpSpreadsheetAvailable()) {
652
            throw new Exception("PHPSpreadsheet is not installed");
653
        }
654
        if ($ext === null) {
655
            $ext = pathinfo($filepath, PATHINFO_EXTENSION);
656
        }
657
        $readerType = self::getReaderForExtension($ext);
0 ignored issues
show
Bug introduced by
It seems like $ext can also be of type array; however, parameter $ext of LeKoala\ExcelImportExpor...getReaderForExtension() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

657
        $readerType = self::getReaderForExtension(/** @scrutinizer ignore-type */ $ext);
Loading history...
658
        $reader = IOFactory::createReader($readerType);
659
        $reader->setReadDataOnly(true);
660
        if ($sheetname) {
661
            $reader->setLoadSheetsOnly($sheetname);
662
        }
663
        $data = [];
0 ignored issues
show
Unused Code introduced by
The assignment to $data is dead and can be removed.
Loading history...
664
        if ($reader->canRead($filepath)) {
665
            $excel = $reader->load($filepath);
666
            $data = [];
667
            $headers = [];
668
            $headersCount = 0;
669
            foreach ($excel->getActiveSheet()->getRowIterator() as $row) {
670
                $cellIterator = $row->getCellIterator();
671
                if (self::$iterate_only_existing_cells) {
672
                    $cellIterator->setIterateOnlyExistingCells(true);
673
                }
674
                $cells = [];
675
                foreach ($cellIterator as $cell) {
676
                    if (self::$use_old_calculated_value) {
677
                        $cells[] = $cell->getOldCalculatedValue();
678
                    } else {
679
                        $cells[] = $cell->getFormattedValue();
680
                    }
681
                }
682
                if (empty($headers)) {
683
                    $headers = $cells;
684
                    // Some crappy excel file may need this
685
                    if (self::$process_headers) {
686
                        $headers = array_map(function ($v) {
687
                            // Numeric headers are most of the time dates
688
                            if (is_numeric($v)) {
689
                                $v =  self::convertExcelDate($v);
690
                            }
691
                            // trim does not always work great and headers can contain utf8 stuff
692
                            return is_string($v) ? preg_replace('/(^\s+)|(\s+$)/us', '', $v) : $v;
693
                        }, $headers);
694
                    }
695
                    $headersCount = count($headers);
696
                } else {
697
                    $diff = count($cells) - $headersCount;
698
                    if ($diff != 0) {
699
                        if ($diff > 0) {
700
                            // we have too many cells
701
                            $cells = array_slice($cells, 0, $headersCount);
702
                        } else {
703
                            // we are missing some cells
704
                            for ($i = 0; $i < abs($diff); $i++) {
705
                                $cells[] = null;
706
                            }
707
                        }
708
                    }
709
                    $data[] = array_combine($headers, $cells);
710
                }
711
            }
712
        } else {
713
            throw new Exception("Cannot read $filepath");
714
        }
715
        return $data;
716
    }
717
}
718