Merge branch 'm22_MDL-33053_AICC_flattened_TOC' of git://github.com/scara/moodle...
[moodle.git] / lib / csvlib.class.php
blob2eb9df30bf6930d228d7092e01c63cda8721cf55
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17 /**
18 * This is a one-line short description of the file
20 * You can have a rather longer description of the file as well,
21 * if you like, and it can span multiple lines.
23 * @package core
24 * @subpackage lib
25 * @copyright Petr Skoda
26 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
29 defined('MOODLE_INTERNAL') || die();
31 /**
32 * Utitily class for importing of CSV files.
33 * @copyright Petr Skoda
34 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
35 * @package moodlecore
37 class csv_import_reader {
38 /**
39 * @var int import identifier
41 var $_iid;
42 /**
43 * @var string which script imports?
45 var $_type;
46 /**
47 * @var string|null Null if ok, error msg otherwise
49 var $_error;
50 /**
51 * @var array cached columns
53 var $_columns;
54 /**
55 * @var object file handle used during import
57 var $_fp;
59 /**
60 * Contructor
62 * @param int $iid import identifier
63 * @param string $type which script imports?
65 function csv_import_reader($iid, $type) {
66 $this->_iid = $iid;
67 $this->_type = $type;
70 /**
71 * Parse this content
73 * @global object
74 * @global object
75 * @param string $content passed by ref for memory reasons, unset after return
76 * @param string $encoding content encoding
77 * @param string $delimiter_name separator (comma, semicolon, colon, cfg)
78 * @param string $column_validation name of function for columns validation, must have one param $columns
79 * @return bool false if error, count of data lines if ok; use get_error() to get error string
81 function load_csv_content(&$content, $encoding, $delimiter_name, $column_validation=null) {
82 global $USER, $CFG;
84 $this->close();
85 $this->_error = null;
87 $textlib = textlib_get_instance();
89 $content = $textlib->convert($content, $encoding, 'utf-8');
90 // remove Unicode BOM from first line
91 $content = $textlib->trim_utf8_bom($content);
92 // Fix mac/dos newlines
93 $content = preg_replace('!\r\n?!', "\n", $content);
94 // is there anyting in file?
95 $columns = strtok($content, "\n");
96 if ($columns === false) {
97 $this->_error = get_string('csvemptyfile', 'error');
98 return false;
100 $csv_delimiter = csv_import_reader::get_delimiter($delimiter_name);
101 $csv_encode = csv_import_reader::get_encoded_delimiter($delimiter_name);
103 // process header - list of columns
104 $columns = explode($csv_delimiter, $columns);
105 $col_count = count($columns);
106 if ($col_count === 0) {
107 $this->_error = get_string('csvemptyfile', 'error');
108 return false;
111 foreach ($columns as $key=>$value) {
112 $columns[$key] = str_replace($csv_encode, $csv_delimiter, trim($value));
114 if ($column_validation) {
115 $result = $column_validation($columns);
116 if ($result !== true) {
117 $this->_error = $result;
118 return false;
121 $this->_columns = $columns; // cached columns
123 // open file for writing
124 $filename = $CFG->tempdir.'/csvimport/'.$this->_type.'/'.$USER->id.'/'.$this->_iid;
125 $fp = fopen($filename, "w");
126 fwrite($fp, serialize($columns)."\n");
128 // again - do we have any data for processing?
129 $line = strtok("\n");
130 $data_count = 0;
131 while ($line !== false) {
132 $line = explode($csv_delimiter, $line);
133 foreach ($line as $key=>$value) {
134 $line[$key] = str_replace($csv_encode, $csv_delimiter, trim($value));
136 if (count($line) !== $col_count) {
137 // this is critical!!
138 $this->_error = get_string('csvweirdcolumns', 'error');
139 fclose($fp);
140 $this->cleanup();
141 return false;
143 fwrite($fp, serialize($line)."\n");
144 $data_count++;
145 $line = strtok("\n");
148 fclose($fp);
149 return $data_count;
153 * Returns list of columns
155 * @return array
157 function get_columns() {
158 if (isset($this->_columns)) {
159 return $this->_columns;
162 global $USER, $CFG;
164 $filename = $CFG->tempdir.'/csvimport/'.$this->_type.'/'.$USER->id.'/'.$this->_iid;
165 if (!file_exists($filename)) {
166 return false;
168 $fp = fopen($filename, "r");
169 $line = fgets($fp);
170 fclose($fp);
171 if ($line === false) {
172 return false;
174 $this->_columns = unserialize($line);
175 return $this->_columns;
179 * Init iterator.
181 * @global object
182 * @global object
183 * @return bool Success
185 function init() {
186 global $CFG, $USER;
188 if (!empty($this->_fp)) {
189 $this->close();
191 $filename = $CFG->tempdir.'/csvimport/'.$this->_type.'/'.$USER->id.'/'.$this->_iid;
192 if (!file_exists($filename)) {
193 return false;
195 if (!$this->_fp = fopen($filename, "r")) {
196 return false;
198 //skip header
199 return (fgets($this->_fp) !== false);
203 * Get next line
205 * @return mixed false, or an array of values
207 function next() {
208 if (empty($this->_fp) or feof($this->_fp)) {
209 return false;
211 if ($ser = fgets($this->_fp)) {
212 return unserialize($ser);
213 } else {
214 return false;
219 * Release iteration related resources
221 * @return void
223 function close() {
224 if (!empty($this->_fp)) {
225 fclose($this->_fp);
226 $this->_fp = null;
231 * Get last error
233 * @return string error text of null if none
235 function get_error() {
236 return $this->_error;
240 * Cleanup temporary data
242 * @global object
243 * @global object
244 * @param boolean $full true means do a full cleanup - all sessions for current user, false only the active iid
246 function cleanup($full=false) {
247 global $USER, $CFG;
249 if ($full) {
250 @remove_dir($CFG->tempdir.'/csvimport/'.$this->_type.'/'.$USER->id);
251 } else {
252 @unlink($CFG->tempdir.'/csvimport/'.$this->_type.'/'.$USER->id.'/'.$this->_iid);
257 * Get list of cvs delimiters
259 * @return array suitable for selection box
261 static function get_delimiter_list() {
262 global $CFG;
263 $delimiters = array('comma'=>',', 'semicolon'=>';', 'colon'=>':', 'tab'=>'\\t');
264 if (isset($CFG->CSV_DELIMITER) and strlen($CFG->CSV_DELIMITER) === 1 and !in_array($CFG->CSV_DELIMITER, $delimiters)) {
265 $delimiters['cfg'] = $CFG->CSV_DELIMITER;
267 return $delimiters;
271 * Get delimiter character
273 * @param string separator name
274 * @return string delimiter char
276 static function get_delimiter($delimiter_name) {
277 global $CFG;
278 switch ($delimiter_name) {
279 case 'colon': return ':';
280 case 'semicolon': return ';';
281 case 'tab': return "\t";
282 case 'cfg': if (isset($CFG->CSV_DELIMITER)) { return $CFG->CSV_DELIMITER; } // no break; fall back to comma
283 case 'comma': return ',';
288 * Get encoded delimiter character
290 * @global object
291 * @param string separator name
292 * @return string encoded delimiter char
294 function get_encoded_delimiter($delimiter_name) {
295 global $CFG;
296 if ($delimiter_name == 'cfg' and isset($CFG->CSV_ENCODE)) {
297 return $CFG->CSV_ENCODE;
299 $delimiter = csv_import_reader::get_delimiter($delimiter_name);
300 return '&#'.ord($delimiter);
304 * Create new import id
306 * @global object
307 * @param string who imports?
308 * @return int iid
310 function get_new_iid($type) {
311 global $USER;
313 $filename = make_temp_directory('csvimport/'.$type.'/'.$USER->id);
315 // use current (non-conflicting) time stamp
316 $iiid = time();
317 while (file_exists($filename.'/'.$iiid)) {
318 $iiid--;
321 return $iiid;