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 * @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='"') {
88 $content = core_text
::convert($content, $encoding, 'utf-8');
89 // remove Unicode BOM from first line
90 $content = core_text
::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));
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 // do not try using fgetcsv() because there is nothing
106 // to split rows properly - fgetcsv() itself can not do it.
107 $tempfile = tempnam(make_temp_directory('/cvsimport'), 'tmp');
108 if (!$fp = fopen($tempfile, 'w+b')) {
109 $this->_error
= get_string('cannotsavedata', 'error');
113 fwrite($fp, $content);
115 // Create an array to store the imported data for error checking.
117 // str_getcsv doesn't iterate through the csv data properly. It has
118 // problems with line returns.
119 while ($fgetdata = fgetcsv($fp, 0, $csv_delimiter, $enclosure)) {
120 // Check to see if we have an empty line.
121 if (count($fgetdata) == 1) {
122 if ($fgetdata[0] !== null) {
123 // The element has data. Add it to the array.
124 $columns[] = $fgetdata;
127 $columns[] = $fgetdata;
132 // process header - list of columns
133 if (!isset($columns[0])) {
134 $this->_error
= get_string('csvemptyfile', 'error');
139 $col_count = count($columns[0]);
142 // Column validation.
143 if ($column_validation) {
144 $result = $column_validation($columns[0]);
145 if ($result !== true) {
146 $this->_error
= $result;
153 $this->_columns
= $columns[0]; // cached columns
154 // check to make sure that the data columns match up with the headers.
155 foreach ($columns as $rowdata) {
156 if (count($rowdata) !== $col_count) {
157 $this->_error
= get_string('csvweirdcolumns', 'error');
165 $filename = $CFG->tempdir
.'/csvimport/'.$this->_type
.'/'.$USER->id
.'/'.$this->_iid
;
166 $filepointer = fopen($filename, "w");
167 // The information has been stored in csv format, as serialized data has issues
168 // with special characters and line returns.
169 $storedata = csv_export_writer
::print_array($columns, ',', '"', true);
170 fwrite($filepointer, $storedata);
174 fclose($filepointer);
176 $datacount = count($columns);
181 * Returns list of columns
185 function get_columns() {
186 if (isset($this->_columns
)) {
187 return $this->_columns
;
192 $filename = $CFG->tempdir
.'/csvimport/'.$this->_type
.'/'.$USER->id
.'/'.$this->_iid
;
193 if (!file_exists($filename)) {
196 $fp = fopen($filename, "r");
197 $line = fgetcsv($fp);
199 if ($line === false) {
202 $this->_columns
= $line;
203 return $this->_columns
;
211 * @return bool Success
216 if (!empty($this->_fp
)) {
219 $filename = $CFG->tempdir
.'/csvimport/'.$this->_type
.'/'.$USER->id
.'/'.$this->_iid
;
220 if (!file_exists($filename)) {
223 if (!$this->_fp
= fopen($filename, "r")) {
227 return (fgetcsv($this->_fp
) !== false);
233 * @return mixed false, or an array of values
236 if (empty($this->_fp
) or feof($this->_fp
)) {
239 if ($ser = fgetcsv($this->_fp
)) {
247 * Release iteration related resources
252 if (!empty($this->_fp
)) {
261 * @return string error text of null if none
263 function get_error() {
264 return $this->_error
;
268 * Cleanup temporary data
272 * @param boolean $full true means do a full cleanup - all sessions for current user, false only the active iid
274 function cleanup($full=false) {
278 @remove_dir
($CFG->tempdir
.'/csvimport/'.$this->_type
.'/'.$USER->id
);
280 @unlink
($CFG->tempdir
.'/csvimport/'.$this->_type
.'/'.$USER->id
.'/'.$this->_iid
);
285 * Get list of cvs delimiters
287 * @return array suitable for selection box
289 static function get_delimiter_list() {
291 $delimiters = array('comma'=>',', 'semicolon'=>';', 'colon'=>':', 'tab'=>'\\t');
292 if (isset($CFG->CSV_DELIMITER
) and strlen($CFG->CSV_DELIMITER
) === 1 and !in_array($CFG->CSV_DELIMITER
, $delimiters)) {
293 $delimiters['cfg'] = $CFG->CSV_DELIMITER
;
299 * Get delimiter character
301 * @param string separator name
302 * @return string delimiter char
304 static function get_delimiter($delimiter_name) {
306 switch ($delimiter_name) {
307 case 'colon': return ':';
308 case 'semicolon': return ';';
309 case 'tab': return "\t";
310 case 'cfg': if (isset($CFG->CSV_DELIMITER
)) { return $CFG->CSV_DELIMITER
; } // no break; fall back to comma
311 case 'comma': return ',';
312 default : return ','; // If anything else comes in, default to comma.
317 * Get encoded delimiter character
320 * @param string separator name
321 * @return string encoded delimiter char
323 static function get_encoded_delimiter($delimiter_name) {
325 if ($delimiter_name == 'cfg' and isset($CFG->CSV_ENCODE
)) {
326 return $CFG->CSV_ENCODE
;
328 $delimiter = csv_import_reader
::get_delimiter($delimiter_name);
329 return '&#'.ord($delimiter);
333 * Create new import id
336 * @param string who imports?
339 static function get_new_iid($type) {
342 $filename = make_temp_directory('csvimport/'.$type.'/'.$USER->id
);
344 // use current (non-conflicting) time stamp
346 while (file_exists($filename.'/'.$iiid)) {
355 * Utitily class for exporting of CSV files.
356 * @copyright 2012 Adrian Greeve
357 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
361 class csv_export_writer
{
363 * @var string $delimiter The name of the delimiter. Supported types(comma, tab, semicolon, colon, cfg)
367 * @var string $csvenclosure How fields with spaces and commas are enclosed.
371 * @var string $mimetype Mimetype of the file we are exporting.
375 * @var string $filename The filename for the csv file to be downloaded.
379 * @var string $path The directory path for storing the temporary csv file.
383 * @var resource $fp File pointer for the csv file.
388 * Constructor for the csv export reader
390 * @param string $delimiter The name of the character used to seperate fields. Supported types(comma, tab, semicolon, colon, cfg)
391 * @param string $enclosure The character used for determining the enclosures.
392 * @param string $mimetype Mime type of the file that we are exporting.
394 public function __construct($delimiter = 'comma', $enclosure = '"', $mimetype = 'application/download') {
395 $this->delimiter
= $delimiter;
396 // Check that the enclosure is a single character.
397 if (strlen($enclosure) == 1) {
398 $this->csvenclosure
= $enclosure;
400 $this->csvenclosure
= '"';
402 $this->filename
= "Moodle-data-export.csv";
403 $this->mimetype
= $mimetype;
407 * Set the file path to the temporary file.
409 protected function set_temp_file_path() {
411 make_temp_directory('csvimport/' . $USER->id
);
412 $path = $CFG->tempdir
. '/csvimport/' . $USER->id
. '/' . $this->filename
;
413 // Check to see if the file exists, if so delete it.
414 if (file_exists($path)) {
421 * Add data to the temporary file in csv format
423 * @param array $row An array of values.
425 public function add_data($row) {
426 if(!isset($this->path
)) {
427 $this->set_temp_file_path();
428 $this->fp
= fopen($this->path
, 'w+');
430 $delimiter = csv_import_reader
::get_delimiter($this->delimiter
);
431 fputcsv($this->fp
, $row, $delimiter, $this->csvenclosure
);
435 * Echos or returns a csv data line by line for displaying.
437 * @param bool $return Set to true to return a string with the csv data.
438 * @return string csv data.
440 public function print_csv_data($return = false) {
443 while (($content = fgets($this->fp
)) !== false) {
447 $returnstring .= $content;
451 return $returnstring;
456 * Set the filename for the uploaded csv file
458 * @param string $dataname The name of the module.
459 * @param string $extenstion File extension for the file.
461 public function set_filename($dataname, $extension = '.csv') {
462 $filename = clean_filename($dataname);
463 $filename .= clean_filename('-' . gmdate("Ymd_Hi"));
464 $filename .= clean_filename("-{$this->delimiter}_separated");
465 $filename .= $extension;
466 $this->filename
= $filename;
470 * Output file headers to initialise the download of the file.
472 protected function send_header() {
474 if (strpos($CFG->wwwroot
, 'https://') === 0) { //https sites - watch out for IE! KB812935 and KB316431
475 header('Cache-Control: max-age=10');
477 } else { //normal http - prevent caching at all cost
478 header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0');
479 header('Pragma: no-cache');
481 header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');
482 header("Content-Type: $this->mimetype\n");
483 header("Content-Disposition: attachment; filename=\"$this->filename\"");
487 * Download the csv file.
489 public function download_file() {
490 $this->send_header();
491 $this->print_csv_data();
496 * Creates a file for downloading an array into a deliminated format.
497 * This function is useful if you are happy with the defaults and all of your
498 * information is in one array.
500 * @param string $filename The filename of the file being created.
501 * @param array $records An array of information to be converted.
502 * @param string $delimiter The name of the delimiter. Supported types(comma, tab, semicolon, colon, cfg)
503 * @param string $enclosure How speical fields are enclosed.
505 public static function download_array($filename, array &$records, $delimiter = 'comma', $enclosure='"') {
506 $csvdata = new csv_export_writer($delimiter, $enclosure);
507 $csvdata->set_filename($filename);
508 foreach ($records as $row) {
509 $csvdata->add_data($row);
511 $csvdata->download_file();
515 * This will convert an array of values into a deliminated string.
516 * Like the above function, this is for convenience.
518 * @param array $records An array of information to be converted.
519 * @param string $delimiter The name of the delimiter. Supported types(comma, tab, semicolon, colon, cfg)
520 * @param string $enclosure How speical fields are enclosed.
521 * @param bool $return If true will return a string with the csv data.
522 * @return string csv data.
524 public static function print_array(array &$records, $delimiter = 'comma', $enclosure = '"', $return = false) {
525 $csvdata = new csv_export_writer($delimiter, $enclosure);
526 foreach ($records as $row) {
527 $csvdata->add_data($row);
529 $data = $csvdata->print_csv_data($return);
536 * Make sure that everything is closed when we are finished.
538 public function __destruct() {