MDL-34074 - lib - Creation of a csv upload class which is FRC 4180 compliant and the alteration of various files around Moodle to use this class.

This commit is contained in:
Adrian Greeve 2012-07-27 09:37:33 +08:00
parent 55a568fa7d
commit 9a5abd1b70
6 changed files with 319 additions and 93 deletions

View File

@ -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;
}

View File

@ -16,6 +16,7 @@
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
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;
}
}

View File

@ -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);
}
}

View File

@ -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}

View File

@ -0,0 +1,75 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* 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][] = '<p>A field that contains "double quotes"</p>';
$csvdata[1][] = 'Asahi';
$csvdata[2][] = 'Phillip Jenkins';
$csvdata[2][] = '<p>This field has </p>
<p>Multiple lines</p>
<p>and also contains "double quotes"</p>';
$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","<p>A field that contains ""double quotes""</p>",Asahi
"Phillip Jenkins","<p>This field has </p>
<p>Multiple lines</p>
<p>and also contains ""double quotes""</p>",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);
}
}

View File

@ -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;
}
/**