diff --git a/admin/user/user_bulk_download.php b/admin/user/user_bulk_download.php
index c60c663cc07..21ceba0a773 100644
--- a/admin/user/user_bulk_download.php
+++ b/admin/user/user_bulk_download.php
@@ -148,23 +148,13 @@ function user_download_csv($fields) {
global $CFG, $SESSION, $DB;
require_once($CFG->dirroot.'/user/profile/lib.php');
+ require_once($CFG->libdir . '/csvlib.class.php');
- $filename = clean_filename(get_string('users').'.csv');
+ $filename = clean_filename(get_string('users'));
- header("Content-Type: application/download\n");
- header("Content-Disposition: attachment; filename=\"$filename\"");
- header("Expires: 0");
- header("Cache-Control: must-revalidate,post-check=0,pre-check=0");
- header("Pragma: public");
-
- $delimiter = get_string('listsep', 'langconfig');
- $encdelim = ''.ord($delimiter);
-
- $row = array();
- foreach ($fields as $fieldname) {
- $row[] = str_replace($delimiter, $encdelim, $fieldname);
- }
- echo implode($delimiter, $row)."\n";
+ $csvexport = new csv_export_writer();
+ $csvexport->set_filename($filename);
+ $csvexport->add_data($fields);
foreach ($SESSION->bulk_users as $userid) {
$row = array();
@@ -172,10 +162,19 @@ function user_download_csv($fields) {
continue;
}
profile_load_data($user);
+ $userprofiledata = array();
foreach ($fields as $field=>$unused) {
- $row[] = str_replace($delimiter, $encdelim, $user->$field);
+ // Custom user profile textarea fields come in an array
+ // The first element is the text and the second is the format.
+ // We only take the text.
+ if (is_array($user->$field)) {
+ $userprofiledata[] = reset($user->$field);
+ } else {
+ $userprofiledata[] = $user->$field;
+ }
}
- echo implode($delimiter, $row)."\n";
+ $csvexport->add_data($userprofiledata);
}
+ $csvexport->download_file();
die;
}
diff --git a/grade/export/txt/grade_export_txt.php b/grade/export/txt/grade_export_txt.php
index 2f654d673cf..0f76af607bd 100644
--- a/grade/export/txt/grade_export_txt.php
+++ b/grade/export/txt/grade_export_txt.php
@@ -16,6 +16,7 @@
// along with Moodle. If not, see .
require_once($CFG->dirroot.'/grade/export/lib.php');
+require_once($CFG->libdir . '/csvlib.class.php');
class grade_export_txt extends grade_export {
@@ -54,46 +55,25 @@ class grade_export_txt extends grade_export {
$strgrades = get_string('grades');
$profilefields = grade_helper::get_user_profile_fields($this->course->id, $this->usercustomfields);
- switch ($this->separator) {
- case 'comma':
- $separator = ",";
- break;
- case 'tab':
- default:
- $separator = "\t";
- }
-
- // Print header to force download
- if (strpos($CFG->wwwroot, 'https://') === 0) { //https sites - watch out for IE! KB812935 and KB316431
- @header('Cache-Control: max-age=10');
- @header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');
- @header('Pragma: ');
- } else { //normal http - prevent caching at all cost
- @header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0');
- @header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');
- @header('Pragma: no-cache');
- }
- header("Content-Type: application/download\n");
$shortname = format_string($this->course->shortname, true, array('context' => context_course::instance($this->course->id)));
$downloadfilename = clean_filename("$shortname $strgrades");
- header("Content-Disposition: attachment; filename=\"$downloadfilename.txt\"");
+ $csvexport = new csv_export_writer($this->separator);
+ $csvexport->set_filename($downloadfilename);
// Print names of all the fields
- $fieldfullnames = array();
+ $exporttitle = array();
foreach ($profilefields as $field) {
- $fieldfullnames[] = $field->fullname;
+ $exporttitle[] = $field->fullname;
}
- echo implode($separator, $fieldfullnames);
+ // Add a feedback column.
foreach ($this->columns as $grade_item) {
- echo $separator.$this->format_column_name($grade_item);
-
- // Add a feedback column.
+ $exporttitle[] = $this->format_column_name($grade_item);
if ($this->export_feedback) {
- echo $separator.$this->format_column_name($grade_item, true);
+ $exporttitle[] = $this->format_column_name($grade_item, true);
}
}
- echo "\n";
+ $csvexport->add_data($exporttitle);
// Print all the lines of data.
$geub = new grade_export_update_buffer();
@@ -103,31 +83,29 @@ class grade_export_txt extends grade_export {
$gui->init();
while ($userdata = $gui->next_user()) {
+ $exportdata = array();
$user = $userdata->user;
- $items = array();
foreach ($profilefields as $field) {
$fieldvalue = grade_helper::get_user_field_value($user, $field);
- $items[] = $fieldvalue;
+ $exportdata[] = $fieldvalue;
}
- echo implode($separator, $items);
-
foreach ($userdata->grades as $itemid => $grade) {
if ($export_tracking) {
$status = $geub->track($grade);
}
- echo $separator.$this->format_grade($grade);
+ $exportdata[] = $this->format_grade($grade);
if ($this->export_feedback) {
- echo $separator.$this->format_feedback($userdata->feedbacks[$itemid]);
+ $exportdata[] = $this->format_feedback($userdata->feedbacks[$itemid]);
}
}
- echo "\n";
+ $csvexport->add_data($exportdata);
}
$gui->close();
$geub->close();
-
+ $csvexport->download_file();
exit;
}
}
diff --git a/lib/csvlib.class.php b/lib/csvlib.class.php
index 92296fd481a..421058a4128 100644
--- a/lib/csvlib.class.php
+++ b/lib/csvlib.class.php
@@ -279,6 +279,7 @@ class csv_import_reader {
case 'tab': return "\t";
case 'cfg': if (isset($CFG->CSV_DELIMITER)) { return $CFG->CSV_DELIMITER; } // no break; fall back to comma
case 'comma': return ',';
+ default : return ','; // If anything else comes in, default to comma.
}
}
@@ -319,3 +320,193 @@ class csv_import_reader {
return $iiid;
}
}
+
+/**
+ * Utitily class for exporting of CSV files.
+ * @copyright 2012 Adrian Greeve
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @package core
+ * @category csv
+ */
+class csv_export_writer {
+ /**
+ * @var string $delimiter The name of the delimiter. Supported types(comma, tab, semicolon, colon, cfg)
+ */
+ var $delimiter;
+ /**
+ * @var string $csvenclosure How fields with spaces and commas are enclosed.
+ */
+ var $csvenclosure;
+ /**
+ * @var string $mimetype Mimetype of the file we are exporting.
+ */
+ var $mimetype;
+ /**
+ * @var string $filename The filename for the csv file to be downloaded.
+ */
+ var $filename;
+ /**
+ * @var string $path The directory path for storing the temporary csv file.
+ */
+ var $path;
+ /**
+ * @var resource $fp File pointer for the csv file.
+ */
+ protected $fp;
+
+ /**
+ * Constructor for the csv export reader
+ *
+ * @param string $delimiter The name of the character used to seperate fields. Supported types(comma, tab, semicolon, colon, cfg)
+ * @param string $enclosure The character used for determining the enclosures.
+ * @param string $mimetype Mime type of the file that we are exporting.
+ */
+ public function __construct($delimiter = 'comma', $enclosure = '"', $mimetype = 'application/download') {
+ $this->delimiter = $delimiter;
+ // Check that the enclosure is a single character.
+ if (strlen($enclosure) == 1) {
+ $this->csvenclosure = $enclosure;
+ } else {
+ $this->csvenclosure = '"';
+ }
+ $this->filename = "Moodle-data-export.csv";
+ $this->mimetype = $mimetype;
+ }
+
+ /**
+ * Set the file path to the temporary file.
+ */
+ protected function set_temp_file_path() {
+ global $USER, $CFG;
+ make_temp_directory('csvimport/' . $USER->id);
+ $path = $CFG->tempdir . '/csvimport/' . $USER->id. '/' . $this->filename;
+ // Check to see if the file exists, if so delete it.
+ if (file_exists($path)) {
+ unlink($path);
+ }
+ $this->path = $path;
+ }
+
+ /**
+ * Add data to the temporary file in csv format
+ *
+ * @param array $row An array of values.
+ */
+ public function add_data($row) {
+ if(!isset($this->path)) {
+ $this->set_temp_file_path();
+ $this->fp = fopen($this->path, 'w+');
+ }
+ $delimiter = csv_import_reader::get_delimiter($this->delimiter);
+ fputcsv($this->fp, $row, $delimiter, $this->csvenclosure);
+ }
+
+ /**
+ * Echos or returns a csv data line by line for displaying.
+ *
+ * @param bool $return Set to true to return a string with the csv data.
+ * @return string csv data.
+ */
+ public function print_csv_data($return = false) {
+ fseek($this->fp, 0);
+ $returnstring = '';
+ while (($content = fgets($this->fp)) !== false) {
+ if (!$return){
+ echo $content;
+ } else {
+ $returnstring .= $content;
+ }
+ }
+ if ($return) {
+ return $returnstring;
+ }
+ }
+
+ /**
+ * Set the filename for the uploaded csv file
+ *
+ * @param string $dataname The name of the module.
+ * @param string $extenstion File extension for the file.
+ */
+ public function set_filename($dataname, $extension = '.csv') {
+ $filename = clean_filename($dataname);
+ $filename .= clean_filename('-' . gmdate("Ymd_Hi"));
+ $filename .= clean_filename("-{$this->delimiter}_separated");
+ $filename .= $extension;
+ $this->filename = $filename;
+ }
+
+ /**
+ * Output file headers to initialise the download of the file.
+ */
+ protected function send_header() {
+ global $CFG;
+ if (strpos($CFG->wwwroot, 'https://') === 0) { //https sites - watch out for IE! KB812935 and KB316431
+ header('Cache-Control: max-age=10');
+ header('Pragma: ');
+ } else { //normal http - prevent caching at all cost
+ header('Cache-Control: private, must-revalidate, pre-check=0, post-check=0, max-age=0');
+ header('Pragma: no-cache');
+ }
+ header('Expires: '. gmdate('D, d M Y H:i:s', 0) .' GMT');
+ header("Content-Type: $this->mimetype\n");
+ header("Content-Disposition: attachment; filename=\"$this->filename\"");
+ }
+
+ /**
+ * Download the csv file.
+ */
+ public function download_file() {
+ $this->send_header();
+ $this->print_csv_data();
+ exit;
+ }
+
+ /**
+ * Creates a file for downloading an array into a deliminated format.
+ * This function is useful if you are happy with the defaults and all of your
+ * information is in one array.
+ *
+ * @param string $filename The filename of the file being created.
+ * @param array $records An array of information to be converted.
+ * @param string $delimiter The name of the delimiter. Supported types(comma, tab, semicolon, colon, cfg)
+ * @param string $enclosure How speical fields are enclosed.
+ */
+ public static function download_array($filename, array &$records, $delimiter = 'comma', $enclosure='"') {
+ $csvdata = new csv_export_writer($delimiter, $enclosure);
+ $csvdata->set_filename($filename);
+ foreach ($records as $row) {
+ $csvdata->add_data($row);
+ }
+ $csvdata->download_file();
+ }
+
+ /**
+ * This will convert an array of values into a deliminated string.
+ * Like the above function, this is for convenience.
+ *
+ * @param array $records An array of information to be converted.
+ * @param string $delimiter The name of the delimiter. Supported types(comma, tab, semicolon, colon, cfg)
+ * @param string $enclosure How speical fields are enclosed.
+ * @param bool $return If true will return a string with the csv data.
+ * @return string csv data.
+ */
+ public static function print_array(array &$records, $delimiter = 'comma', $enclosure = '"', $return = false) {
+ $csvdata = new csv_export_writer($delimiter, $enclosure);
+ foreach ($records as $row) {
+ $csvdata->add_data($row);
+ }
+ $data = $csvdata->print_csv_data($return);
+ if ($return) {
+ return $data;
+ }
+ }
+
+ /**
+ * Make sure that everything is closed when we are finished.
+ */
+ public function __destruct() {
+ fclose($this->fp);
+ unlink($this->path);
+ }
+}
diff --git a/lib/tablelib.php b/lib/tablelib.php
index b8105642cfd..1f14082921a 100644
--- a/lib/tablelib.php
+++ b/lib/tablelib.php
@@ -1557,18 +1557,19 @@ class table_ods_export_format extends table_spreadsheet_export_format_parent {
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class table_text_export_format_parent extends table_default_export_format_parent {
- protected $seperator = "\t";
+ protected $seperator = "tab";
protected $mimetype = 'text/tab-separated-values';
protected $ext = '.txt';
+ protected $myexporter;
+
+ public function __construct() {
+ $this->myexporter = new csv_export_writer($this->seperator, '"', $this->mimetype);
+ }
public function start_document($filename) {
- $this->filename = $filename . $this->ext;
- header('Content-Type: ' . $this->mimetype . '; charset=UTF-8');
- header('Content-Disposition: attachment; filename="' . $this->filename . '"');
- header('Expires: 0');
- header('Cache-Control: must-revalidate,post-check=0,pre-check=0');
- header('Pragma: public');
+ $this->filename = $filename;
$this->documentstarted = true;
+ $this->myexporter->set_filename($filename, $this->ext);
}
public function start_table($sheettitle) {
@@ -1576,19 +1577,20 @@ class table_text_export_format_parent extends table_default_export_format_parent
}
public function output_headers($headers) {
- echo $this->format_row($headers);
+ $this->myexporter->add_data($headers);
}
public function add_data($row) {
- echo $this->format_row($row);
+ $this->myexporter->add_data($row);
return true;
}
public function finish_table() {
- echo "\n\n";
+ //nothing to do here
}
public function finish_document() {
+ $this->myexporter->download_file();
exit;
}
@@ -1612,24 +1614,23 @@ class table_text_export_format_parent extends table_default_export_format_parent
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class table_tsv_export_format extends table_text_export_format_parent {
- protected $seperator = "\t";
+ protected $seperator = "tab";
protected $mimetype = 'text/tab-separated-values';
protected $ext = '.txt';
}
-
+require_once($CFG->libdir . '/csvlib.class.php');
/**
* @package moodlecore
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class table_csv_export_format extends table_text_export_format_parent {
- protected $seperator = ",";
+ protected $seperator = "comma";
protected $mimetype = 'text/csv';
protected $ext = '.csv';
}
-
/**
* @package moodlecore
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
diff --git a/lib/tests/csvclass_test.php b/lib/tests/csvclass_test.php
new file mode 100644
index 00000000000..ff932279aa3
--- /dev/null
+++ b/lib/tests/csvclass_test.php
@@ -0,0 +1,75 @@
+.
+
+/**
+ * Tests csv import and export functions
+ *
+ * @package core
+ * @category phpunit
+ * @copyright 2012 Adrian Greeve
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->dirroot . '/lib/csvlib.class.php');
+
+class csvclass_testcase extends advanced_testcase {
+
+ var $testdata = array();
+ var $teststring = '';
+
+ protected function setUp(){
+
+ $this->resetAfterTest(true);
+
+ $csvdata = array();
+ $csvdata[0][] = 'fullname';
+ $csvdata[0][] = 'description of things';
+ $csvdata[0][] = 'beer';
+ $csvdata[1][] = 'William B Stacey';
+ $csvdata[1][] = '
A field that contains "double quotes"
';
+ $csvdata[1][] = 'Asahi';
+ $csvdata[2][] = 'Phillip Jenkins';
+ $csvdata[2][] = 'This field has
+Multiple lines
+and also contains "double quotes"
';
+ $csvdata[2][] = 'Yebisu';
+ $this->testdata = $csvdata;
+
+ // Please note that each line needs a carriage return.
+ $this->teststring = 'fullname,"description of things",beer
+"William B Stacey","A field that contains ""double quotes""
",Asahi
+"Phillip Jenkins","This field has
+Multiple lines
+and also contains ""double quotes""
",Yebisu
+';
+ }
+
+ public function test_csv_functions() {
+ $csvexport = new csv_export_writer();
+ $csvexport->set_filename('unittest');
+ foreach ($this->testdata as $data) {
+ $csvexport->add_data($data);
+ }
+ $csvoutput = $csvexport->print_csv_data(true);
+ $this->assertEquals($csvoutput, $this->teststring);
+
+ $test_data = csv_export_writer::print_array($this->testdata, 'comma', '"', true);
+ $this->assertEquals($test_data, $this->teststring);
+ }
+}
diff --git a/mod/data/lib.php b/mod/data/lib.php
index 3b3b2a7f5b5..df5fe42b326 100644
--- a/mod/data/lib.php
+++ b/mod/data/lib.php
@@ -2646,37 +2646,19 @@ function data_supports($feature) {
* @param bool $return
* @return string|void
*/
-function data_export_csv($export, $delimiter_name, $dataname, $count, $return=false) {
+function data_export_csv($export, $delimiter_name, $database, $count, $return=false) {
global $CFG;
require_once($CFG->libdir . '/csvlib.class.php');
- $delimiter = csv_import_reader::get_delimiter($delimiter_name);
- $filename = clean_filename("{$dataname}-{$count}_record");
+
+ $filename = $database . '-' . $count . '-record';
if ($count > 1) {
$filename .= 's';
}
- $filename .= clean_filename('-' . gmdate("Ymd_Hi"));
- $filename .= clean_filename("-{$delimiter_name}_separated");
- $filename .= '.csv';
- if (empty($return)) {
- header("Content-Type: application/download\n");
- header("Content-Disposition: attachment; filename=\"$filename\"");
- header('Expires: 0');
- header('Cache-Control: must-revalidate,post-check=0,pre-check=0');
- header('Pragma: public');
+ if ($return) {
+ return csv_export_writer::print_array($export, $delimiter_name, '"', true);
+ } else {
+ csv_export_writer::download_array($filename, $export, $delimiter_name);
}
- $encdelim = '' . ord($delimiter) . ';';
- $returnstr = '';
- foreach($export as $row) {
- foreach($row as $key => $column) {
- $row[$key] = str_replace($delimiter, $encdelim, $column);
- }
- $returnstr .= implode($delimiter, $row) . "\n";
- }
- if (empty($return)) {
- echo $returnstr;
- return;
- }
- return $returnstr;
}
/**