2 // This file is part of Moodle - http://moodle.org/
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.
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/>.
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.
25 * @copyright Petr Skoda
26 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
29 defined('MOODLE_INTERNAL') ||
die();
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
37 class csv_import_reader
{
39 * @var int import identifier
43 * @var string which script imports?
47 * @var string|null Null if ok, error msg otherwise
51 * @var array cached columns
55 * @var object file handle used during import
62 * @param int $iid import identifier
63 * @param string $type which script imports?
65 function csv_import_reader($iid, $type) {
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) {
87 $content = textlib
::convert($content, $encoding, 'utf-8');
88 // remove Unicode BOM from first line
89 $content = textlib
::trim_utf8_bom($content);
90 // Fix mac/dos newlines
91 $content = preg_replace('!\r\n?!', "\n", $content);
92 // is there anyting in file?
93 $columns = strtok($content, "\n");
94 if ($columns === false) {
95 $this->_error
= get_string('csvemptyfile', 'error');
98 $csv_delimiter = csv_import_reader
::get_delimiter($delimiter_name);
99 $csv_encode = csv_import_reader
::get_encoded_delimiter($delimiter_name);
101 // process header - list of columns
102 $columns = explode($csv_delimiter, $columns);
103 $col_count = count($columns);
104 if ($col_count === 0) {
105 $this->_error
= get_string('csvemptyfile', 'error');
109 foreach ($columns as $key=>$value) {
110 $columns[$key] = str_replace($csv_encode, $csv_delimiter, trim($value));
112 if ($column_validation) {
113 $result = $column_validation($columns);
114 if ($result !== true) {
115 $this->_error
= $result;
119 $this->_columns
= $columns; // cached columns
121 // open file for writing
122 $filename = $CFG->tempdir
.'/csvimport/'.$this->_type
.'/'.$USER->id
.'/'.$this->_iid
;
123 $fp = fopen($filename, "w");
124 fwrite($fp, serialize($columns)."\n");
126 // again - do we have any data for processing?
127 $line = strtok("\n");
129 while ($line !== false) {
130 $line = explode($csv_delimiter, $line);
131 foreach ($line as $key=>$value) {
132 $line[$key] = str_replace($csv_encode, $csv_delimiter, trim($value));
134 if (count($line) !== $col_count) {
135 // this is critical!!
136 $this->_error
= get_string('csvweirdcolumns', 'error');
141 fwrite($fp, serialize($line)."\n");
143 $line = strtok("\n");
151 * Returns list of columns
155 function get_columns() {
156 if (isset($this->_columns
)) {
157 return $this->_columns
;
162 $filename = $CFG->tempdir
.'/csvimport/'.$this->_type
.'/'.$USER->id
.'/'.$this->_iid
;
163 if (!file_exists($filename)) {
166 $fp = fopen($filename, "r");
169 if ($line === false) {
172 $this->_columns
= unserialize($line);
173 return $this->_columns
;
181 * @return bool Success
186 if (!empty($this->_fp
)) {
189 $filename = $CFG->tempdir
.'/csvimport/'.$this->_type
.'/'.$USER->id
.'/'.$this->_iid
;
190 if (!file_exists($filename)) {
193 if (!$this->_fp
= fopen($filename, "r")) {
197 return (fgets($this->_fp
) !== false);
203 * @return mixed false, or an array of values
206 if (empty($this->_fp
) or feof($this->_fp
)) {
209 if ($ser = fgets($this->_fp
)) {
210 return unserialize($ser);
217 * Release iteration related resources
222 if (!empty($this->_fp
)) {
231 * @return string error text of null if none
233 function get_error() {
234 return $this->_error
;
238 * Cleanup temporary data
242 * @param boolean $full true means do a full cleanup - all sessions for current user, false only the active iid
244 function cleanup($full=false) {
248 @remove_dir
($CFG->tempdir
.'/csvimport/'.$this->_type
.'/'.$USER->id
);
250 @unlink
($CFG->tempdir
.'/csvimport/'.$this->_type
.'/'.$USER->id
.'/'.$this->_iid
);
255 * Get list of cvs delimiters
257 * @return array suitable for selection box
259 static function get_delimiter_list() {
261 $delimiters = array('comma'=>',', 'semicolon'=>';', 'colon'=>':', 'tab'=>'\\t');
262 if (isset($CFG->CSV_DELIMITER
) and strlen($CFG->CSV_DELIMITER
) === 1 and !in_array($CFG->CSV_DELIMITER
, $delimiters)) {
263 $delimiters['cfg'] = $CFG->CSV_DELIMITER
;
269 * Get delimiter character
271 * @param string separator name
272 * @return string delimiter char
274 static function get_delimiter($delimiter_name) {
276 switch ($delimiter_name) {
277 case 'colon': return ':';
278 case 'semicolon': return ';';
279 case 'tab': return "\t";
280 case 'cfg': if (isset($CFG->CSV_DELIMITER
)) { return $CFG->CSV_DELIMITER
; } // no break; fall back to comma
281 case 'comma': return ',';
286 * Get encoded delimiter character
289 * @param string separator name
290 * @return string encoded delimiter char
292 static function get_encoded_delimiter($delimiter_name) {
294 if ($delimiter_name == 'cfg' and isset($CFG->CSV_ENCODE
)) {
295 return $CFG->CSV_ENCODE
;
297 $delimiter = csv_import_reader
::get_delimiter($delimiter_name);
298 return '&#'.ord($delimiter);
302 * Create new import id
305 * @param string who imports?
308 static function get_new_iid($type) {
311 $filename = make_temp_directory('csvimport/'.$type.'/'.$USER->id
);
313 // use current (non-conflicting) time stamp
315 while (file_exists($filename.'/'.$iiid)) {