Passed
Push — master ( 754437...3b285f )
by Maurício
10:47
created

libraries/classes/Plugins/Import/ImportCsv.php (1 issue)

Severity
1
<?php
2
/**
3
 * CSV import plugin for phpMyAdmin
4
 *
5
 * @todo       add an option for handling NULL values
6
 */
7
8
declare(strict_types=1);
9
10
namespace PhpMyAdmin\Plugins\Import;
11
12
use PhpMyAdmin\Html\Generator;
13
use PhpMyAdmin\Message;
14
use PhpMyAdmin\Properties\Options\Items\BoolPropertyItem;
15
use PhpMyAdmin\Properties\Options\Items\NumberPropertyItem;
16
use PhpMyAdmin\Properties\Options\Items\TextPropertyItem;
17
use PhpMyAdmin\Util;
18
use function array_splice;
19
use function basename;
20
use function count;
21
use function is_array;
22
use function mb_strlen;
23
use function mb_strpos;
24
use function mb_strtolower;
25
use function mb_substr;
26
use function preg_grep;
27
use function preg_replace;
28
use function preg_split;
29
use function rtrim;
30
use function strlen;
31
use function strtr;
32
use function trim;
33
34
/**
35
 * Handles the import for the CSV format
36
 */
37
class ImportCsv extends AbstractImportCsv
38
{
39
    /**
40
     * Whether to analyze tables
41
     *
42
     * @var bool
43
     */
44
    private $_analyze;
45
46 20
    public function __construct()
47
    {
48 20
        parent::__construct();
49 20
        $this->setProperties();
50 20
    }
51
52
    /**
53
     * Sets the import plugin properties.
54
     * Called in the constructor.
55
     *
56
     * @return void
57
     */
58 20
    protected function setProperties()
59
    {
60 20
        $this->setAnalyze(false);
61
62 20
        if ($GLOBALS['plugin_param'] !== 'table') {
63 20
            $this->setAnalyze(true);
64
        }
65
66 20
        $generalOptions = parent::setProperties();
67 20
        $this->properties->setText('CSV');
68 20
        $this->properties->setExtension('csv');
69
70 20
        if ($GLOBALS['plugin_param'] !== 'table') {
71 20
            $leaf = new TextPropertyItem(
72 20
                'new_tbl_name',
73 20
                __(
74 20
                    'Name of the new table (optional):'
75
                )
76
            );
77 20
            $generalOptions->addProperty($leaf);
78
79 20
            if ($GLOBALS['plugin_param'] === 'server') {
80
                $leaf = new TextPropertyItem(
81
                    'new_db_name',
82
                    __(
83
                        'Name of the new database (optional):'
84
                    )
85
                );
86
                $generalOptions->addProperty($leaf);
87
            }
88
89 20
            $leaf = new NumberPropertyItem(
90 20
                'partial_import',
91 20
                __(
92 20
                    'Import these many number of rows (optional):'
93
                )
94
            );
95 20
            $generalOptions->addProperty($leaf);
96
97 20
            $leaf = new BoolPropertyItem(
98 20
                'col_names',
99 20
                __(
100
                    'The first line of the file contains the table column names'
101
                    . ' <i>(if this is unchecked, the first line will become part'
102 20
                    . ' of the data)</i>'
103
                )
104
            );
105 20
            $generalOptions->addProperty($leaf);
106
        } else {
107 4
            $leaf = new NumberPropertyItem(
108 4
                'partial_import',
109 4
                __(
110 4
                    'Import these many number of rows (optional):'
111
                )
112
            );
113 4
            $generalOptions->addProperty($leaf);
114
115 4
            $hint = new Message(
116 4
                __(
117
                    'If the data in each row of the file is not'
118
                    . ' in the same order as in the database, list the corresponding'
119
                    . ' column names here. Column names must be separated by commas'
120 4
                    . ' and not enclosed in quotations.'
121
                )
122
            );
123 4
            $leaf = new TextPropertyItem(
124 4
                'columns',
125 4
                __('Column names:') . ' ' . Generator::showHint($hint)
126
            );
127 4
            $generalOptions->addProperty($leaf);
128
        }
129
130 20
        $leaf = new BoolPropertyItem(
131 20
            'ignore',
132 20
            __('Do not abort on INSERT error')
133
        );
134 20
        $generalOptions->addProperty($leaf);
135 20
    }
136
137
    /**
138
     * Handles the whole import logic
139
     *
140
     * @param array $sql_data 2-element array with sql data
141
     *
142
     * @return void
143
     */
144 12
    public function doImport(array &$sql_data = [])
145
    {
146 12
        global $error, $message;
147 12
        global $db, $table, $csv_terminated, $csv_enclosed, $csv_escaped,
148 12
               $csv_new_line, $csv_columns, $err_url;
149
        // $csv_replace and $csv_ignore should have been here,
150
        // but we use directly from $_POST
151 12
        global $timeout_passed, $finished;
152
153
        $replacements = [
154 12
            '\\n' => "\n",
155
            '\\t' => "\t",
156
            '\\r' => "\r",
157
        ];
158 12
        $csv_terminated = strtr($csv_terminated, $replacements);
159 12
        $csv_enclosed = strtr($csv_enclosed, $replacements);
160 12
        $csv_escaped = strtr($csv_escaped, $replacements);
161 12
        $csv_new_line = strtr($csv_new_line, $replacements);
162
163 12
        [$error, $message] = $this->buildErrorsForParams(
164 12
            $csv_terminated,
165 9
            $csv_enclosed,
166 9
            $csv_escaped,
167 9
            $csv_new_line,
168 12
            (string) $err_url
169
        );
170
171 12
        [$sql_template, $required_fields, $fields] = $this->getSqlTemplateAndRequiredFields($db, $table, $csv_columns);
172
173
        // Defaults for parser
174 12
        $i = 0;
175 12
        $len = 0;
176 12
        $lastlen = null;
177 12
        $line = 1;
178 12
        $lasti = -1;
179 12
        $values = [];
180 12
        $csv_finish = false;
181 12
        $max_lines = 0; // defaults to 0 (get all the lines)
182
183
        /**
184
         * If we get a negative value, probably someone changed min value
185
         * attribute in DOM or there is an integer overflow, whatever be
186
         * the case, get all the lines.
187
         */
188 12
        if (isset($_REQUEST['csv_partial_import']) && $_REQUEST['csv_partial_import'] > 0) {
189 4
            $max_lines = $_REQUEST['csv_partial_import'];
190
        }
191 12
        $max_lines_constraint = $max_lines+1;
192
        // if the first row has to be counted as column names, include one more row in the max lines
193 12
        if (isset($_REQUEST['csv_col_names'])) {
194
            $max_lines_constraint++;
195
        }
196
197 12
        $tempRow = [];
198 12
        $rows = [];
199 12
        $col_names = [];
200 12
        $tables = [];
201
202 12
        $buffer = '';
203 12
        $col_count = 0;
204 12
        $max_cols = 0;
205 12
        $csv_terminated_len = mb_strlen($csv_terminated);
206 12
        while (! ($finished && $i >= $len) && ! $error && ! $timeout_passed) {
207 12
            $data = $this->import->getNextChunk();
208 12
            if ($data === false) {
209
                // subtract data we didn't handle yet and stop processing
210
                $GLOBALS['offset'] -= strlen($buffer);
211
                break;
212
            }
213
214 12
            if ($data !== true) {
215
                // Append new data to buffer
216 12
                $buffer .= $data;
217 12
                unset($data);
218
219
                // Force a trailing new line at EOF to prevent parsing problems
220 12
                if ($finished && $buffer) {
221 12
                    $finalch = mb_substr($buffer, -1);
222 12
                    if ($csv_new_line == 'auto'
223 12
                        && $finalch != "\r"
224 12
                        && $finalch != "\n"
225
                    ) {
226
                        $buffer .= "\n";
227 12
                    } elseif ($csv_new_line != 'auto'
228 12
                        && $finalch != $csv_new_line
229
                    ) {
230
                        $buffer .= $csv_new_line;
231
                    }
232
                }
233
234
                // Do not parse string when we're not at the end
235
                // and don't have new line inside
236 12
                if (($csv_new_line == 'auto'
237 12
                    && mb_strpos($buffer, "\r") === false
238 12
                    && mb_strpos($buffer, "\n") === false)
239 12
                    || ($csv_new_line != 'auto'
240 12
                    && mb_strpos($buffer, $csv_new_line) === false)
241
                ) {
242
                    continue;
243
                }
244
            }
245
246
            // Current length of our buffer
247 12
            $len = mb_strlen($buffer);
248
            // Currently parsed char
249
250 12
            $ch = mb_substr($buffer, $i, 1);
251 12
            if ($csv_terminated_len > 1 && $ch == $csv_terminated[0]) {
252
                $ch = $this->readCsvTerminatedString(
253
                    $buffer,
254
                    $ch,
255
                    $i,
256
                    $csv_terminated_len
257
                );
258
                $i += $csv_terminated_len - 1;
259
            }
260 12
            while ($i < $len) {
261
                // Deadlock protection
262 12
                if ($lasti == $i && $lastlen == $len) {
263
                    $message = Message::error(
264
                        __('Invalid format of CSV input on line %d.')
265
                    );
266
                    $message->addParam($line);
267
                    $error = true;
268
                    break;
269
                }
270 12
                $lasti = $i;
271 12
                $lastlen = $len;
272
273
                // This can happen with auto EOL and \r at the end of buffer
274 12
                if (! $csv_finish) {
275
                    // Grab empty field
276 12
                    if ($ch == $csv_terminated) {
277
                        if ($i == $len - 1) {
278
                            break;
279
                        }
280
                        $values[] = '';
281
                        $i++;
282
                        $ch = mb_substr($buffer, $i, 1);
283
                        if ($csv_terminated_len > 1 && $ch == $csv_terminated[0]) {
284
                            $ch = $this->readCsvTerminatedString(
285
                                $buffer,
286
                                $ch,
287
                                $i,
288
                                $csv_terminated_len
289
                            );
290
                            $i += $csv_terminated_len - 1;
291
                        }
292
                        continue;
293
                    }
294
295
                    // Grab one field
296 12
                    $fallbacki = $i;
297 12
                    if ($ch == $csv_enclosed) {
298 12
                        if ($i == $len - 1) {
299
                            break;
300
                        }
301 12
                        $need_end = true;
302 12
                        $i++;
303 12
                        $ch = mb_substr($buffer, $i, 1);
304 12
                        if ($csv_terminated_len > 1 && $ch == $csv_terminated[0]) {
305
                            $ch = $this->readCsvTerminatedString(
306
                                $buffer,
307
                                $ch,
308
                                $i,
309
                                $csv_terminated_len
310
                            );
311 12
                            $i += $csv_terminated_len - 1;
312
                        }
313
                    } else {
314
                        $need_end = false;
315
                    }
316 12
                    $fail = false;
317 12
                    $value = '';
318 12
                    while (($need_end
0 ignored issues
show
Consider adding parentheses for clarity. Current Interpretation: ($need_end && $ch != $cs...ch == ' ' || $ch == ' ', Probably Intended Meaning: $need_end && ($ch != $cs...h == ' ' || $ch == ' ')
Loading history...
319 12
                            && ($ch != $csv_enclosed
320 12
                                || $csv_enclosed == $csv_escaped))
321
                        || (! $need_end
322
                            && ! ($ch == $csv_terminated
323
                                || $ch == $csv_new_line
324
                                || ($csv_new_line == 'auto'
325 12
                                    && ($ch == "\r" || $ch == "\n"))))
326
                    ) {
327 12
                        if ($ch == $csv_escaped) {
328 12
                            if ($i == $len - 1) {
329
                                $fail = true;
330
                                break;
331
                            }
332 12
                            $i++;
333 12
                            $ch = mb_substr($buffer, $i, 1);
334 12
                            if ($csv_terminated_len > 1
335 12
                                && $ch == $csv_terminated[0]
336
                            ) {
337
                                $ch = $this->readCsvTerminatedString(
338
                                    $buffer,
339
                                    $ch,
340
                                    $i,
341
                                    $csv_terminated_len
342
                                );
343
                                $i += $csv_terminated_len - 1;
344
                            }
345 12
                            if ($csv_enclosed == $csv_escaped
346 12
                                && ($ch == $csv_terminated
347 12
                                || $ch == $csv_new_line
348 12
                                || ($csv_new_line == 'auto'
349 12
                                && ($ch == "\r" || $ch == "\n")))
350
                            ) {
351 12
                                break;
352
                            }
353
                        }
354 12
                        $value .= $ch;
355 12
                        if ($i == $len - 1) {
356
                            if (! $finished) {
357
                                $fail = true;
358
                            }
359
                            break;
360
                        }
361 12
                        $i++;
362 12
                        $ch = mb_substr($buffer, $i, 1);
363 12
                        if ($csv_terminated_len <= 1 || $ch != $csv_terminated[0]) {
364 12
                            continue;
365
                        }
366
367
                        $ch = $this->readCsvTerminatedString(
368
                            $buffer,
369
                            $ch,
370
                            $i,
371
                            $csv_terminated_len
372
                        );
373
                        $i += $csv_terminated_len - 1;
374
                    }
375
376
                    // unquoted NULL string
377 12
                    if ($need_end === false && $value === 'NULL') {
378
                        $value = null;
379
                    }
380
381 12
                    if ($fail) {
382
                        $i = $fallbacki;
383
                        $ch = mb_substr($buffer, $i, 1);
384
                        if ($csv_terminated_len > 1 && $ch == $csv_terminated[0]) {
385
                            $i += $csv_terminated_len - 1;
386
                        }
387
                        break;
388
                    }
389
                    // Need to strip trailing enclosing char?
390 12
                    if ($need_end && $ch == $csv_enclosed) {
391
                        if ($finished && $i == $len - 1) {
392
                            $ch = null;
393
                        } elseif ($i == $len - 1) {
394
                            $i = $fallbacki;
395
                            $ch = mb_substr($buffer, $i, 1);
396
                            if ($csv_terminated_len > 1
397
                                && $ch == $csv_terminated[0]
398
                            ) {
399
                                $i += $csv_terminated_len - 1;
400
                            }
401
                            break;
402
                        } else {
403
                            $i++;
404
                            $ch = mb_substr($buffer, $i, 1);
405
                            if ($csv_terminated_len > 1
406
                                && $ch == $csv_terminated[0]
407
                            ) {
408
                                $ch = $this->readCsvTerminatedString(
409
                                    $buffer,
410
                                    $ch,
411
                                    $i,
412
                                    $csv_terminated_len
413
                                );
414
                                $i += $csv_terminated_len - 1;
415
                            }
416
                        }
417
                    }
418
                    // Are we at the end?
419 12
                    if ($ch == $csv_new_line
420 12
                        || ($csv_new_line == 'auto' && ($ch == "\r" || $ch == "\n"))
421 12
                        || ($finished && $i == $len - 1)
422
                    ) {
423 12
                        $csv_finish = true;
424
                    }
425
                    // Go to next char
426 12
                    if ($ch == $csv_terminated) {
427
                        if ($i == $len - 1) {
428
                            $i = $fallbacki;
429
                            $ch = mb_substr($buffer, $i, 1);
430
                            if ($csv_terminated_len > 1
431
                                && $ch == $csv_terminated[0]
432
                            ) {
433
                                $i += $csv_terminated_len - 1;
434
                            }
435
                            break;
436
                        }
437
                        $i++;
438
                        $ch = mb_substr($buffer, $i, 1);
439
                        if ($csv_terminated_len > 1
440
                            && $ch == $csv_terminated[0]
441
                        ) {
442
                            $ch = $this->readCsvTerminatedString(
443
                                $buffer,
444
                                $ch,
445
                                $i,
446
                                $csv_terminated_len
447
                            );
448
                            $i += $csv_terminated_len - 1;
449
                        }
450
                    }
451
                    // If everything went okay, store value
452 12
                    $values[] = $value;
453
                }
454
455
                // End of line
456 12
                if (! $csv_finish
457 12
                    && $ch != $csv_new_line
458 12
                    && ($csv_new_line != 'auto' || ($ch != "\r" && $ch != "\n"))
459
                ) {
460
                    continue;
461
                }
462
463 12
                if ($csv_new_line == 'auto' && $ch == "\r") { // Handle "\r\n"
464
                    if ($i >= ($len - 2) && ! $finished) {
465
                        break; // We need more data to decide new line
466
                    }
467
                    if (mb_substr($buffer, $i + 1, 1) == "\n") {
468
                        $i++;
469
                    }
470
                }
471
                // We didn't parse value till the end of line, so there was
472
                // empty one
473 12
                if (! $csv_finish) {
474
                    $values[] = '';
475
                }
476
477 12
                if ($this->getAnalyze()) {
478 12
                    foreach ($values as $val) {
479 12
                        $tempRow[] = $val;
480 12
                        ++$col_count;
481
                    }
482
483 12
                    if ($col_count > $max_cols) {
484 12
                        $max_cols = $col_count;
485
                    }
486 12
                    $col_count = 0;
487
488 12
                    $rows[] = $tempRow;
489 12
                    $tempRow = [];
490
                } else {
491
                    // Do we have correct count of values?
492
                    if (count($values) != $required_fields) {
493
                        // Hack for excel
494
                        if ($values[count($values) - 1] != ';') {
495
                            $message = Message::error(
496
                                __(
497
                                    'Invalid column count in CSV input'
498
                                    . ' on line %d.'
499
                                )
500
                            );
501
                            $message->addParam($line);
502
                            $error = true;
503
                            break;
504
                        }
505
506
                        unset($values[count($values) - 1]);
507
                    }
508
509
                    $first = true;
510
                    $sql = $sql_template;
511
                    foreach ($values as $key => $val) {
512
                        if (! $first) {
513
                            $sql .= ', ';
514
                        }
515
                        if ($val === null) {
516
                            $sql .= 'NULL';
517
                        } else {
518
                            $sql .= '\''
519
                                . $GLOBALS['dbi']->escapeString($val)
520
                                . '\'';
521
                        }
522
523
                        $first = false;
524
                    }
525
                    $sql .= ')';
526
                    if (isset($_POST['csv_replace'])) {
527
                        $sql .= ' ON DUPLICATE KEY UPDATE ';
528
                        foreach ($fields as $field) {
529
                            $fieldName = Util::backquote(
530
                                $field['Field']
531
                            );
532
                            $sql .= $fieldName . ' = VALUES(' . $fieldName
533
                                . '), ';
534
                        }
535
                        $sql = rtrim($sql, ', ');
536
                    }
537
538
                    /**
539
                     * @todo maybe we could add original line to verbose
540
                     * SQL in comment
541
                     */
542
                    $this->import->runQuery($sql, $sql, $sql_data);
543
                }
544
545 12
                $line++;
546 12
                $csv_finish = false;
547 12
                $values = [];
548 12
                $buffer = mb_substr($buffer, $i + 1);
549 12
                $len = mb_strlen($buffer);
550 12
                $i = 0;
551 12
                $lasti = -1;
552 12
                $ch = mb_substr($buffer, 0, 1);
553 12
                if ($max_lines > 0 && $line == $max_lines_constraint) {
554
                    $finished = 1;
555
                    break;
556
                }
557
            } // End of parser loop
558 12
            if ($max_lines > 0 && $line == $max_lines_constraint) {
559
                $finished = 1;
560
                break;
561
            }
562
        } // End of import loop
563
564 12
        if ($this->getAnalyze()) {
565
            /* Fill out all rows */
566 12
            $num_rows = count($rows);
567 12
            for ($i = 0; $i < $num_rows; ++$i) {
568 12
                for ($j = count($rows[$i]); $j < $max_cols; ++$j) {
569
                    $rows[$i][] = 'NULL';
570
                }
571
            }
572
573 12
            $col_names = $this->getColumnNames($col_names, $max_cols, $rows);
574 12
            $tbl_name = $this->getTableNameFromImport((string) $db);
575
576 12
            $tables[] = [
577 12
                $tbl_name,
578 12
                $col_names,
579 12
                $rows,
580
            ];
581
582
            /* Obtain the best-fit MySQL types for each column */
583 12
            $analyses = [];
584 12
            $analyses[] = $this->import->analyzeTable($tables[0]);
585
586
            /**
587
             * string $db_name (no backquotes)
588
             *
589
             * array $table = array(table_name, array() column_names, array()() rows)
590
             * array $tables = array of "$table"s
591
             *
592
             * array $analysis = array(array() column_types, array() column_sizes)
593
             * array $analyses = array of "$analysis"s
594
             *
595
             * array $create = array of SQL strings
596
             *
597
             * array $options = an associative array of options
598
             */
599
600
            /* Set database name to the currently selected one, if applicable,
601
             * Otherwise, check if user provided the database name in the request,
602
             * if not, set the default name
603
             */
604 12
            if (isset($_REQUEST['csv_new_db_name'])
605 12
                && strlen($_REQUEST['csv_new_db_name']) > 0
606
            ) {
607 4
                $newDb = $_REQUEST['csv_new_db_name'];
608
            } else {
609 8
                $result = $GLOBALS['dbi']->fetchResult('SHOW DATABASES');
610 8
                if (! is_array($result)) {
611 8
                    $result = [];
612
                }
613 8
                $newDb = 'CSV_DB ' . (count($result) + 1);
614
            }
615 12
            [$db_name, $options] = $this->getDbnameAndOptions($db, $newDb);
616
617
            /* Non-applicable parameters */
618 12
            $create = null;
619
620
            /* Created and execute necessary SQL statements from data */
621 12
            $this->import->buildSql($db_name, $tables, $analyses, $create, $options, $sql_data);
622
623 12
            unset($tables, $analyses);
624
        }
625
626
        // Commit any possible data in buffers
627 12
        $this->import->runQuery('', '', $sql_data);
628
629 12
        if (count($values) == 0 || $error) {
630 12
            return;
631
        }
632
633
        $message = Message::error(
634
            __('Invalid format of CSV input on line %d.')
635
        );
636
        $message->addParam($line);
637
        $error = true;
638
    }
639
640 12
    private function buildErrorsForParams(
641
        string $csvTerminated,
642
        string $csvEnclosed,
643
        string $csvEscaped,
644
        string $csvNewLine,
645
        string $errUrl
646
    ): array {
647 12
        global $error, $message;
648
649 12
        $param_error = false;
650 12
        if (strlen($csvTerminated) === 0) {
651
            $message = Message::error(
652
                __('Invalid parameter for CSV import: %s')
653
            );
654
            $message->addParam(__('Columns terminated with'));
655
            $error = true;
656
            $param_error = true;
657
            // The default dialog of MS Excel when generating a CSV produces a
658
            // semi-colon-separated file with no chance of specifying the
659
            // enclosing character. Thus, users who want to import this file
660
            // tend to remove the enclosing character on the Import dialog.
661
            // I could not find a test case where having no enclosing characters
662
            // confuses this script.
663
            // But the parser won't work correctly with strings so we allow just
664
            // one character.
665 12
        } elseif (mb_strlen($csvEnclosed) > 1) {
666
            $message = Message::error(
667
                __('Invalid parameter for CSV import: %s')
668
            );
669
            $message->addParam(__('Columns enclosed with'));
670
            $error = true;
671
            $param_error = true;
672
            // I could not find a test case where having no escaping characters
673
            // confuses this script.
674
            // But the parser won't work correctly with strings so we allow just
675
            // one character.
676 12
        } elseif (mb_strlen($csvEscaped) > 1) {
677
            $message = Message::error(
678
                __('Invalid parameter for CSV import: %s')
679
            );
680
            $message->addParam(__('Columns escaped with'));
681
            $error = true;
682
            $param_error = true;
683 12
        } elseif (mb_strlen($csvNewLine) != 1
684 12
            && $csvNewLine != 'auto'
685
        ) {
686
            $message = Message::error(
687
                __('Invalid parameter for CSV import: %s')
688
            );
689
            $message->addParam(__('Lines terminated with'));
690
            $error = true;
691
            $param_error = true;
692
        }
693
694
        // If there is an error in the parameters entered,
695
        // indicate that immediately.
696 12
        if ($param_error) {
697
            Generator::mysqlDie(
698
                $message->getMessage(),
699
                '',
700
                false,
701
                $errUrl
702
            );
703
        }
704
705 12
        return [$error, $message];
706
    }
707
708 12
    private function getTableNameFromImport(string $databaseName): string
709
    {
710 12
        global $import_file_name;
711
712 12
        $importFileName = basename($import_file_name, '.csv');
713 12
        $importFileName = mb_strtolower($importFileName);
714 12
        $importFileName = (string) preg_replace('/[^a-zA-Z0-9_]/', '_', $importFileName);
715
716
        // get new table name, if user didn't provide one, set the default name
717 12
        if (isset($_REQUEST['csv_new_tbl_name'])
718 12
            && strlen($_REQUEST['csv_new_tbl_name']) > 0
719
        ) {
720 4
            return $_REQUEST['csv_new_tbl_name'];
721
        }
722 8
        if (mb_strlen($databaseName)) {
723
            $result = $GLOBALS['dbi']->fetchResult('SHOW TABLES');
724
725
            // logic to get table name from filename
726
            // if no table then use filename as table name
727
            if (count($result) === 0) {
728
                return $importFileName;
729
            }
730
            // check to see if {filename} as table exist
731
            $nameArray = preg_grep('/' . $importFileName . '/isU', $result);
732
            // if no use filename as table name
733
            if (count($nameArray) === 0) {
734
                return $importFileName;
735
            }
736
            // check if {filename}_ as table exist
737
            $nameArray = preg_grep('/' . $importFileName . '_/isU', $result);
738
739
            return $importFileName . '_' . (count($nameArray) + 1);
740
        }
741
742 8
        return $importFileName;
743
    }
744
745 12
    private function getColumnNames(array $columnNames, int $maxCols, array $rows): array
746
    {
747 12
        if (isset($_REQUEST['csv_col_names'])) {
748
            $columnNames = array_splice($rows, 0, 1);
749
            $columnNames = $columnNames[0];
750
            // MySQL column names can't end with a space character.
751
            foreach ($columnNames as $key => $col_name) {
752
                $columnNames[$key] = rtrim($col_name);
753
            }
754
        }
755
756 12
        if ((isset($columnNames) && count($columnNames) != $maxCols)
757 12
            || ! isset($columnNames)
758
        ) {
759
            // Fill out column names
760 12
            for ($i = 0; $i < $maxCols; ++$i) {
761 12
                $columnNames[] = 'COL ' . ($i + 1);
762
            }
763
        }
764
765 12
        return $columnNames;
766
    }
767
768 12
    private function getSqlTemplateAndRequiredFields(
769
        ?string $db,
770
        ?string $table,
771
        ?string $csvColumns
772
    ): array {
773 12
        $requiredFields = 0;
774 12
        $sqlTemplate = '';
775 12
        $fields = [];
776 12
        if (! $this->getAnalyze() && $db !== null && $table !== null) {
777
            $sqlTemplate = 'INSERT';
778
            if (isset($_POST['csv_ignore'])) {
779
                $sqlTemplate .= ' IGNORE';
780
            }
781
            $sqlTemplate .= ' INTO ' . Util::backquote($table);
782
783
            $tmp_fields = $GLOBALS['dbi']->getColumns($db, $table);
784
785
            if (empty($csvColumns)) {
786
                $fields = $tmp_fields;
787
            } else {
788
                $sqlTemplate .= ' (';
789
                $fields = [];
790
                $tmp = preg_split('/,( ?)/', $csvColumns);
791
                foreach ($tmp as $key => $val) {
792
                    if (count($fields) > 0) {
793
                        $sqlTemplate .= ', ';
794
                    }
795
                    /* Trim also `, if user already included backquoted fields */
796
                    $val = trim($val, " \t\r\n\0\x0B`");
797
                    $found = false;
798
                    foreach ($tmp_fields as $field) {
799
                        if ($field['Field'] == $val) {
800
                            $found = true;
801
                            break;
802
                        }
803
                    }
804
                    if (! $found) {
805
                        $message = Message::error(
806
                            __(
807
                                'Invalid column (%s) specified! Ensure that columns'
808
                                . ' names are spelled correctly, separated by commas'
809
                                . ', and not enclosed in quotes.'
810
                            )
811
                        );
812
                        $message->addParam($val);
813
                        $error = true;
814
                        break;
815
                    }
816
                    if (isset($field)) {
817
                        $fields[] = $field;
818
                    }
819
                    $sqlTemplate .= Util::backquote($val);
820
                }
821
                $sqlTemplate .= ') ';
822
            }
823
824
            $requiredFields = count($fields);
825
826
            $sqlTemplate .= ' VALUES (';
827
        }
828
829 12
        return [$sqlTemplate, $requiredFields, $fields];
830
    }
831
832
    /**
833
     * Read the expected column_separated_with String of length
834
     * $csv_terminated_len from the $buffer
835
     * into variable $ch and return the read string $ch
836
     *
837
     * @param string $buffer             The original string buffer read from
838
     *                                   csv file
839
     * @param string $ch                 Partially read "column Separated with"
840
     *                                   string, also used to return after
841
     *                                   reading length equal $csv_terminated_len
842
     * @param int    $i                  Current read counter of buffer string
843
     * @param int    $csv_terminated_len The length of "column separated with"
844
     *                                   String
845
     *
846
     * @return string
847
     */
848
    public function readCsvTerminatedString($buffer, $ch, $i, $csv_terminated_len)
849
    {
850
        for ($j = 0; $j < $csv_terminated_len - 1; $j++) {
851
            $i++;
852
            $ch .= mb_substr($buffer, $i, 1);
853
        }
854
855
        return $ch;
856
    }
857
858
    /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */
859
860
    /**
861
     * Returns true if the table should be analyzed, false otherwise
862
     *
863
     * @return bool
864
     */
865 12
    private function getAnalyze()
866
    {
867 12
        return $this->_analyze;
868
    }
869
870
    /**
871
     * Sets to true if the table should be analyzed, false otherwise
872
     *
873
     * @param bool $analyze status
874
     *
875
     * @return void
876
     */
877 20
    private function setAnalyze($analyze)
878
    {
879 20
        $this->_analyze = $analyze;
880 20
    }
881
}
882