From ee285a20be7ad4b55c13d52a53b6352e74cf315e Mon Sep 17 00:00:00 2001 From: scyrma Date: Tue, 5 Feb 2008 08:42:34 +0000 Subject: [PATCH] Merge from 19_STABLE for MDL-10639. --- grade/edit/outcome/export.php | 106 ++++++++++++++ grade/edit/outcome/import.php | 250 ++++++++++++++++++++++++++++++++++ grade/edit/outcome/index.php | 64 +++++++-- lang/en_utf8/grades.php | 10 ++ 4 files changed, 421 insertions(+), 9 deletions(-) create mode 100644 grade/edit/outcome/export.php create mode 100644 grade/edit/outcome/import.php diff --git a/grade/edit/outcome/export.php b/grade/edit/outcome/export.php new file mode 100644 index 0000000000..5c7aff8d67 --- /dev/null +++ b/grade/edit/outcome/export.php @@ -0,0 +1,106 @@ +dirroot.'/grade/lib.php'; +require_once $CFG->libdir.'/gradelib.php'; + +$courseid = optional_param('id', 0, PARAM_INT); +$action = optional_param('action', '', PARAM_ALPHA); +$export = required_param('export', PARAM_INT); + +/// Make sure they can even access this course +if ($courseid) { + if (!$course = get_record('course', 'id', $courseid)) { + print_error('nocourseid'); + } + require_login($course); + $context = get_context_instance(CONTEXT_COURSE, $course->id); + require_capability('moodle/grade:manage', $context); + + if (empty($CFG->enableoutcomes)) { + redirect('../../index.php?id='.$courseid); + } + +} else { + require_once $CFG->libdir.'/adminlib.php'; + admin_externalpage_setup('outcomes'); +} + +if (!confirm_sesskey()) { + break; +} +// $outcome = grade_outcome::fetch(array('id'=>$outcomeid)); + +$systemcontext = get_context_instance(CONTEXT_SYSTEM); + +header("Content-Type: text/csv; charset=utf-8"); +// TODO: make the filename more useful, include a date, a specific name, something... +header('Content-Disposition: attachment; filename=outcomes.csv'); + +// sending header with clear names, to make 'what is what' as easy as possible to understand +$header = array('outcome_name', 'outcome_shortname', 'outcome_description', 'scale_name', 'scale_items', 'scale_description'); +echo format_csv($header, ';', '"'); + +foreach($export as $outcome_id) { + + $outcome = grade_outcome::fetch(array('id' => $outcome_id)); + + $line = array(); + + $line[] = $outcome->get_name(); + $line[] = $outcome->get_shortname(); + $line[] = $outcome->description; + + $scale = $outcome->load_scale(); + $line[] = $scale->get_name(); + $line[] = $scale->compact_items(); + $line[] = $scale->description; + + echo format_csv($line, ';', '"'); +} + +/** + * Formats and returns a line of data, in CSV format. This code + * is from http://au2.php.net/manual/en/function.fputcsv.php#77866 + * + * @params array-of-string $fields data to be exported + * @params char $delimiter char to be used to separate fields + * @params char $enclosure char used to enclose strings that contains newlines, spaces, tabs or the delimiter char itself + * @returns string one line of csv data + */ +function format_csv($fields = array(), $delimiter = ';', $enclosure = '"') { + $str = ''; + $escape_char = '\\'; + foreach ($fields as $value) { + if (strpos($value, $delimiter) !== false || + strpos($value, $enclosure) !== false || + strpos($value, "\n") !== false || + strpos($value, "\r") !== false || + strpos($value, "\t") !== false || + strpos($value, ' ') !== false) { + $str2 = $enclosure; + $escaped = 0; + $len = strlen($value); + for ($i=0;$i<$len;$i++) { + if ($value[$i] == $escape_char) { + $escaped = 1; + } else if (!$escaped && $value[$i] == $enclosure) { + $str2 .= $enclosure; + } else { + $escaped = 0; + } + $str2 .= $value[$i]; + } + $str2 .= $enclosure; + $str .= $str2.$delimiter; + } else { + $str .= $value.$delimiter; + } + } + $str = substr($str,0,-1); + $str .= "\n"; + + return $str; +} + diff --git a/grade/edit/outcome/import.php b/grade/edit/outcome/import.php new file mode 100644 index 0000000000..ff7ebfbad1 --- /dev/null +++ b/grade/edit/outcome/import.php @@ -0,0 +1,250 @@ +dirroot.'/grade/lib.php'; +require_once $CFG->libdir.'/gradelib.php'; + +$courseid = optional_param('id', 0, PARAM_INT); +$action = optional_param('action', '', PARAM_ALPHA); +$scope = optional_param('scope', 'global', PARAM_ALPHA); + +/// Make sure they can even access this course +if ($courseid) { + if (!$course = get_record('course', 'id', $courseid)) { + print_error('nocourseid'); + } + require_login($course); + $context = get_context_instance(CONTEXT_COURSE, $course->id); + + if (empty($CFG->enableoutcomes)) { + redirect('../../index.php?id='.$courseid); + } + +} else { + require_once $CFG->libdir.'/adminlib.php'; + admin_externalpage_setup('outcomes'); + $context = get_context_instance(CONTEXT_SYSTEM); +} + +require_capability('moodle/grade:manageoutcomes', $context); + +$strgrades = get_string('grades'); +$pagename = get_string('outcomes', 'grades'); + +$navigation = grade_build_nav(__FILE__, $pagename, $courseid); + +$strshortname = get_string('shortname'); +$strfullname = get_string('fullname'); +$strscale = get_string('scale'); +$strstandardoutcome = get_string('outcomesstandard', 'grades'); +$strcustomoutcomes = get_string('outcomescustom', 'grades'); +$strdelete = get_string('delete'); +$stredit = get_string('edit'); +$srtcreatenewoutcome = get_string('outcomecreate', 'grades'); +$stritems = get_string('items', 'grades'); +$strcourses = get_string('courses'); +$stredit = get_string('edit'); +$strexport = get_string('export', 'grades'); + +if (!confirm_sesskey()) { + break; +} + +$systemcontext = get_context_instance(CONTEXT_SYSTEM); +$caneditsystemscales = has_capability('moodle/course:managescales', $systemcontext); + +if ($courseid) { + /// Print header + print_header_simple($strgrades.': '.$pagename, ': '.$strgrades, $navigation, '', '', true, '', navmenu($course)); + /// Print the plugin selector at the top + print_grade_plugin_selector($courseid, 'edit', 'outcome'); + + $caneditcoursescales = has_capability('moodle/course:managescales', $context); + + $currenttab = 'outcomes'; + require('tabs.php'); + +} else { + admin_externalpage_print_header(); + $caneditcoursescales = $caneditsystemscales; +} + +if ($_FILES['userfile']['size'] == 0) { + redirect('index.php'. ($courseid ? "?id=$courseid" : ''), get_string('importfilemissing', 'grades')); +} + +/// which scope are we importing the outcomes in? +if (isset($courseid) && ($scope == 'local')) { + // custom scale + $local_scope = true; +} elseif (($scope == 'global') && has_capability('moodle/grade:manage', get_context_instance(CONTEXT_SYSTEM))) { + // global scale + $local_scope = false; +} else { + // shouldn't happen .. user might be trying to access this script without the right permissions. + redirect('index.php', get_string('importerror', 'grades')); +} + +// open the file, start importing data +if ($handle = fopen($_FILES['userfile']['tmp_name'], 'r')) { + $line = 0; // will keep track of current line, to give better error messages. + $file_headers = ''; + + // $csv_data needs to have at least these columns, the value is the default position in the data file. + $headers = array('outcome_name' => 0, 'outcome_shortname' => 1, 'scale_name' => 3, 'scale_items' => 4); + $optional_headers = array('outcome_description'=>2, 'scale_description' => 5); + $imported_headers = array(); // will later be initialized with the values found in the file + + // data should be separated by a ';'. *NOT* by a comma! TODO: version 2.0 + // or whenever we can depend on PHP5, set the second parameter (8192) to 0 (unlimited line length) : the database can store over 128k per line. + while ( $csv_data = fgetcsv($handle, 8192, ';', '"')) { // if the line is over 8k, it won't work... + $line++; + + // be tolerant on input, as fgetcsv returns "an array comprising a single null field" on blank lines + if ($csv_data == array(null)) { + continue; + } + + // on first run, grab and analyse the header + if ($file_headers == '') { + + $file_headers = array_flip($csv_data); // save the header line ... TODO: use the header line to let import work with columns in arbitrary order + + $error = false; + foreach($headers as $key => $value) { + // sanity check #1: make sure the file contains all the mandatory headers + if (!array_key_exists($key, $file_headers)) { + $error = true; + break; + } + } + if ($error) { + print_box(get_string('importoutcomenofile', 'grades', $line)); + break; + } + + foreach(array_merge($headers, $optional_headers) as $header => $position) { + // match given columns to expected columns *into* $headers + $imported_headers[$header] = $file_headers[$header]; + } + + continue; // we don't import headers + } + + // sanity check #2: every line must have the same number of columns as there are + // headers. If not, processing stops. + if ( count($csv_data) != count($file_headers) ) { + print_box(get_string('importoutcomenofile', 'grades', $line)); + //print_box(var_export($csv_data, true) ."
". var_export($header, true)); + break; + } + + // sanity check #3: all required fields must be present on the current line. + foreach ($headers as $header => $position) { + if ($csv_data[$imported_headers[$header]] == '') { + print_box(get_string('importoutcomenofile', 'grades', $line)); + break; + } + } + + //var_dump($csv_data); + //$db->debug = 3498723498237; // .. very large randomly-typed random value + + if ($local_scope) { + $outcome = get_records_select('grade_outcomes', 'shortname = "'. $csv_data[$imported_headers['outcome_shortname']] .'" and courseid = '. $courseid ); + } else { + $outcome = get_records_select('grade_outcomes', 'shortname = "'. $csv_data[$imported_headers['outcome_shortname']] .'" and courseid is null'); + } + //var_export($outcome); + + if ($outcome) { + // already exists, print a message and skip. + print_box(get_string('importskippedoutcome', 'grades', $csv_data[$imported_headers['outcome_shortname']])); + continue; + } + + // new outcome will be added, search for compatible existing scale... + $scale = get_records_select('scale', 'name ="'. $csv_data[$imported_headers['scale_name']] .'" and scale ="'. $csv_data[$imported_headers['scale_items']] .'" and (courseid = '. $courseid .' or courseid = 0)'); + + if ($scale) { + // already exists in the right scope: use it. + $scale_id = key($scale); + } else { + if (!has_capability('moodle/course:managescales', $context)) { + print_box(get_string('importskippednomanagescale', 'grades', $csv_data[$imported_headers['outcome_shortname']])); + continue; + } else { + // scale doesn't exists : create it. + $scale_data = array('name' => $csv_data[$imported_headers['scale_name']], + 'scale' => $csv_data[$imported_headers['scale_items']], + 'description' => $csv_data[$imported_headers['scale_description']], + 'userid' => $USER->id); + + if ($local_scope) { + $scale_data['courseid'] = $courseid; + } else { + $scale_data['courseid'] = 0; // 'global' : scale use '0', outcomes use null + } + $scale = new grade_scale($scale_data); + $scale_id = $scale->insert(); + } + } + + // add outcome + $outcome_data = array('shortname' => $csv_data[$imported_headers['outcome_shortname']], + 'fullname' => $csv_data[$imported_headers['outcome_name']], + 'scaleid' => $scale_id, + 'description' => $csv_data[$imported_headers['outcome_description']], + 'usermodified' => $USER->id); + + if ($local_scope) { + $outcome_data['courseid'] = $courseid; + } else { + $outcome_data['courseid'] = null; // 'global' : scale use '0', outcomes use null + } + $outcome = new grade_outcome($outcome_data); + $outcome_id = $outcome->insert(); + + $outcome_success_strings = new StdClass(); + $outcome_success_strings->name = $outcome_data['fullname']; + $outcome_success_strings->id = $outcome_id; + print_box(get_string('importoutcomesuccess', 'grades', $outcome_success_strings)); + } +} else { + print_box(get_string('importoutcomenofile', 'grades', 0)); +} + +// finish +fclose($handle); + +if ($courseid) { + print_footer($course); +} else { + admin_externalpage_print_footer(); +} + +?> diff --git a/grade/edit/outcome/index.php b/grade/edit/outcome/index.php index 9d554dd776..4c8376d6cf 100644 --- a/grade/edit/outcome/index.php +++ b/grade/edit/outcome/index.php @@ -38,7 +38,7 @@ if ($courseid) { } require_login($course); $context = get_context_instance(CONTEXT_COURSE, $course->id); - require_capability('moodle/grade:manage', $context); + require_capability('moodle/grade:manageoutcomes', $context); if (empty($CFG->enableoutcomes)) { redirect('../../index.php?id='.$courseid); @@ -69,6 +69,7 @@ $srtcreatenewoutcome = get_string('outcomecreate', 'grades'); $stritems = get_string('items', 'grades'); $strcourses = get_string('courses'); $stredit = get_string('edit'); +$strexport = get_string('export', 'grades'); switch ($action) { case 'delete': @@ -115,7 +116,7 @@ if ($courseid) { $caneditcoursescales = $caneditsystemscales; } - +print('
' ."\n"); if ($courseid and $outcomes = grade_outcome::fetch_all_local($courseid)) { @@ -157,19 +158,25 @@ if ($courseid and $outcomes = grade_outcome::fetch_all_local($courseid)) { " src=\"$CFG->pixpath/t/delete.gif\" class=\"iconsmall\" alt=\"$strdelete\" /> "; } $line[] = $buttons; + + $buttons = ''; + $line[] = $buttons; + $data[] = $line; } $table = new object(); - $table->head = array($strfullname, $strshortname, $strscale, $stritems, $stredit); - $table->size = array('30%', '20%', '20%', '20%', '10%'); - $table->align = array('left', 'left', 'left', 'center', 'center'); + $table->head = array($strfullname, $strshortname, $strscale, $stritems, $stredit, $strexport); + $table->size = array('30%', '18%', '18%', '18%', '8%', '8%' ); + $table->align = array('left', 'left', 'left', 'center', 'center', 'center'); $table->width = '90%'; $table->data = $data; print_table($table); } + if ($outcomes = grade_outcome::fetch_all_global()) { - print_heading($strstandardoutcome); + + print_heading($strstandardoutcome); $data = array(); foreach($outcomes as $outcome) { $line = array(); @@ -210,12 +217,16 @@ if ($outcomes = grade_outcome::fetch_all_global()) { " src=\"$CFG->pixpath/t/delete.gif\" class=\"iconsmall\" alt=\"$strdelete\" /> "; } $line[] = $buttons; + + $buttons = ''; + $line[] = $buttons; + $data[] = $line; } $table = new object(); - $table->head = array($strfullname, $strshortname, $strscale, $strcourses, $stritems, $stredit); - $table->size = array('30%', '20%', '20%', '10%', '10%', '10%'); - $table->align = array('left', 'left', 'left', 'center', 'center', 'center'); + $table->head = array($strfullname, $strshortname, $strscale, $strcourses, $stritems, $stredit, $strexport); + $table->size = array('30%', '19%', '19%', '8%', '8%', '8%', '8%'); + $table->align = array('left', 'left', 'left', 'center', 'center', 'center', 'center'); $table->width = '90%'; $table->data = $data; print_table($table); @@ -223,9 +234,44 @@ if ($outcomes = grade_outcome::fetch_all_global()) { echo '
'; +echo "sesskey\" />"; +print(''); print_single_button('edit.php', array('courseid'=>$courseid), $srtcreatenewoutcome); echo '
'; +echo '
'; +$upload_max_filesize = get_max_upload_file_size($CFG->maxbytes); +$filesize = display_size($upload_max_filesize); + +$strimportoutcomes = get_string('importoutcomes', 'grades'); +$struploadthisfile = get_string('uploadthisfile'); +$strimportcustom = get_string('importcustom', 'grades'); +$strimportstandard = get_string('importstandard', 'grades'); +$strmaxsize = get_string("maxsize", "", $filesize); + +require_once($CFG->dirroot.'/lib/uploadlib.php'); + +echo '
'; +echo '
'; +echo ''; +echo ''; +echo ''; +echo ''; +if ($courseid && has_capability('moodle/grade:manage', get_context_instance(CONTEXT_SYSTEM))) { + echo ''; +} +echo ''; +echo ''; +echo '
    '; + echo '
  • '; + echo '
  • '; + echo '

'. $strimportoutcomes .'('. $strmaxsize .')

'. + upload_print_form_fragment(1,array('userfile'),null,false,null,$upload_max_filesize,0,true) . + '
'; +echo '
'; +echo ''; +echo '
'; + if ($courseid) { print_footer($course); } else { diff --git a/lang/en_utf8/grades.php b/lang/en_utf8/grades.php index 2ebea12b4a..e920164c05 100644 --- a/lang/en_utf8/grades.php +++ b/lang/en_utf8/grades.php @@ -158,6 +158,7 @@ $string['expand'] = 'Expand Category'; $string['export'] = 'Export'; $string['exportfeedback'] = 'Include feedback in export'; $string['exportplugins'] = 'Export plugins'; +$string['exportselectedoutcomes'] = 'Export selected outcomes'; $string['extracredit'] = 'Extra Credit'; $string['extracreditwarning'] = 'Note: Setting all items for a category to extra credit will effectively remove them from the grade calculation. Since there will be no point total'; $string['feedback'] = 'Feedback'; @@ -253,11 +254,20 @@ $string['idnumbers'] = 'Id numbers'; $string['identifier'] = 'Identify user by'; $string['import'] = 'Import'; $string['importcsv'] = 'Import CSV'; +$string['importcustom'] = 'Import as custom outcomes (only this course)'; +$string['importerror'] = 'An error occured, this script wasn\'t called with the right parameters.'; $string['importfailed'] = 'Import failed'; $string['importfeedback'] = 'Import feedback'; $string['importfile'] = 'Import file'; +$string['importfilemissing'] = 'No file was received, go back to the form and make sure to upload a valid file.'; +$string['importoutcomenofile'] = 'The uploaded file is empty or corrupted. Please verify this is a valid file. The problem was detected at line $a; this is triggered by the data lines not having as many columns as the first line (the header line) or if the imported file is missing expected headers. Look at the exported file for an example of a file with valid header.'; +$string['importoutcomes'] = 'Import outcomes'; +$string['importoutcomesuccess'] = 'Imported outcome \"$a->name\" with ID #$a->id'; $string['importplugins'] = 'Import plugins'; $string['importpreview'] = 'Import preview'; +$string['importstandard'] = 'Import as standard outcomes'; +$string['importskippednomanagescale'] = 'You don\'t have permission to add a new scale, so outcome "$a" was skipped as it required creating a new scale'; +$string['importskippedoutcome'] = 'An outcome with shortname \"$a\" already exists in this context, the one in the imported file was skipped.'; $string['importsuccess'] = 'Grade import success'; $string['importxml'] = 'Import XML'; $string['incorrectcourseid'] = 'Course ID was incorrect'; -- 2.39.5