Upgraded phpmyadmin to 4.0.4 (All Languages) - No modifications yet
[openemr.git] / phpmyadmin / libraries / plugins / import / ImportCsv.class.php
blobd4805648445df77d2f2b51364e5aff24779265ce
1 <?php
2 /* vim: set expandtab sw=4 ts=4 sts=4: */
3 /**
4 * CSV import plugin for phpMyAdmin
6 * @todo add an option for handling NULL values
7 * @package PhpMyAdmin-Import
8 * @subpackage CSV
9 */
10 if (! defined('PHPMYADMIN')) {
11 exit;
14 /* Get the import interface */
15 require_once 'libraries/plugins/import/AbstractImportCsv.class.php';
17 /**
18 * Handles the import for the CSV format
20 * @package PhpMyAdmin-Import
21 * @subpackage CSV
23 class ImportCsv extends AbstractImportCsv
25 /**
26 * Whether to analyze tables
28 * @var bool
30 private $_analyze;
32 /**
33 * Constructor
35 public function __construct()
37 $this->setProperties();
40 /**
41 * Sets the import plugin properties.
42 * Called in the constructor.
44 * @return void
46 protected function setProperties()
48 $this->_setAnalyze(false);
50 if ($GLOBALS['plugin_param'] !== 'table') {
51 $this->_setAnalyze(true);
54 $generalOptions = parent::setProperties();
55 $this->properties->setText('CSV');
56 $this->properties->setExtension('csv');
58 if ($GLOBALS['plugin_param'] !== 'table') {
59 $leaf = new BoolPropertyItem();
60 $leaf->setName("col_names");
61 $leaf->setText(
62 __(
63 'The first line of the file contains the table column names'
64 . ' <i>(if this is unchecked, the first line will become part'
65 . ' of the data)</i>'
68 $generalOptions->addProperty($leaf);
69 } else {
70 $hint = new PMA_Message(
71 __(
72 'If the data in each row of the file is not'
73 . ' in the same order as in the database, list the corresponding'
74 . ' column names here. Column names must be separated by commas'
75 . ' and not enclosed in quotations.'
78 $leaf = new TextPropertyItem();
79 $leaf->setName("columns");
80 $leaf->setText(
81 __('Column names: ')
82 . PMA_Util::showHint($hint)
84 $generalOptions->addProperty($leaf);
87 $leaf = new BoolPropertyItem();
88 $leaf->setName("ignore");
89 $leaf->setText(__('Do not abort on INSERT error'));
90 $generalOptions->addProperty($leaf);
94 /**
95 * This method is called when any PluginManager to which the observer
96 * is attached calls PluginManager::notify()
98 * @param SplSubject $subject The PluginManager notifying the observer
99 * of an update.
101 * @return void
103 public function update (SplSubject $subject)
108 * Handles the whole import logic
110 * @return void
112 public function doImport()
114 global $db, $table, $csv_terminated, $csv_enclosed, $csv_escaped,
115 $csv_new_line, $csv_columns, $err_url;
116 // $csv_replace and $csv_ignore should have been here,
117 // but we use directly from $_POST
118 global $error, $timeout_passed, $finished, $message;
120 $replacements = array(
121 '\\n' => "\n",
122 '\\t' => "\t",
123 '\\r' => "\r",
125 $csv_terminated = strtr($csv_terminated, $replacements);
126 $csv_enclosed = strtr($csv_enclosed, $replacements);
127 $csv_escaped = strtr($csv_escaped, $replacements);
128 $csv_new_line = strtr($csv_new_line, $replacements);
130 $param_error = false;
131 if (strlen($csv_terminated) != 1) {
132 $message = PMA_Message::error(
133 __('Invalid parameter for CSV import: %s')
135 $message->addParam(__('Columns terminated by'), false);
136 $error = true;
137 $param_error = true;
138 // The default dialog of MS Excel when generating a CSV produces a
139 // semi-colon-separated file with no chance of specifying the
140 // enclosing character. Thus, users who want to import this file
141 // tend to remove the enclosing character on the Import dialog.
142 // I could not find a test case where having no enclosing characters
143 // confuses this script.
144 // But the parser won't work correctly with strings so we allow just
145 // one character.
146 } elseif (strlen($csv_enclosed) > 1) {
147 $message = PMA_Message::error(
148 __('Invalid parameter for CSV import: %s')
150 $message->addParam(__('Columns enclosed by'), false);
151 $error = true;
152 $param_error = true;
153 } elseif (strlen($csv_escaped) != 1) {
154 $message = PMA_Message::error(
155 __('Invalid parameter for CSV import: %s')
157 $message->addParam(__('Columns escaped by'), false);
158 $error = true;
159 $param_error = true;
160 } elseif (strlen($csv_new_line) != 1 && $csv_new_line != 'auto') {
161 $message = PMA_Message::error(
162 __('Invalid parameter for CSV import: %s')
164 $message->addParam(__('Lines terminated by'), false);
165 $error = true;
166 $param_error = true;
169 // If there is an error in the parameters entered,
170 // indicate that immediately.
171 if ($param_error) {
172 PMA_Util::mysqlDie($message->getMessage(), '', '', $err_url);
175 $buffer = '';
176 $required_fields = 0;
178 if (! $this->_getAnalyze()) {
179 if (isset($_POST['csv_replace'])) {
180 $sql_template = 'REPLACE';
181 } else {
182 $sql_template = 'INSERT';
183 if (isset($_POST['csv_ignore'])) {
184 $sql_template .= ' IGNORE';
187 $sql_template .= ' INTO ' . PMA_Util::backquote($table);
189 $tmp_fields = PMA_DBI_get_columns($db, $table);
191 if (empty($csv_columns)) {
192 $fields = $tmp_fields;
193 } else {
194 $sql_template .= ' (';
195 $fields = array();
196 $tmp = preg_split('/,( ?)/', $csv_columns);
197 foreach ($tmp as $key => $val) {
198 if (count($fields) > 0) {
199 $sql_template .= ', ';
201 /* Trim also `, if user already included backquoted fields */
202 $val = trim($val, " \t\r\n\0\x0B`");
203 $found = false;
204 foreach ($tmp_fields as $field) {
205 if ($field['Field'] == $val) {
206 $found = true;
207 break;
210 if (! $found) {
211 $message = PMA_Message::error(
213 'Invalid column (%s) specified! Ensure that columns'
214 . ' names are spelled correctly, separated by commas'
215 . ', and not enclosed in quotes.'
218 $message->addParam($val);
219 $error = true;
220 break;
222 $fields[] = $field;
223 $sql_template .= PMA_Util::backquote($val);
225 $sql_template .= ') ';
228 $required_fields = count($fields);
230 $sql_template .= ' VALUES (';
233 // Defaults for parser
234 $i = 0;
235 $len = 0;
236 $line = 1;
237 $lasti = -1;
238 $values = array();
239 $csv_finish = false;
241 $tempRow = array();
242 $rows = array();
243 $col_names = array();
244 $tables = array();
246 $col_count = 0;
247 $max_cols = 0;
249 while (! ($finished && $i >= $len) && ! $error && ! $timeout_passed) {
250 $data = PMA_importGetNextChunk();
251 if ($data === false) {
252 // subtract data we didn't handle yet and stop processing
253 $offset -= strlen($buffer);
254 break;
255 } elseif ($data === true) {
256 // Handle rest of buffer
257 } else {
258 // Append new data to buffer
259 $buffer .= $data;
260 unset($data);
261 // Do not parse string when we're not at the end
262 // and don't have new line inside
263 if (($csv_new_line == 'auto'
264 && strpos($buffer, "\r") === false
265 && strpos($buffer, "\n") === false)
266 || ($csv_new_line != 'auto'
267 && strpos($buffer, $csv_new_line) === false)
269 continue;
273 // Current length of our buffer
274 $len = strlen($buffer);
275 // Currently parsed char
276 $ch = $buffer[$i];
277 while ($i < $len) {
278 // Deadlock protection
279 if ($lasti == $i && $lastlen == $len) {
280 $message = PMA_Message::error(
281 __('Invalid format of CSV input on line %d.')
283 $message->addParam($line);
284 $error = true;
285 break;
287 $lasti = $i;
288 $lastlen = $len;
290 // This can happen with auto EOL and \r at the end of buffer
291 if (! $csv_finish) {
292 // Grab empty field
293 if ($ch == $csv_terminated) {
294 if ($i == $len - 1) {
295 break;
297 $values[] = '';
298 $i++;
299 $ch = $buffer[$i];
300 continue;
303 // Grab one field
304 $fallbacki = $i;
305 if ($ch == $csv_enclosed) {
306 if ($i == $len - 1) {
307 break;
309 $need_end = true;
310 $i++;
311 $ch = $buffer[$i];
312 } else {
313 $need_end = false;
315 $fail = false;
316 $value = '';
317 while (($need_end
318 && ( $ch != $csv_enclosed || $csv_enclosed == $csv_escaped ))
319 || ( ! $need_end
320 && ! ( $ch == $csv_terminated
321 || $ch == $csv_new_line
322 || ( $csv_new_line == 'auto'
323 && ( $ch == "\r" || $ch == "\n" ) ) ) )
325 if ($ch == $csv_escaped) {
326 if ($i == $len - 1) {
327 $fail = true;
328 break;
330 $i++;
331 $ch = $buffer[$i];
332 if ($csv_enclosed == $csv_escaped
333 && ($ch == $csv_terminated
334 || $ch == $csv_new_line
335 || ($csv_new_line == 'auto'
336 && ($ch == "\r" || $ch == "\n")))
338 break;
341 $value .= $ch;
342 if ($i == $len - 1) {
343 if (! $finished) {
344 $fail = true;
346 break;
348 $i++;
349 $ch = $buffer[$i];
352 // unquoted NULL string
353 if (false === $need_end && $value === 'NULL') {
354 $value = null;
357 if ($fail) {
358 $i = $fallbacki;
359 $ch = $buffer[$i];
360 break;
362 // Need to strip trailing enclosing char?
363 if ($need_end && $ch == $csv_enclosed) {
364 if ($finished && $i == $len - 1) {
365 $ch = null;
366 } elseif ($i == $len - 1) {
367 $i = $fallbacki;
368 $ch = $buffer[$i];
369 break;
370 } else {
371 $i++;
372 $ch = $buffer[$i];
375 // Are we at the end?
376 if ($ch == $csv_new_line
377 || ($csv_new_line == 'auto' && ($ch == "\r" || $ch == "\n"))
378 || ($finished && $i == $len - 1)
380 $csv_finish = true;
382 // Go to next char
383 if ($ch == $csv_terminated) {
384 if ($i == $len - 1) {
385 $i = $fallbacki;
386 $ch = $buffer[$i];
387 break;
389 $i++;
390 $ch = $buffer[$i];
392 // If everything went okay, store value
393 $values[] = $value;
396 // End of line
397 if ($csv_finish
398 || $ch == $csv_new_line
399 || ($csv_new_line == 'auto' && ($ch == "\r" || $ch == "\n"))
401 if ($csv_new_line == 'auto' && $ch == "\r") { // Handle "\r\n"
402 if ($i >= ($len - 2) && ! $finished) {
403 break; // We need more data to decide new line
405 if ($buffer[$i + 1] == "\n") {
406 $i++;
409 // We didn't parse value till the end of line, so there was
410 // empty one
411 if (! $csv_finish) {
412 $values[] = '';
415 if ($this->_getAnalyze()) {
416 foreach ($values as $val) {
417 $tempRow[] = $val;
418 ++$col_count;
421 if ($col_count > $max_cols) {
422 $max_cols = $col_count;
424 $col_count = 0;
426 $rows[] = $tempRow;
427 $tempRow = array();
428 } else {
429 // Do we have correct count of values?
430 if (count($values) != $required_fields) {
432 // Hack for excel
433 if ($values[count($values) - 1] == ';') {
434 unset($values[count($values) - 1]);
435 } else {
436 $message = PMA_Message::error(
437 __('Invalid column count in CSV input on line %d.')
439 $message->addParam($line);
440 $error = true;
441 break;
445 $first = true;
446 $sql = $sql_template;
447 foreach ($values as $key => $val) {
448 if (! $first) {
449 $sql .= ', ';
451 if ($val === null) {
452 $sql .= 'NULL';
453 } else {
454 $sql .= '\''
455 . PMA_Util::sqlAddSlashes($val)
456 . '\'';
459 $first = false;
461 $sql .= ')';
464 * @todo maybe we could add original line to verbose
465 * SQL in comment
467 PMA_importRunQuery($sql, $sql);
470 $line++;
471 $csv_finish = false;
472 $values = array();
473 $buffer = substr($buffer, $i + 1);
474 $len = strlen($buffer);
475 $i = 0;
476 $lasti = -1;
477 $ch = $buffer[0];
479 } // End of parser loop
480 } // End of import loop
482 if ($this->_getAnalyze()) {
483 /* Fill out all rows */
484 $num_rows = count($rows);
485 for ($i = 0; $i < $num_rows; ++$i) {
486 for ($j = count($rows[$i]); $j < $max_cols; ++$j) {
487 $rows[$i][] = 'NULL';
491 if (isset($_REQUEST['csv_col_names'])) {
492 $col_names = array_splice($rows, 0, 1);
493 $col_names = $col_names[0];
496 if ((isset($col_names) && count($col_names) != $max_cols)
497 || ! isset($col_names)
499 // Fill out column names
500 for ($i = 0; $i < $max_cols; ++$i) {
501 $col_names[] = 'COL '.($i+1);
505 if (strlen($db)) {
506 $result = PMA_DBI_fetch_result('SHOW TABLES');
507 $tbl_name = 'TABLE '.(count($result) + 1);
508 } else {
509 $tbl_name = 'TBL_NAME';
512 $tables[] = array($tbl_name, $col_names, $rows);
514 /* Obtain the best-fit MySQL types for each column */
515 $analyses = array();
516 $analyses[] = PMA_analyzeTable($tables[0]);
519 * string $db_name (no backquotes)
521 * array $table = array(table_name, array() column_names, array()() rows)
522 * array $tables = array of "$table"s
524 * array $analysis = array(array() column_types, array() column_sizes)
525 * array $analyses = array of "$analysis"s
527 * array $create = array of SQL strings
529 * array $options = an associative array of options
532 /* Set database name to the currently selected one, if applicable */
533 if (strlen($db)) {
534 $db_name = $db;
535 $options = array('create_db' => false);
536 } else {
537 $db_name = 'CSV_DB';
538 $options = null;
541 /* Non-applicable parameters */
542 $create = null;
544 /* Created and execute necessary SQL statements from data */
545 PMA_buildSQL($db_name, $tables, $analyses, $create, $options);
547 unset($tables);
548 unset($analyses);
551 // Commit any possible data in buffers
552 PMA_importRunQuery();
554 if (count($values) != 0 && ! $error) {
555 $message = PMA_Message::error(
556 __('Invalid format of CSV input on line %d.')
558 $message->addParam($line);
559 $error = true;
564 /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */
568 * Returns true if the table should be analyzed, false otherwise
570 * @return bool
572 private function _getAnalyze()
574 return $this->_analyze;
578 * Sets to true if the table should be analyzed, false otherwise
580 * @param bool $analyze status
582 * @return void
584 private function _setAnalyze($analyze)
586 $this->_analyze = $analyze;