Passed
Pull Request — master (#6637)
by
unknown
14:37 queued 06:29
created

Import::csvColumnToArray()   A

Complexity

Conditions 6
Paths 5

Size

Total Lines 31
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 11
nc 5
nop 2
dl 0
loc 31
rs 9.2222
c 1
b 0
f 0
1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use League\Csv\Reader;
6
use PhpOffice\PhpSpreadsheet\Reader\Xls;
7
8
/**
9
 * Class Import
10
 * This class provides some functions which can be used when importing data from
11
 * external files into Chamilo.
12
 */
13
class Import
14
{
15
    /**
16
     * @param string $path
17
     * @param bool   $setFirstRowAsHeader
18
     *
19
     * @return array
20
     */
21
    public static function csv_reader($path, $setFirstRowAsHeader = true)
22
    {
23
        return self::csvToArray($path);
24
    }
25
26
    /**
27
     * Guess the CSV delimiter by scanning the first non-empty line.
28
     */
29
    public static function detectCsvSeparator(string $filePath): ?string
30
    {
31
        if (!is_readable($filePath)) {
32
            return null;
33
        }
34
        $h = @fopen($filePath, 'rb');
35
        if (!$h) {
0 ignored issues
show
introduced by
$h is of type false|resource, thus it always evaluated to false.
Loading history...
36
            return null;
37
        }
38
        $line = '';
39
        for ($i = 0; $i < 5 && !feof($h); $i++) {
40
            $line = fgets($h, 1024 * 1024);
41
            if ($line !== false && trim($line) !== '') {
42
                break;
43
            }
44
        }
45
        fclose($h);
46
        if ($line === false || $line === '') {
47
            return null;
48
        }
49
50
        $cands = [',', ';', "\t", '|'];
51
        $scores = array_fill_keys($cands, 0);
52
        $inQuotes = false;
53
        $len = strlen($line);
54
        for ($i = 0; $i < $len; $i++) {
55
            $ch = $line[$i];
56
            if ($ch === '"') {
57
                $prev = $i > 0 ? $line[$i - 1] : null;
58
                if ($prev !== '\\') {
59
                    $inQuotes = !$inQuotes;
60
                }
61
            } elseif (!$inQuotes && isset($scores[$ch])) {
62
                $scores[$ch]++;
63
            }
64
        }
65
        arsort($scores);
66
        $top = array_key_first($scores);
67
        return ($scores[$top] > 0) ? $top : null;
68
    }
69
70
    /**
71
     * Check if the CSV is comma-separated.
72
     * - Default (returnMessage=false): returns bool (true = OK, false = not comma).
73
     * - With returnMessage=true: returns true (OK) OR a string with the error message.
74
     */
75
    public static function assertCommaSeparated(string $filePath, bool $returnMessage = false): bool|string
76
    {
77
        $det = self::detectCsvSeparator($filePath);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $det is correct as self::detectCsvSeparator($filePath) targeting Import::detectCsvSeparator() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
78
79
        if ($det === null || $det === ',') {
80
            return true;
81
        }
82
83
        $msg = 'Semicolon (;) delimiter detected. This version of Chamilo requires comma (,) as the CSV separator. Please export your file again as CSV (comma-separated).';
84
85
        return $returnMessage ? get_lang($msg) : false;
86
    }
87
88
    /**
89
     * Reads a CSV-file into an array. The first line of the CSV-file should contain the array-keys.
90
     * The encoding of the input file is tried to be detected.
91
     * The elements of the returned array are encoded in the system encoding.
92
     * Example:
93
     *   FirstName;LastName;Email
94
     *   John;Doe;[email protected]
95
     *   Adam;Adams;[email protected]
96
     *  returns
97
     *   $result [0]['FirstName'] = 'John';
98
     *   $result [0]['LastName'] = 'Doe';
99
     *   $result [0]['Email'] = 'john.doe@mail. com';
100
     *   $result [1]['FirstName'] = 'Adam';
101
     *   ...
102
     *
103
     * @param string $filename the path to the CSV-file which should be imported
104
     *
105
     * @return array returns an array (in the system encoding) that contains all data from the CSV-file
106
     */
107
    public static function csvToArray($filename, $delimiter = ','): array
108
    {
109
        if (empty($filename)) {
110
            return [];
111
        }
112
113
        $reader = Reader::createFromPath($filename, 'r');
114
        if ($reader) {
115
            $reader->setDelimiter($delimiter);
116
            $reader->setHeaderOffset(0);
117
            $iterator = $reader->getRecords();
118
119
            return iterator_to_array($iterator);
120
        }
121
122
        return [];
123
    }
124
125
    /**
126
     * @param string $filename
127
     *
128
     * @return array
129
     */
130
    public static function xlsToArray($filename)
131
    {
132
        if (empty($filename)) {
133
            return [];
134
        }
135
136
        $reader = new Xls();
137
        $spreadsheet = $reader->load($filename);
138
        $sheet = $spreadsheet->getActiveSheet();
139
140
        return $sheet->toArray();
141
    }
142
143
    public static function csvColumnToArray($filename, $columnIndex = 0): array
144
    {
145
        if (empty($filename) || !is_readable($filename)) {
146
            return [];
147
        }
148
149
        $reader = Reader::createFromPath($filename, 'r');
150
151
        // Use semicolon as delimiter.
152
        $reader->setDelimiter(';');
153
154
        // Ensure we do not include BOM in data (defensive: only if the method exists).
155
        // In League CSV v9 the BOM is excluded by default; this call is just a safe guard.
156
        if (method_exists($reader, 'includeInputBOM')) {
157
            // Do not include the BOM as part of the first value.
158
            $reader->includeInputBOM(false);
159
        }
160
161
        // Skip empty records to avoid [null] rows.
162
        $reader->skipEmptyRecords();
163
164
        // Read the requested column as an iterator and materialize it.
165
        $values = iterator_to_array($reader->fetchColumn($columnIndex), false);
166
167
        // Extra safety: remove UTF-8 BOM if it somehow slipped into the first value.
168
        if (isset($values[0]) && is_string($values[0])) {
169
            // Remove leading BOM from the first element only.
170
            $values[0] = preg_replace('/^\xEF\xBB\xBF/u', '', $values[0]);
171
        }
172
173
        return $values;
174
    }
175
}
176