MDL-36805 Correct docs for workshop_grade_item_update in mod_workshop
[moodle.git] / lib / csvlib.class.php
blob5e2e94b81ba556129a1d100793b55c8bf9d15ccb
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 * @param string $enclosure field wrapper. One character only.
80 * @return bool false if error, count of data lines if ok; use get_error() to get error string
82 function load_csv_content(&$content, $encoding, $delimiter_name, $column_validation=null, $enclosure='"') {
83 global $USER, $CFG;
85 $this->close();
86 $this->_error = null;
88 $content = textlib::convert($content, $encoding, 'utf-8');
89 // remove Unicode BOM from first line
90 $content = textlib::trim_utf8_bom($content);
91 // Fix mac/dos newlines
92 $content = preg_replace('!\r\n?!', "\n", $content);
93 // Remove any spaces or new lines at the end of the file.
94 if ($delimiter_name == 'tab') {
95 // trim() by default removes tabs from the end of content which is undesirable in a tab separated file.
96 $content = trim($content, chr(0x20) . chr(0x0A) . chr(0x0D) . chr(0x00) . chr(0x0B));
97 } else {
98 $content = trim($content);
101 $csv_delimiter = csv_import_reader::get_delimiter($delimiter_name);
102 // $csv_encode = csv_import_reader::get_encoded_delimiter($delimiter_name);
104 // create a temporary file and store the csv file there.
105 $fp = tmpfile();
106 fwrite($fp, $content);
107 fseek($fp, 0);
108 // Create an array to store the imported data for error checking.
109 $columns = array();
110 // str_getcsv doesn't iterate through the csv data properly. It has
111 // problems with line returns.
112 while ($fgetdata = fgetcsv($fp, 0, $csv_delimiter, $enclosure)) {
113 // Check to see if we have an empty line.
114 if (count($fgetdata) == 1) {
115 if ($fgetdata[0] !== null) {
116 // The element has data. Add it to the array.
117 $columns[] = $fgetdata;
119 } else {
120 $columns[] = $fgetdata;
123 $col_count = 0;
125 // process header - list of columns
126 if (!isset($columns[0])) {
127 $this->_error = get_string('csvemptyfile', 'error');
128 fclose($fp);
129 return false;
130 } else {
131 $col_count = count($columns[0]);
134 // Column validation.
135 if ($column_validation) {
136 $result = $column_validation($columns[0]);
137 if ($result !== true) {
138 $this->_error = $result;
139 fclose($fp);
140 return false;
144 $this->_columns = $columns[0]; // cached columns
145 // check to make sure that the data columns match up with the headers.
146 foreach ($columns as $rowdata) {
147 if (count($rowdata) !== $col_count) {
148 $this->_error = get_string('csvweirdcolumns', 'error');
149 fclose($fp);
150 $this->cleanup();
151 return false;
155 $filename = $CFG->tempdir.'/csvimport/'.$this->_type.'/'.$USER->id.'/'.$this->_iid;
156 $filepointer = fopen($filename, "w");
157 // The information has been stored in csv format, as serialized data has issues
158 // with special characters and line returns.
159 $storedata = csv_export_writer::print_array($columns, ',', '"', true);
160 fwrite($filepointer, $storedata);
162 fclose($fp);
163 fclose($filepointer);
165 $datacount = count($columns);
166 return $datacount;
170 * Returns list of columns
172 * @return array
174 function get_columns() {
175 if (isset($this->_columns)) {
176 return $this->_columns;
179 global $USER, $CFG;
181 $filename = $CFG->tempdir.'/csvimport/'.$this->_type.'/'.$USER->id.'/'.$this->_iid;
182 if (!file_exists($filename)) {
183 return false;
185 $fp = fopen($filename, "r");
186 $line = fgetcsv($fp);
187 fclose($fp);
188 if ($line === false) {
189 return false;
191 $this->_columns = $line;
192 return $this->_columns;
196 * Init iterator.
198 * @global object
199 * @global object
200 * @return bool Success
202 function init() {
203 global $CFG, $USER;
205 if (!empty($this->_fp)) {
206 $this->close();
208 $filename = $CFG->tempdir.'/csvimport/'.$this->_type.'/'.$USER->id.'/'.$this->_iid;
209 if (!file_exists($filename)) {
210 return false;
212 if (!$this->_fp = fopen($filename, "r")) {
213 return false;
215 //skip header
216 return (fgetcsv($this->_fp) !== false);
220 * Get next line
222 * @return mixed false, or an array of values
224 function next() {
225 if (empty($this->_fp) or feof($this->_fp)) {
226 return false;
228 if ($ser = fgetcsv($this->_fp)) {
229 return $ser;
230 } else {
231 return false;
236 * Release iteration related resources
238 * @return void
240 function close() {
241 if (!empty($this->_fp)) {
242 fclose($this->_fp);
243 $this->_fp = null;
248 * Get last error
250 * @return string error text of null if none
252 function get_error() {
253 return $this->_error;
257 * Cleanup temporary data
259 * @global object
260 * @global object
261 * @param boolean $full true means do a full cleanup - all sessions for current user, false only the active iid
263 function cleanup($full=false) {
264 global $USER, $CFG;
266 if ($full) {
267 @remove_dir($CFG->tempdir.'/csvimport/'.$this->_type.'/'.$USER->id);
268 } else {
269 @unlink($CFG->tempdir.'/csvimport/'.$this->_type.'/'.$USER->id.'/'.$this->_iid);
274 * Get list of cvs delimiters
276 * @return array suitable for selection box
278 static function get_delimiter_list() {
279 global $CFG;
280 $delimiters = array('comma'=>',', 'semicolon'=>';', 'colon'=>':', 'tab'=>'\\t');
281 if (isset($CFG->CSV_DELIMITER) and strlen($CFG->CSV_DELIMITER) === 1 and !in_array($CFG->CSV_DELIMITER, $delimiters)) {
282 $delimiters['cfg'] = $CFG->CSV_DELIMITER;
284 return $delimiters;
288 * Get delimiter character
290 * @param string separator name
291 * @return string delimiter char
293 static function get_delimiter($delimiter_name) {
294 global $CFG;
295 switch ($delimiter_name) {
296 case 'colon': return ':';
297 case 'semicolon': return ';';
298 case 'tab': return "\t";
299 case 'cfg': if (isset($CFG->CSV_DELIMITER)) { return $CFG->CSV_DELIMITER; } // no break; fall back to comma
300 case 'comma': return ',';
301 default : return ','; // If anything else comes in, default to comma.
306 * Get encoded delimiter character
308 * @global object
309 * @param string separator name
310 * @return string encoded delimiter char
312 static function get_encoded_delimiter($delimiter_name) {
313 global $CFG;
314 if ($delimiter_name == 'cfg' and isset($CFG->CSV_ENCODE)) {
315 return $CFG->CSV_ENCODE;
317 $delimiter = csv_import_reader::get_delimiter($delimiter_name);
318 return '&#'.ord($delimiter);
322 * Create new import id
324 * @global object
325 * @param string who imports?
326 * @return int iid
328 static function get_new_iid($type) {
329 global $USER;
331 $filename = make_temp_directory('csvimport/'.$type.'/'.$USER->id);
333 // use current (non-conflicting) time stamp
334 $iiid = time();
335 while (file_exists($filename.'/'.$iiid)) {
336 $iiid--;
339 return $iiid;
344 * Utitily class for exporting of CSV files.
345 * @copyright 2012 Adrian Greeve
346 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
347 * @package core
348 * @category csv
350 class csv_export_writer {
352 * @var string $delimiter The name of the delimiter. Supported types(comma, tab, semicolon, colon, cfg)
354 var $delimiter;
356 * @var string $csvenclosure How fields with spaces and commas are enclosed.
358 var $csvenclosure;
360 * @var string $mimetype Mimetype of the file we are exporting.
362 var $mimetype;
364 * @var string $filename The filename for the csv file to be downloaded.
366 var $filename;
368 * @var string $path The directory path for storing the temporary csv file.
370 var $path;
372 * @var resource $fp File pointer for the csv file.
374 protected $fp;
377 * Constructor for the csv export reader
379 * @param string $delimiter The name of the character used to seperate fields. Supported types(comma, tab, semicolon, colon, cfg)
380 * @param string $enclosure The character used for determining the enclosures.
381 * @param string $mimetype Mime type of the file that we are exporting.
383 public function __construct($delimiter = 'comma', $enclosure = '"', $mimetype = 'application/download') {
384 $this->delimiter = $delimiter;
385 // Check that the enclosure is a single character.
386 if (strlen($enclosure) == 1) {
387 $this->csvenclosure = $enclosure;
388 } else {
389 $this->csvenclosure = '"';
391 $this->filename = "Moodle-data-export.csv";
392 $this->mimetype = $mimetype;
396 * Set the file path to the temporary file.
398 protected function set_temp_file_path() {
399 global $USER, $CFG;
400 make_temp_directory('csvimport/' . $USER->id);
401 $path = $CFG->tempdir . '/csvimport/' . $USER->id. '/' . $this->filename;
402 // Check to see if the file exists, if so delete it.
403 if (file_exists($path)) {
404 unlink($path);
406 $this->path = $path;
410 * Add data to the temporary file in csv format
412 * @param array $row An array of values.
414 public function add_data($row) {
415 if(!isset($this->path)) {
416 $this->set_temp_file_path();
417 $this->fp = fopen($this->path, 'w+');
419 $delimiter = csv_import_reader::get_delimiter($this->delimiter);
420 fputcsv($this->fp, $row, $delimiter, $this->csvenclosure);
424 * Echos or returns a csv data line by line for displaying.
426 * @param bool $return Set to true to return a string with the csv data.
427 * @return string csv data.
429 public function print_csv_data($return = false) {
430 fseek($this->fp, 0);
431 $returnstring = '';
432 while (($content = fgets($this->fp)) !== false) {
433 if (!$return){
434 echo $content;
435 } else {
436 $returnstring .= $content;
439 if ($return) {
440 return $returnstring;
445 * Set the filename for the uploaded csv file
447 * @param string $dataname The name of the module.
448 * @param string $extenstion File extension for the file.
450 public function set_filename($dataname, $extension = '.csv') {
451 $filename = clean_filename($dataname);
452 $filename .= clean_filename('-' . gmdate("Ymd_Hi"));
453 $filename .= clean_filename("-{$this->delimiter}_separated");
454 $filename .= $extension;
455 $this->filename = $filename;
459 * Output file headers to initialise the download of the file.
461 protected function send_header() {
462 global $CFG;
463 if (strpos($CFG->wwwroot, 'https://') === 0) { //https sites - watch out for IE! KB812935 and KB316431
464 header('Cache-Control: max-age=10');
465 header('Pragma: ');
466 } else { //normal http - prevent caching at all cost
467 header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0');
468 header('Pragma: no-cache');
470 header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');
471 header("Content-Type: $this->mimetype\n");
472 header("Content-Disposition: attachment; filename=\"$this->filename\"");
476 * Download the csv file.
478 public function download_file() {
479 $this->send_header();
480 $this->print_csv_data();
481 exit;
485 * Creates a file for downloading an array into a deliminated format.
486 * This function is useful if you are happy with the defaults and all of your
487 * information is in one array.
489 * @param string $filename The filename of the file being created.
490 * @param array $records An array of information to be converted.
491 * @param string $delimiter The name of the delimiter. Supported types(comma, tab, semicolon, colon, cfg)
492 * @param string $enclosure How speical fields are enclosed.
494 public static function download_array($filename, array &$records, $delimiter = 'comma', $enclosure='"') {
495 $csvdata = new csv_export_writer($delimiter, $enclosure);
496 $csvdata->set_filename($filename);
497 foreach ($records as $row) {
498 $csvdata->add_data($row);
500 $csvdata->download_file();
504 * This will convert an array of values into a deliminated string.
505 * Like the above function, this is for convenience.
507 * @param array $records An array of information to be converted.
508 * @param string $delimiter The name of the delimiter. Supported types(comma, tab, semicolon, colon, cfg)
509 * @param string $enclosure How speical fields are enclosed.
510 * @param bool $return If true will return a string with the csv data.
511 * @return string csv data.
513 public static function print_array(array &$records, $delimiter = 'comma', $enclosure = '"', $return = false) {
514 $csvdata = new csv_export_writer($delimiter, $enclosure);
515 foreach ($records as $row) {
516 $csvdata->add_data($row);
518 $data = $csvdata->print_csv_data($return);
519 if ($return) {
520 return $data;
525 * Make sure that everything is closed when we are finished.
527 public function __destruct() {
528 fclose($this->fp);
529 unlink($this->path);