Parent Directory
|
Revision Log
tested final version with screenshots in example_files folder
<?php
/**
* The question type class for the SourceCode JUnit question type.
*
* @copyright © 2008 Süreç Özcan
* @author suerec@darkjade.net
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
* @package so_questiontypes
*/
require_once(dirname(__FILE__) . '/config.php');
/**
* The SourceCode JUnit question class
*
* This question type is to be used in adaptive-mode.
* Therefore the print_question_grading_details-method has been
* overridden so that gradings won't get displayed when using
* "Submit"-button.
*/
class sojunit_qtype extends default_questiontype {
var $operating_system = PHP_OS; //Windows: "WINNT" or Linux: "Linux"
function name() {
return 'sojunit';
}
/**
* @return boolean to indicate success of failure.
*/
function get_question_options(&$question) {
// code to retrieve the extra data you stored in the database into $question->options.
// Get additional information from database and attach it to the question object
if (!$question->options = get_record('question_sojunit', 'questionid', $question->id)) {
notify('Error: Missing question options for junit question '. $question->id. '!');
return false;
}
return true;
}
/**
* Save the units and the answers associated with this question.
* @return boolean to indicate success of failure.
*/
function save_question_options($question) {
// code to save the extra data to your database tables from the
// $question object, which has all the post data from editquestion.html
// I don't need the answer-table -> so I just don't fill it up with inputs
// question_sojunit - table
$update = true;
$options = get_record("question_sojunit", "questionid", $question->id);
if (!$options) {
$update = false;
$options = new stdClass;
$options->questionid = $question->id;
}
// new fields for this questin type as new options
$options->givencode = trim($question->givencode);
$options->testclassname = trim($question->testclassname);
//$options->useeditor_genfeedb = trim($question->useeditor_genfeedb); //Tim Hunt
$options->useeditor_genfeedb = !empty($question->useeditor_genfeedb); //Tim Hunt
$options->sourcecode = $question->sourcecode;
if ($update) {
if (!update_record("question_sojunit", $options)) {
$result->error = "Could not update quiz sojunit-table options! (id=$options->id)";
return $result;
}
} else {
if (!insert_record("question_sojunit", $options)) {
$result->error = "Could not insert quiz sojunit-table options!";
return $result;
}
}
return true;
}
/**
* Deletes question from the question-type specific tables
*
* @param integer $questionid The question being deleted
* @return boolean to indicate success of failure.
*/
function delete_question($questionid) {
delete_records("question_sojunit", "questionid", $questionid);
return true;
}
/**
* unpack $state->responses[''], which has just been loaded from the
* database field question_states.answer into the $state->responses array.
*
* We store the reponses by turning the associative array $state->responses
* into a string as follows. For example, array('f2' => 'No, never - ever', 'f1' => '10')
* becomes 'f1-10,f2-No\, never - ever'. That is, comma separated pairs, sorted by key,
* key and value linked with a '-', commas in vales escaped with '\'.
*/
function restore_session_and_responses(&$question, &$state) {
// Deal with special case: no responses at all.
if (empty($state->responses) || empty($state->responses[''])) {
$state->responses == array();
return true;
}
// Split the responses on non-backslash-escaped commas.
// JUnit: chanded '\,' into '#,' because MySQL handles '\,' special so that commas won't get seperated correctly
//$responses = preg_split('/(?<!\\\\)\,/', $state->responses['']);
$responses = preg_split('/(?<!#),/', $state->responses['']);
// Now set $state->responses properly.
$state->responses = array();
foreach ($responses as $response) {
list($key, $value) = explode('-', $response, 2);
$state->responses[$key] = str_replace('#,', ',', $value);
}
return true;
}
/**
* See commment at the top of restore_session_and_responses for what we are trying to do.
* package up the students response from the $state->responses
* array into a string and save it in the question_states.answer field.
*/
function save_session_and_responses(&$question, &$state) {
$responses = array();
ksort($state->responses);
foreach ($state->responses as $key => $value) {
$responses[] = $key . '-' . str_replace(',', '#,', $value);
}
$responses = implode(',', $responses);
return set_field('question_states', 'answer', $responses, 'id', $state->id);
}
/**
* in order to use syntax highlighted source code, add corresponding code-tags around the $code
*/
function format_code($code, $cmoptions) {
return $this->format_text('[code syntax="java" linenumbers="yes" indentsize="2"]' . $code . '[/code]',
FORMAT_MOODLE, $cmoptions);
}
function print_question_formulation_and_controls(&$question, &$state, $cmoptions, $options) {
global $CFG, $COURSE;
// Print the questiontext
$questiontext = $this->format_text($question->questiontext, $question->questiontextformat, $cmoptions);
$image = get_question_image($question, $cmoptions->course);
// Prepare the students response.
if (!empty($state->responses[''])) {
$studentscode = stripslashes_safe($state->responses['']);
} else {
$studentscode = $question->options->givencode;
}
$inputname = $question->name_prefix;
$course_files_directory_backslashes = make_upload_directory("$COURSE->id"); // path for course files
$course_files_directory = str_replace("\\","/",$course_files_directory_backslashes); //replace \\ with /
$testcode = file_get_contents($course_files_directory . '/' . $question->options->testclassname);
// Question text, image and given code.
echo '<div class="qtext">', $questiontext, '</div>';
if ($image) {
echo '<img class="qimage" src="', $image, '" alt="" />';
}
// Start of students response, feedback, compiler output, execution output etc.
echo '<div class="ablock clearfix" spellcheck="false">';
// Response of student
if (empty($options->readonly)) { //if not readonly - editable
$answer = print_textarea(false, 30, 100, 630, 400, $inputname, $studentscode, $cmoptions->course, true);
echo $answer;
} else { //if readonly - not editable
echo '<div class="studentscode">', $this->format_code($studentscode, $cmoptions) . '</div>';
}
// display compiler output allways
//Note bug: in Moodle 1.8.2+ the compilation output doesn't get displayed after usage of "Submit all and finish"-button?
if (!empty($state->responses['_compileroutput'])) { //display compilation output when compilation error occured!
echo '<div class="feedback">';
echo '<p>', get_string('compileroutput','qtype_sojunit'), '</p>';
if (empty($options->readonly)) { //display the studentresponse nicely syntax highlighted so that they can find their comp/exec - error easier
echo '<div class="studentscode">', $this->format_code($studentscode, $cmoptions) . '</div>';
}
//echo '<pre class="compileroutput">', "some text" . s($state->responses['_compileroutput'], true), '</pre>';
echo '<pre class="compileroutput">', s($state->responses['_compileroutput'], true), '</pre>';
echo '</div>';
} else if((isset($_POST['resp540_submit'])) and (empty($options->readonly))){
//TODO if not readonly and Submit-button pressed -> display compilation success; if readonly -> bug issue
echo '<div class="feedback">';
echo '<pre class="compileroutput">', get_string('compileroutputsuccess','qtype_sojunit'), '</pre>';
echo '</div>';
}
// Display any feedback - that is any compiler output or execution output.
if (($options->feedback) or (!empty($options->readonly))) {
if (!empty($state->responses['_executionoutput'])) {
echo '<div class="feedback">';
echo '<p>', get_string('executionoutput','qtype_sojunit'), '</p>';
echo '<pre class="executionoutput">', s($state->responses['_executionoutput'], true), '</pre>';
echo '</div>';
}
}
$this->print_question_submit_buttons($question, $state, $cmoptions, $options);
// End of <div class="ablock clearfix">
echo '</div>';
}
/**
* Here happens everything, starting from compiling and executing the proper files
* till calculating the grade.
*
* see forum entry: http://moodle.org/mod/forum/discuss.php?d=97086
* $state->responses['compileroutput']=...; // store compileroutput here
* $state->responses['_executionoutput']=...; // store executionoutput here
*
* $state->raw_grade //the final grade of the student should be stored in here
* $state->responses //should hold the response of a student
* $question->maxgrade //max. points for this question
* //should hold the grade number the teacher typed in the formular while creating the new question
*
* @param $question
* @param $state
* @param $cmoptions
* @return unknown
*/
function grade_responses(&$question, &$state, $cmoptions) {
global $CFG;
global $COURSE;
$state->raw_grade = 0;
/* preparation:
* create a new sub-folder in the course-files-path for each user who takes this question type.
* Put the related source codes (Test.java and studentid_response.java) of a user into this sub-folder
*/
//locate the path for the used testclass, and create it.
$pre = 'a' . $state->attempt . '_' . $state->question . '_'; // the pre-string for unique file naming
$cfg_dataroot_backslashes = $CFG->dataroot;
$cfg_dataroot = str_replace("\\", "/", $cfg_dataroot_backslashes);
//$temp_folder = $cfg_dataroot . '/so_junit_temp/' . $state->attempt . '/' . $state->question;
$temp_folder = $cfg_dataroot . '/so_junit_temp'; // $state->attempt . '/' . $state->question;
$this->mkdir_recursive($temp_folder);
//print_object('temp_folder: ' . $temp_folder); //debug
// Save the test code to a .java file.
$testclassname = $question->options->testclassname; //loaded and used testclass by teacher for this question
//print_object('testclassname: ' . $testclassname);
//locate the path for the used testclass which has been uploaded
//keep replacing "\" with "/" which should work on linux and windows for compilation and execution
$course_files_directory_backslashes = make_upload_directory("$COURSE->id"); // path for course files
$course_files_directory = str_replace("\\","/",$course_files_directory_backslashes); //replace \\ with / NEW
$testclass_path = $course_files_directory.'/'.$testclassname;
//print_object('testclass_path: ' . $testclass_path); //debug
//copy the test-file into the temporary-folder directory ($temp_file)
//e.g. "D:/oezcan/workspace/moodleSue/moodledata/temp/sojunit/85/500/FactorialTest.java"
$cfg_dataroot_backslashes = $CFG->dataroot;
$cfg_dataroot = str_replace("\\", "/", $cfg_dataroot_backslashes);
// usually the test-file is directly in the course_files_directory
if ($pos_of_slash = strpos($testclassname, "/")){ // if test-file is in a subfolder of the course path e.g. "suerec/Test.java"
$testclassname_without_pre = substr($testclassname,$pos_of_slash + 1); //needed for compilation and execution so that the calls work properly
$testclassname_without_pre_and_java = substr($testclassname_without_pre, 0, -5);
$testclassname = $pre . substr($testclassname,$pos_of_slash + 1); // only the string after e.g. "suerec"
} else {
$testclassname_without_pre_and_java = substr($testclassname, 0, -5); //needed for compilation and execution so that the calls work properly
$testclassname = $pre . $testclassname;
}
//print_object('testclassname_without_pre: ' . $testclassname_without_pre);
//print_object('testclassname: ' . $testclassname);
$temp_file = $temp_folder.'/'.$testclassname;
//print_object('temp_file: ' . $temp_file);
if (!copy($testclass_path,$temp_file)){
echo "Case1: Failed to copy test file \"$testclass_path\" into a temporary userid-file \"$temp_file\".";
}
//print_object('testclassname: ' . $testclassname); //debug
// Get the student's code.
// Deal with special case: no responses at all.
if (empty($state->responses)) { //Tim Hunt: changed the next 10 lines
$state->responses == array();
}
// Prepare the students response.
if (!empty($state->responses[''])) {
$studentscode = stripslashes($state->responses['']);
} else {
$studentscode = $question->options->givencode;
}
// Try to find the class name they used.
$matches = array();
// ignore text that is stated above the class X e.g. imports, comments
preg_match('/^(?:\s*public)?\s*class\s+(\w[a-zA-Z0-9_]+)/m', $studentscode, $matches);
if (empty( $matches[1])){ //if student deletes accidently the class name return error-message
preg_match('/^(?:\s*public)?\s*class\s+(\w[a-zA-Z0-9_]+)/', $question->options->givencode, $matches);
$studentsclassname = $matches[1];
//filter the class name from givencode
$brace_pos = strpos($question->options->givencode, '{'); //get the pos in the string where 'Time' starts
$class_given = substr($question->options->givencode, 0, $brace_pos);
$state->responses['_compileroutput'] = get_string('error_class', 'qtype_sojunit') . $class_given;
return true;
} else {
$studentsclassname = $matches[1];
}
// Write their code to a file.
file_put_contents($temp_folder . '/' . $pre . $studentsclassname . '.java', $studentscode);
//print_object('studentsclassname: ' . $studentsclassname);
// Compile it.
$studentclass_path = $temp_folder. '/' . $pre . $studentsclassname . '.java';
//print_object('studentclass_path' . $studentclass_path);
list($compiles, $compileroutput) = $this->compile($temp_file, $studentclass_path, $temp_folder, $pre, $testclassname_without_pre_and_java);
//shorten the $compileroutput by the $temp_folder part in order to
// 1. mainly get same compiler-errors in the Results->Item analysis grouped together for a better analysis
// 2. hide the compilation-path from the student (it is not important to do so because of the SecurityManager used, but this would be nice as well)
$compileroutput_without_path = substr_replace($compileroutput, '', 0, strlen($temp_folder)+1);
//remove the pre-string from the class-name so that the student won't get confused why it is named different
$compileroutput_without_pre = str_replace($pre, '', $compileroutput_without_path);
$state->responses['_compileroutput'] = addslashes($compileroutput_without_pre);
if ($compiles) {
// Run the tests.
$testclassname_without_java = substr($testclassname,0,-5);
$executionoutput = $this->execute($testclassname_without_java, $temp_folder, $pre);
//filter the $executionoutput by 'Time 0.000' part in order to get same
//execution-outputs in the Results->Item analysis grouped together for a better analysis
$cleaned_executionoutput = preg_replace("/Time:\s([0-9]+[.][0-9]+)\s/","",$executionoutput);
//remove the pre-string from the class-name so that the student won't get confused why it is named different
$executionoutput_without_pre = str_replace($pre, '', $cleaned_executionoutput);
$state->responses['_executionoutput'] = addslashes($executionoutput_without_pre);
//the JUnit-execution-output returns always a String in the first line e.g. "...F.",
//which means that 1 out of 4 test cases didn't pass the JUnit test
//In the second line it says "Time ..."
// Discard the summary.
$pos = strpos($executionoutput, 'Time') + 1; //get the pos in the string where 'Time' starts
$executionoutputresult = substr($executionoutput, 0, $pos);
// Count the failures and errors.
$numfailures = substr_count($executionoutputresult, '.F');
$numerrors = substr_count($executionoutputresult, '.E');
// Work out the score based on this - we ensure this is not negative below.
$state->raw_grade = $question->maxgrade - $numfailures - $numerrors;
// Apply the penalty for this attempt
$state->penalty = $question->penalty * $question->maxgrade;
} else {
// Doesn't compile - zero grade, but don't penalise the student for this submission.
$state->responses['_executionoutput'] = '';
$state->raw_grade = 0;
$state->penalty = 0;
}
// Make sure we don't assign negative or too high marks. Note: The grading doesn't work if this line is used!?!
//$state->raw_grade = min(max((float) $state->raw_grade,0.0), 1.0) * $question->maxgrade;
// mark the state as graded
$state->event = ($state->event == QUESTION_EVENTCLOSE) ? QUESTION_EVENTCLOSEANDGRADE : QUESTION_EVENTGRADE;
//delete the temporarily created files of a current attempt to its corresponding question (java, log, class)
$this->rfr($temp_folder,"/*.java");
$this->rfr($temp_folder,"/*.log");
$this->rfr($temp_folder,"/*.class");
return true;
}
/**
* This function deletes all files of the $path-folder that match the ending e.g. '.log'
* @param string $path path to the directory in which the files are to be deleted
* @param int $match files with this ending shall be deleted e.g. '.log'
*/
function rfr($path,$match){
static $deld = 0, $dsize = 0;
$files = glob($path.$match);
foreach($files as $file){
if(is_file($file)){
$dsize += filesize($file);
unlink($file);
$deld++;
}
}
return "$deld files deleted with a total size of $dsize bytes";
}
/**
* This function exists because mkdir(folder,mode,TRUE) doesn't work on our server.
* Safe to call even if folder already exists (checks)
* @param string $folder Folder to create
* @param int $mode Mode for creation (default 0755)
* @return boolean True if folder (now) exists, false if there was a failure
*/
function mkdir_recursive($folder, $mode='') {
if(is_dir($folder)) {
return true;
}
if ($mode == '') {
global $CFG;
$mode = $CFG->directorypermissions;
}
if(!$this->mkdir_recursive(dirname($folder),$mode)) {
return false;
}
return mkdir($folder,$mode);
}
/**
* This function renames all occurances of the $search string with the $replace string for a given file.
* It will be used to rename the class-name and wherever that string is used in the test- or student-response-file
* so that it can compile properly. Later we can rename it back to the original strings.
* Safe to call even if folder already exists (checks)
* @param string $file path to the file in which the renaming shall take place
* @param string $search search for this string
* @param string $replace replace the searched string with this string
*/
function temp_rename($file, $search, $replace){
$file_contents = file_get_contents($file);
$fh = fopen($file, "w");
$file_contents = str_replace($search, $replace, $file_contents);
//print_object('testclassname_without_pre_and_java: ' . $search);
//print_object('testclassname_with_pre_and_java: ' . $replace);
fwrite($fh, $file_contents);
fclose($fh);
}
/**
* compile the student-response-file and the test-file
* @param string $temp_file the complete path of the test-file to be compiled
* @param string $studentclass_path the complete path of the student-response-file to be compiled
* @param string $temp_folder the complete path to the temp-folder
* @param string $pre the attempt_questionId of the current attempt of the question compiled
* @param string $testclassname_without_pre_and_java the test-class name without pre and '.java', used to overwrite it temporarily in order to compile properly (needed for proper *.class file naming)
* @return array $compiles, $compileroutput $compiles returns true, if the code compiles, else false; $compileroutput is the content of the compiler output
**/
function compile($temp_file, $studentclass_path, $temp_folder, $pre, $testclassname_without_pre_and_java) {
// Work out the compile command line.
$compileroutputfile = $temp_folder . '/' . $pre . 'compileroutput.log';
//temporarily rename every appearance of $studentsclassname with $pre.$studentsclassname in the .java-files in order to get correct compilation
$this->temp_rename($temp_file, $testclassname_without_pre_and_java, $pre.$testclassname_without_pre_and_java);
$this->temp_rename($studentclass_path, $testclassname_without_pre_and_java, $pre.$testclassname_without_pre_and_java);
//the following $command is the same on different operating systems
$command = PATH_TO_JAVAC . ' -cp ' . PATH_TO_JUNIT . ' ' . $temp_file . ' ' .
$studentclass_path . ' 2> ' . $compileroutputfile;
//print_object($command);
// Execute it.
exec($command);
//undo the renaming in the .java-files
$this->temp_rename($temp_file, $pre.$testclassname_without_pre_and_java, $testclassname_without_pre_and_java);
$this->temp_rename($studentclass_path, $pre.$testclassname_without_pre_and_java, $testclassname_without_pre_and_java);
// get the content of the compiler output.
$compileroutput = file_get_contents($compileroutputfile);
//print_object('compileroutput: '.$compileroutput); //debug
$compiles = empty($compileroutput);
return array($compiles, $compileroutput);
}
/**
* execute the file that should have been compiled first
* @param string $temp_file complete path to the Junit-test-file to be executed
* @param string $temp_folder the complete path to the temp-folder
* @param string $pre the attempt_questionId of the current attempt of the question compiled
* @return string $executionoutput the content of the junit-execution-output
**/
function execute($temp_file, $temp_folder, $pre){
$executionoutputfile = $temp_folder . '/' . $pre . 'executionoutput.log';
// first chdir and prepare Security policy so that no 'bad' code can be executed by the student - execute in the next step secure
if (!chdir($temp_folder)){ // change the directory to the $temp_folder-directory for the policy
return get_string('chdir', 'qtype_sojunit') . $temp_folder;
}
//path for the security policy for the system for any unsecure entries;
//to be executed together with the execution file in $command
$commandWithSecurity =
PATH_TO_JAVA . " -Djava.security.manager=default" // Run standard security manager
. " -Djava.security.policy=". PATH_TO_POLICY . " ";
//in dependence of the operating system use another syntax for the $command
if ($this->operating_system == "WINNT"){ //Windows
$command = $commandWithSecurity . ' -cp ' . PATH_TO_JUNIT . ';.' .
' junit.textui.TestRunner ' . $temp_file . ' > ' . $executionoutputfile . ' 2>&1';
}
if ($this->operating_system == "Linux"){ //Linux
$command = $commandWithSecurity . ' -cp ' . PATH_TO_JUNIT . ':. ' . //set junit and $temp_file paths
'junit.textui.TestRunner ' . $temp_file . ' > ' . $executionoutputfile . ' 2>&1';
//put execution error and failure into $executionoutputfile
}
// Execute it.
exec($command);
// get the content of the execution output.
$executionoutput = file_get_contents($executionoutputfile);
//print_object('executionoutput: '.$executionoutput); //debug
return $executionoutput;
}
/** This method should be overriden if you want to include a special heading or some other
* html on a question editing page besides the question editing form.
*
* JUnit: This code is to enable the possibility of saving inserted text into the HTML-editor
* and switching back to text editor
*
* @param question_edit_form $mform a child of question_edit_form
* @param object $question
* @param string $wizardnow is '' for first page.
*/
function display_question_editing_page(&$mform, $question, $wizardnow){
parent:: display_question_editing_page($mform, $question, $wizardnow);
$formid = $mform->_form->_attributes['id']; // get form id
echo <<<JS
<script type="text/javascript">
document.forms['$formid'].reloaded.value = 0;
function reload(id) {
var form = document.forms['$formid'];
var iframes = form.getElementsByTagName('iframe');
for (var i = 0; i < iframes.length; i++) {
var ifr = iframes[i];
var element = ifr.parentNode.nextSibling;
element.value = ifr.contentWindow.document.body.innerHTML;
}
form.reloaded.value='1';
form.submit();
}
</script>
JS;
}
/** Prints the score obtained and maximum score available plus any penalty
* information
*
* This function prints a summary of the scoring in the most recently
* graded state (the question may not have been submitted for marking at
* the current state). The default implementation should be suitable for most
* question types.
*
* JUnit: Don't print question grading details when pressing the
* "Submit"-button in adaptive mode. Only print those details after
* the use of "Submit all and finish"-button.
*
* @param object $question The question for which the grading details are
* to be rendered. Question type specific information
* is included. The maximum possible grade is in
* ->maxgrade.
* @param object $state The state. In particular the grading information
* is in ->grade, ->raw_grade and ->penalty.
* @param object $cmoptions
* @param object $options An object describing the rendering options.
*/
function print_question_grading_details(&$question, &$state, $cmoptions, $options) {
/* The default implementation prints the number of marks if no attempt
has been made. Otherwise it displays the grade obtained out of the
maximum grade available and a warning if a penalty was applied for the
attempt and displays the overall grade obtained counting all previous
responses (and penalties) */
if (QUESTION_EVENTDUPLICATE == $state->event) {
echo ' ';
print_string('duplicateresponse', 'quiz');
}
if (!empty($question->maxgrade) && $options->scores) {
if (question_state_is_closed($state->last_graded) ){ // Junit: Do the following only if "Submit all and finish"-button ('finishattempt') is pressed
// Display the grading details from the last graded state
$grade = new stdClass;
$grade->cur = round($state->last_graded->grade, $cmoptions->decimalpoints);
$grade->max = $question->maxgrade;
$grade->raw = round($state->last_graded->raw_grade, $cmoptions->decimalpoints);
// let student know wether the answer was correct
echo '<div class="correctness ';
if ($state->last_graded->raw_grade >= $question->maxgrade/1.01) { // We divide by 1.01 so that rounding errors dont matter.
echo ' correct">';
print_string('correct', 'quiz');
} else if ($state->last_graded->raw_grade > 0) {
echo ' partiallycorrect">';
print_string('partiallycorrect', 'quiz');
} else {
echo ' incorrect">';
print_string('incorrect', 'quiz');
}
echo '</div>';
echo '<div class="gradingdetails">';
// print grade for this submission
print_string('gradingdetails', 'quiz', $grade);
if ($cmoptions->penaltyscheme) {
// print details of grade adjustment due to penalties
if ($state->last_graded->raw_grade > $state->last_graded->grade){
echo ' ';
print_string('gradingdetailsadjustment', 'quiz', $grade);
}
// print info about new penalty
// penalty is relevant only if the answer is not correct and further attempts are possible
if (($state->last_graded->raw_grade < $question->maxgrade / 1.01)
and (QUESTION_EVENTCLOSEANDGRADE !== $state->event)) {
if ('' !== $state->last_graded->penalty && ((float)$state->last_graded->penalty) > 0.0) {
// A penalty was applied so display it
echo ' ';
print_string('gradingdetailspenalty', 'quiz', $state->last_graded->penalty);
} else {
/* No penalty was applied even though the answer was
not correct (eg. a syntax error) so tell the student
that they were not penalised for the attempt */
echo ' ';
print_string('gradingdetailszeropenalty', 'quiz');
}
}
}
echo '</div>';
}
}
}
//the following three functions (get_correct_responses, get_all_responses, get_actual_response)
//are needed to get displayed Results->Item analysis
function get_correct_responses(&$question, &$state) {
// We cannot tell what the correct response is.
return null;
}
function get_all_responses(&$question, &$state) {
// We cannot tell the range of possible respnoses that the question can respond to,
// but we need to return something to make things show up in the item analysis report.
$r = new stdClass();
$r->answer = get_string('responses','qtype_sojunit');
$r->credit = 0.0;
$result = new stdClass;
$result->id = $question->id;
$result->responses = array($r);
return $result;
}
//list each attempts' "Answer's text"
function get_actual_response($question, $state) {
$responses = array();
if (!empty($state->responses['__answerLine'])) {
$responses[] = stripslashes($state->responses['__answerLine']);
} else if (!empty($state->responses['__actionSummary'])) {
$responses[] = stripslashes($state->responses['__actionSummary']);
} else {
$responses[] = implode(',output: ', $state->responses);
//this usually displays the student-response, compiler-output and execution-output
//we just want to display compiler- and execution-outputs
//this way the teacher can get a grouped overview by compiler and execution - outputs:
//this way the teacher can understand which test case being problematic for the students or
//how many responses he needs to grade manually since the responses didn't compile or
//if the teachers test-file didn't compile at all
foreach ($responses as $k => $v){
$pos_of_output = strpos($v, ",output: ");
$responses[$k] = substr($v, $pos_of_output + 1);
}
}
return $responses;
}
// need to overwrite this function to let the event-element from the question_states-table know that it shall stay in state 3 as long as the givencode is
// included in the $state->responses['']-array. In case the student gives its own response the state shall change to 2.
function compare_responses($question, $state, $teststate) {
if (isset($state->responses['']) && isset($teststate->responses[''])) {
if ($state->responses[''] == $question->options->givencode){
return true;
}
else{
return strcmp($state->responses[''], $teststate->responses['']) == 0;
}
}
return false;
}
/**
* Backup the data in the question
*
* This is used in question/backuplib.php
*/
function backup($bf,$preferences,$question,$level=6) {
$status = true;
// Sue - code to backup an instance of your question type. //adapted from multichoice_questiontype.php
$sojunits = get_records("question_sojunit", "questionid",$question,"id");
//if there are junits
if ($sojunits){
//iterate over each sojunit
foreach ($sojunits as $sojunit){
$status = fwrite($bf,start_tag("SOJUNIT", $level,true));
//Print sojunit contents
fwrite ($bf,full_tag("GIVENCODE",$level+1,false,$sojunit->givencode));
fwrite ($bf,full_tag("TESTCLASSNAME",$level+1,false,$sojunit->testclassname));
fwrite ($bf,full_tag("USEEDITORGENFEEDB",$level+1,false,$sojunit->useeditor_genfeedb));
fwrite ($bf,full_tag("SOURCECODE",$level+1,false,$sojunit->sourcecode));
$status = fwrite ($bf,end_tag("SOJUNIT",$level,true));
}
//Now print question_answers
$status = question_backup_answers($bf,$preferences,$question);
}
return $status;
}
/**
* Restores the data in the question: write code to restore an instance of your question type
*
* This is used in question/restorelib.php
*/
function restore($old_question_id,$new_question_id,$info,$restore) {
$status = true;
///////*********************************************** taken from regexp question type
$sojunit = '';
//Get the sojunit array
if (array_key_exists('SOJUNIT', $info['#'])) {
$sojunits = $info['#']['SOJUNIT'];
} else {
$sojunits = array();
}
//Iterate over sojunit
for($i = 0; $i < sizeof($sojunits); $i++) {
$jun_info = $sojunits[$i];
//Now, build the question_sojunit record structure
$sojunit = new stdClass;
$sojunit->questionid = $new_question_id;
$sojunit->givencode = backup_todb($jun_info['#']['GIVENCODE']['0']['#']);
$sojunit->testclassname = backup_todb($jun_info['#']['TESTCLASSNAME']['0']['#']);
$sojunit->useeditor_genfeedb = backup_todb($jun_info['#']['USEEDITORGENFEEDB']['0']['#']);
$sojunit->sourcecode = backup_todb($jun_info['#']['SOURCECODE']['0']['#']);
//The structure is equal to the db, so insert the question_regexp
$newid = insert_record ("question_sojunit",$sojunit);
//Do some output
if (($i+1) % 50 == 0) {
if (!defined('RESTORE_SILENTLY')) {
echo ".";
if (($i+1) % 1000 == 0) {
echo "<br />";
}
}
backup_flush(300);
}
if (!$newid) {
$status = false;
}
////****************************************************
}
return $status;
}
}
// Register this question type with the system.
question_register_questiontype(new sojunit_qtype());
?>
| Moodle CVS Admin | ViewVC Help |
| Powered by ViewVC 1.0.7 |