Fix for the Open in New Window in Patient/Client->Patients search gui, take 2.
[openemr.git] / phpmyadmin / libraries / import / csv.php
blob7acb3eeea20675359108e2bef3353abb96cbbb3d
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 * @version $Id$
8 */
9 if (! defined('PHPMYADMIN')) {
10 exit;
13 /**
16 if ($plugin_param !== 'table') {
17 return;
20 if (isset($plugin_list)) {
21 $plugin_list['csv'] = array(
22 'text' => 'strCSV',
23 'extension' => 'csv',
24 'options' => array(
25 array('type' => 'bool', 'name' => 'replace', 'text' => 'strReplaceTable'),
26 array('type' => 'bool', 'name' => 'ignore', 'text' => 'strIgnoreDuplicates'),
27 array('type' => 'text', 'name' => 'terminated', 'text' => 'strFieldsTerminatedBy', 'size' => 2, 'len' => 2),
28 array('type' => 'text', 'name' => 'enclosed', 'text' => 'strFieldsEnclosedBy', 'size' => 2, 'len' => 2),
29 array('type' => 'text', 'name' => 'escaped', 'text' => 'strFieldsEscapedBy', 'size' => 2, 'len' => 2),
30 array('type' => 'text', 'name' => 'new_line', 'text' => 'strLinesTerminatedBy', 'size' => 2),
31 array('type' => 'text', 'name' => 'columns', 'text' => 'strColumnNames'),
33 'options_text' => 'strOptions',
35 /* We do not define function when plugin is just queried for information above */
36 return;
39 $replacements = array(
40 '\\n' => "\n",
41 '\\t' => "\t",
42 '\\r' => "\r",
44 $csv_terminated = strtr($csv_terminated, $replacements);
45 $csv_enclosed = strtr($csv_enclosed, $replacements);
46 $csv_escaped = strtr($csv_escaped, $replacements);
47 $csv_new_line = strtr($csv_new_line, $replacements);
49 if (strlen($csv_terminated) != 1) {
50 $message = sprintf($strInvalidCSVParameter, $strFieldsTerminatedBy);
51 $show_error_header = TRUE;
52 $error = TRUE;
53 // The default dialog of MS Excel when generating a CSV produces a
54 // semi-colon-separated file with no chance of specifying the
55 // enclosing character. Thus, users who want to import this file
56 // tend to remove the enclosing character on the Import dialog.
57 // I could not find a test case where having no enclosing characters
58 // confuses this script.
59 // But the parser won't work correctly with strings so we allow just
60 // one character.
61 } elseif (strlen($csv_enclosed) > 1) {
62 $message = sprintf($strInvalidCSVParameter, $strFieldsEnclosedBy);
63 $show_error_header = TRUE;
64 $error = TRUE;
65 } elseif (strlen($csv_escaped) != 1) {
66 $message = sprintf($strInvalidCSVParameter, $strFieldsEscapedBy);
67 $show_error_header = TRUE;
68 $error = TRUE;
69 } elseif (strlen($csv_new_line) != 1 && $csv_new_line != 'auto') {
70 $message = sprintf($strInvalidCSVParameter, $strLinesTerminatedBy);
71 $show_error_header = TRUE;
72 $error = TRUE;
75 $buffer = '';
76 if (isset($csv_replace)) {
77 $sql_template = 'REPLACE';
78 } else {
79 $sql_template = 'INSERT';
80 if (isset($csv_ignore)) {
81 $sql_template .= ' IGNORE';
84 $sql_template .= ' INTO ' . PMA_backquote($table);
86 $tmp_fields = PMA_DBI_get_fields($db, $table);
88 if (empty($csv_columns)) {
89 $fields = $tmp_fields;
90 } else {
91 $sql_template .= ' (';
92 $fields = array();
93 $tmp = split(',( ?)', $csv_columns);
94 foreach ($tmp as $key => $val) {
95 if (count($fields) > 0) {
96 $sql_template .= ', ';
98 $val = trim($val);
99 $found = FALSE;
100 foreach ($tmp_fields as $id => $field) {
101 if ($field['Field'] == $val) {
102 $found = TRUE;
103 break;
106 if (!$found) {
107 $message = sprintf($strInvalidColumn, $val);
108 $show_error_header = TRUE;
109 $error = TRUE;
110 break;
112 $fields[] = $field;
113 $sql_template .= PMA_backquote($val);
115 $sql_template .= ') ';
118 $required_fields = count($fields);
120 $sql_template .= ' VALUES (';
122 // Defaults for parser
123 $i = 0;
124 $len = 0;
125 $line = 1;
126 $lasti = -1;
127 $values = array();
128 $csv_finish = FALSE;
130 while (!($finished && $i >= $len) && !$error && !$timeout_passed) {
131 $data = PMA_importGetNextChunk();
132 if ($data === FALSE) {
133 // subtract data we didn't handle yet and stop processing
134 $offset -= strlen($buffer);
135 break;
136 } elseif ($data === TRUE) {
137 // Handle rest of buffer
138 } else {
139 // Append new data to buffer
140 $buffer .= $data;
141 unset($data);
142 // Do not parse string when we're not at the end and don't have new line inside
143 if (($csv_new_line == 'auto' && strpos($buffer, "\r") === FALSE && strpos($buffer, "\n") === FALSE)
144 || ($csv_new_line != 'auto' && strpos($buffer, $csv_new_line) === FALSE)) {
145 continue;
149 // Current length of our buffer
150 $len = strlen($buffer);
151 // Currently parsed char
152 $ch = $buffer[$i];
153 while ($i < $len) {
154 // Deadlock protection
155 if ($lasti == $i && $lastlen == $len) {
156 $message = sprintf($strInvalidCSVFormat, $line);
157 $show_error_header = TRUE;
158 $error = TRUE;
159 break;
161 $lasti = $i;
162 $lastlen = $len;
164 // This can happen with auto EOL and \r at the end of buffer
165 if (!$csv_finish) {
166 // Grab empty field
167 if ($ch == $csv_terminated) {
168 if ($i == $len - 1) {
169 break;
171 $values[] = '';
172 $i++;
173 $ch = $buffer[$i];
174 continue;
177 // Grab one field
178 $fallbacki = $i;
179 if ($ch == $csv_enclosed) {
180 if ($i == $len - 1) {
181 break;
183 $need_end = TRUE;
184 $i++;
185 $ch = $buffer[$i];
186 } else {
187 $need_end = FALSE;
189 $fail = FALSE;
190 $value = '';
191 while (($need_end && $ch != $csv_enclosed)
192 || (!$need_end && !($ch == $csv_terminated
193 || $ch == $csv_new_line || ($csv_new_line == 'auto'
194 && ($ch == "\r" || $ch == "\n"))))) {
195 if ($ch == $csv_escaped) {
196 if ($i == $len - 1) {
197 $fail = TRUE;
198 break;
200 $i++;
201 $ch = $buffer[$i];
203 $value .= $ch;
204 if ($i == $len - 1) {
205 if (!$finished) {
206 $fail = TRUE;
208 break;
210 $i++;
211 $ch = $buffer[$i];
214 // unquoted NULL string
215 if (false === $need_end && $value === 'NULL') {
216 $value = null;
219 if ($fail) {
220 $i = $fallbacki;
221 $ch = $buffer[$i];
222 break;
224 // Need to strip trailing enclosing char?
225 if ($need_end && $ch == $csv_enclosed) {
226 if ($finished && $i == $len - 1) {
227 $ch = NULL;
228 } elseif ($i == $len - 1) {
229 $i = $fallbacki;
230 $ch = $buffer[$i];
231 break;
232 } else {
233 $i++;
234 $ch = $buffer[$i];
237 // Are we at the end?
238 if ($ch == $csv_new_line || ($csv_new_line == 'auto' && ($ch == "\r" || $ch == "\n")) || ($finished && $i == $len - 1)) {
239 $csv_finish = TRUE;
241 // Go to next char
242 if ($ch == $csv_terminated) {
243 if ($i == $len - 1) {
244 $i = $fallbacki;
245 $ch = $buffer[$i];
246 break;
248 $i++;
249 $ch = $buffer[$i];
251 // If everything went okay, store value
252 $values[] = $value;
255 // End of line
256 if ($csv_finish || $ch == $csv_new_line || ($csv_new_line == 'auto' && ($ch == "\r" || $ch == "\n"))) {
257 if ($csv_new_line == 'auto' && $ch == "\r") { // Handle "\r\n"
258 if ($i >= ($len - 2) && !$finished) {
259 break; // We need more data to decide new line
261 if ($buffer[$i + 1] == "\n") {
262 $i++;
265 // We didn't parse value till the end of line, so there was empty one
266 if (!$csv_finish) {
267 $values[] = '';
269 // Do we have correct count of values?
270 if (count($values) != $required_fields) {
272 // Hack for excel
273 if ($values[count($values) - 1] == ';') {
274 unset($values[count($values) - 1]);
275 } else {
276 $message = sprintf($strInvalidCSVFieldCount, $line);
277 $show_error_header = TRUE;
278 $error = TRUE;
279 break;
283 $first = TRUE;
284 $sql = $sql_template;
285 foreach ($values as $key => $val) {
286 if (!$first) {
287 $sql .= ', ';
289 if ($val === null) {
290 $sql .= 'NULL';
291 } else {
292 $sql .= '\'' . addslashes($val) . '\'';
295 $first = FALSE;
297 $sql .= ')';
300 * @todo maybe we could add original line to verbose SQL in comment
302 PMA_importRunQuery($sql, $sql);
303 $line++;
304 $csv_finish = FALSE;
305 $values = array();
306 $buffer = substr($buffer, $i + 1);
307 $len = strlen($buffer);
308 $i = 0;
309 $lasti = -1;
310 $ch = $buffer[0];
312 } // End of parser loop
313 } // End of import loop
315 // Commit any possible data in buffers
316 PMA_importRunQuery();
318 if (count($values) != 0 && !$error) {
319 $message = sprintf($strInvalidCSVFormat, $line);
320 $show_error_header = TRUE;
321 $error = TRUE;