Automatic installer.php lang files by installer_builder (20070726)
[moodle-linuxchix.git] / question / type / shortanswer / questiontype.php
bloba36550f6abce8b02a2a2e06a2f714701ed31d6ef
1 <?php // $Id$
3 ///////////////////
4 /// SHORTANSWER ///
5 ///////////////////
7 /// QUESTION TYPE CLASS //////////////////
9 ///
10 /// This class contains some special features in order to make the
11 /// question type embeddable within a multianswer (cloze) question
12 ///
13 /**
14 * @package questionbank
15 * @subpackage questiontypes
17 require_once("$CFG->dirroot/question/type/questiontype.php");
19 class question_shortanswer_qtype extends default_questiontype {
21 function name() {
22 return 'shortanswer';
25 function get_question_options(&$question) {
26 // Get additional information from database
27 // and attach it to the question object
28 if (!$question->options = get_record('question_shortanswer', 'question', $question->id)) {
29 notify('Error: Missing question options!');
30 return false;
33 if (!$question->options->answers = get_records('question_answers', 'question',
34 $question->id, 'id ASC')) {
35 notify('Error: Missing question answers!');
36 return false;
38 return true;
41 function save_question_options($question) {
42 $result = new stdClass;
44 if (!$oldanswers = get_records('question_answers', 'question', $question->id, 'id ASC')) {
45 $oldanswers = array();
48 $answers = array();
49 $maxfraction = -1;
51 // Insert all the new answers
52 foreach ($question->answer as $key => $dataanswer) {
53 if ($dataanswer != "") {
54 if ($oldanswer = array_shift($oldanswers)) { // Existing answer, so reuse it
55 $answer = $oldanswer;
56 $answer->answer = trim($dataanswer);
57 $answer->fraction = $question->fraction[$key];
58 $answer->feedback = $question->feedback[$key];
59 if (!update_record("question_answers", $answer)) {
60 $result->error = "Could not update quiz answer! (id=$answer->id)";
61 return $result;
63 } else { // This is a completely new answer
64 $answer = new stdClass;
65 $answer->answer = trim($dataanswer);
66 $answer->question = $question->id;
67 $answer->fraction = $question->fraction[$key];
68 $answer->feedback = $question->feedback[$key];
69 if (!$answer->id = insert_record("question_answers", $answer)) {
70 $result->error = "Could not insert quiz answer!";
71 return $result;
74 $answers[] = $answer->id;
75 if ($question->fraction[$key] > $maxfraction) {
76 $maxfraction = $question->fraction[$key];
81 if ($options = get_record("question_shortanswer", "question", $question->id)) {
82 $options->answers = implode(",",$answers);
83 $options->usecase = $question->usecase;
84 if (!update_record("question_shortanswer", $options)) {
85 $result->error = "Could not update quiz shortanswer options! (id=$options->id)";
86 return $result;
88 } else {
89 unset($options);
90 $options->question = $question->id;
91 $options->answers = implode(",",$answers);
92 $options->usecase = $question->usecase;
93 if (!insert_record("question_shortanswer", $options)) {
94 $result->error = "Could not insert quiz shortanswer options!";
95 return $result;
99 // delete old answer records
100 if (!empty($oldanswers)) {
101 foreach($oldanswers as $oa) {
102 delete_records('question_answers', 'id', $oa->id);
106 /// Perform sanity checks on fractional grades
107 if ($maxfraction != 1) {
108 $maxfraction = $maxfraction * 100;
109 $result->noticeyesno = get_string("fractionsnomax", "quiz", $maxfraction);
110 return $result;
111 } else {
112 return true;
117 * Deletes question from the question-type specific tables
119 * @return boolean Success/Failure
120 * @param object $question The question being deleted
122 function delete_question($questionid) {
123 delete_records("question_shortanswer", "question", $questionid);
124 return true;
127 function print_question_formulation_and_controls(&$question, &$state, $cmoptions, $options) {
128 global $CFG;
129 /// This implementation is also used by question type 'numerical'
130 $readonly = empty($options->readonly) ? '' : 'readonly="readonly"';
131 $formatoptions = new stdClass;
132 $formatoptions->noclean = true;
133 $formatoptions->para = false;
134 $nameprefix = $question->name_prefix;
136 /// Print question text and media
138 $questiontext = format_text($question->questiontext,
139 $question->questiontextformat,
140 $formatoptions, $cmoptions->course);
141 $image = get_question_image($question, $cmoptions->course);
143 /// Print input controls
145 if (isset($state->responses[''])) {
146 $value = ' value="'.s($state->responses[''], true).'" ';
147 } else {
148 $value = ' value="" ';
150 $inputname = ' name="'.$nameprefix.'" ';
152 $feedback = '';
153 $class = '';
154 $feedbackimg = '';
156 if ($options->feedback) {
157 $class = question_get_feedback_class(0);
158 $feedbackimg = question_get_feedback_image(0);
159 foreach($question->options->answers as $answer) {
161 if ($this->test_response($question, $state, $answer)) {
162 // Answer was correct or partially correct.
163 $class = question_get_feedback_class($answer->fraction);
164 $feedbackimg = question_get_feedback_image($answer->fraction);
165 if ($answer->feedback) {
166 $feedback = format_text($answer->feedback, true, $formatoptions, $cmoptions->course);
168 break;
173 /// Removed correct answer, to be displayed later MDL-7496
174 include("$CFG->dirroot/question/type/shortanswer/display.html");
177 // ULPGC ecastro
178 function check_response(&$question, &$state) {
179 $answers = &$question->options->answers;
180 $testedstate = clone($state);
181 $teststate = clone($state);
182 foreach($answers as $aid => $answer) {
183 $teststate->responses[''] = trim($answer->answer);
184 if($this->compare_responses($question, $testedstate, $teststate)) {
185 return $aid;
188 return false;
191 function compare_responses($question, $state, $teststate) {
192 if (isset($state->responses['']) && isset($teststate->responses[''])) {
193 if ($question->options->usecase) {
194 return strcmp($state->responses[''], $teststate->responses['']) == 0;
195 } else {
196 $textlib = textlib_get_instance();
197 return strcmp($textlib->strtolower($state->responses['']),
198 $textlib->strtolower($teststate->responses[''])) == 0;
201 return false;
204 function test_response(&$question, $state, $answer) {
205 return $this->compare_string_with_wildcard(stripslashes_safe($state->responses['']),
206 $answer->answer, !$question->options->usecase);
209 function compare_string_with_wildcard($string, $pattern, $ignorecase) {
210 // Break the string on non-escaped asterisks.
211 $bits = preg_split('/(?<!\\\\)\*/', $pattern);
212 // Escape regexp special characters in the bits.
213 $bits = array_map('preg_quote', $bits);
214 // Put it back together to make the regexp.
215 $regexp = '|^' . implode('.*', $bits) . '$|u';
217 // Make the match insensitive if requested to.
218 if ($ignorecase) {
219 $regexp .= 'i';
222 return preg_match($regexp, trim($string));
225 /// BACKUP FUNCTIONS ////////////////////////////
228 * Backup the data in the question
230 * This is used in question/backuplib.php
232 function backup($bf,$preferences,$question,$level=6) {
234 $status = true;
236 $shortanswers = get_records('question_shortanswer', 'question', $question, 'id ASC');
237 //If there are shortanswers
238 if ($shortanswers) {
239 //Iterate over each shortanswer
240 foreach ($shortanswers as $shortanswer) {
241 $status = fwrite ($bf,start_tag("SHORTANSWER",$level,true));
242 //Print shortanswer contents
243 fwrite ($bf,full_tag("ANSWERS",$level+1,false,$shortanswer->answers));
244 fwrite ($bf,full_tag("USECASE",$level+1,false,$shortanswer->usecase));
245 $status = fwrite ($bf,end_tag("SHORTANSWER",$level,true));
247 //Now print question_answers
248 $status = question_backup_answers($bf,$preferences,$question);
250 return $status;
253 /// RESTORE FUNCTIONS /////////////////
256 * Restores the data in the question
258 * This is used in question/restorelib.php
260 function restore($old_question_id,$new_question_id,$info,$restore) {
262 $status = true;
264 //Get the shortanswers array
265 $shortanswers = $info['#']['SHORTANSWER'];
267 //Iterate over shortanswers
268 for($i = 0; $i < sizeof($shortanswers); $i++) {
269 $sho_info = $shortanswers[$i];
271 //Now, build the question_shortanswer record structure
272 $shortanswer = new stdClass;
273 $shortanswer->question = $new_question_id;
274 $shortanswer->answers = backup_todb($sho_info['#']['ANSWERS']['0']['#']);
275 $shortanswer->usecase = backup_todb($sho_info['#']['USECASE']['0']['#']);
277 //We have to recode the answers field (a list of answers id)
278 //Extracts answer id from sequence
279 $answers_field = "";
280 $in_first = true;
281 $tok = strtok($shortanswer->answers,",");
282 while ($tok) {
283 //Get the answer from backup_ids
284 $answer = backup_getid($restore->backup_unique_code,"question_answers",$tok);
285 if ($answer) {
286 if ($in_first) {
287 $answers_field .= $answer->new_id;
288 $in_first = false;
289 } else {
290 $answers_field .= ",".$answer->new_id;
293 //check for next
294 $tok = strtok(",");
296 //We have the answers field recoded to its new ids
297 $shortanswer->answers = $answers_field;
299 //The structure is equal to the db, so insert the question_shortanswer
300 $newid = insert_record ("question_shortanswer",$shortanswer);
302 //Do some output
303 if (($i+1) % 50 == 0) {
304 if (!defined('RESTORE_SILENTLY')) {
305 echo ".";
306 if (($i+1) % 1000 == 0) {
307 echo "<br />";
310 backup_flush(300);
313 if (!$newid) {
314 $status = false;
318 return $status;
323 * Prints the score obtained and maximum score available plus any penalty
324 * information
326 * This function prints a summary of the scoring in the most recently
327 * graded state (the question may not have been submitted for marking at
328 * the current state). The default implementation should be suitable for most
329 * question types.
330 * @param object $question The question for which the grading details are
331 * to be rendered. Question type specific information
332 * is included. The maximum possible grade is in
333 * ->maxgrade.
334 * @param object $state The state. In particular the grading information
335 * is in ->grade, ->raw_grade and ->penalty.
336 * @param object $cmoptions
337 * @param object $options An object describing the rendering options.
339 function print_question_grading_details(&$question, &$state, $cmoptions, $options) {
340 /* The default implementation prints the number of marks if no attempt
341 has been made. Otherwise it displays the grade obtained out of the
342 maximum grade available and a warning if a penalty was applied for the
343 attempt and displays the overall grade obtained counting all previous
344 responses (and penalties) */
346 // MDL-7496 show correct answer after "Incorrect"
347 $correctanswer = '';
348 if ($correctanswers = $this->get_correct_responses($question, $state)) {
349 if ($options->readonly && $options->correct_responses) {
350 $delimiter = '';
351 if ($correctanswers) {
352 foreach ($correctanswers as $ca) {
353 $correctanswer .= $delimiter.$ca;
354 $delimiter = ', ';
360 if (QUESTION_EVENTDUPLICATE == $state->event) {
361 echo ' ';
362 print_string('duplicateresponse', 'quiz');
364 if (!empty($question->maxgrade) && $options->scores) {
365 if (question_state_is_graded($state->last_graded)) {
366 // Display the grading details from the last graded state
367 $grade = new stdClass;
368 $grade->cur = round($state->last_graded->grade, $cmoptions->decimalpoints);
369 $grade->max = $question->maxgrade;
370 $grade->raw = round($state->last_graded->raw_grade, $cmoptions->decimalpoints);
372 // let student know wether the answer was correct
373 echo '<div class="correctness ';
374 if ($state->last_graded->raw_grade >= $question->maxgrade/1.01) { // We divide by 1.01 so that rounding errors dont matter.
375 echo ' correct">';
376 print_string('correct', 'quiz');
377 } else if ($state->last_graded->raw_grade > 0) {
378 echo ' partiallycorrect">';
379 print_string('partiallycorrect', 'quiz');
380 // MDL-7496
381 if ($correctanswer) {
382 echo ('<div class="correctness">');
383 print_string('correctansweris', 'quiz', s($correctanswer));
384 echo ('</div>');
386 } else {
387 echo ' incorrect">';
388 // MDL-7496
389 print_string('incorrect', 'quiz');
390 if ($correctanswer) {
391 echo ('<div class="correctness">');
392 print_string('correctansweris', 'quiz', s($correctanswer));
393 echo ('</div>');
396 echo '</div>';
398 echo '<div class="gradingdetails">';
399 // print grade for this submission
400 print_string('gradingdetails', 'quiz', $grade);
401 if ($cmoptions->penaltyscheme) {
402 // print details of grade adjustment due to penalties
403 if ($state->last_graded->raw_grade > $state->last_graded->grade){
404 echo ' ';
405 print_string('gradingdetailsadjustment', 'quiz', $grade);
407 // print info about new penalty
408 // penalty is relevant only if the answer is not correct and further attempts are possible
409 if (($state->last_graded->raw_grade < $question->maxgrade) and (QUESTION_EVENTCLOSEANDGRADE !== $state->event)) {
410 if ('' !== $state->last_graded->penalty && ((float)$state->last_graded->penalty) > 0.0) {
411 // A penalty was applied so display it
412 echo ' ';
413 print_string('gradingdetailspenalty', 'quiz', $state->last_graded->penalty);
414 } else {
415 /* No penalty was applied even though the answer was
416 not correct (eg. a syntax error) so tell the student
417 that they were not penalised for the attempt */
418 echo ' ';
419 print_string('gradingdetailszeropenalty', 'quiz');
423 echo '</div>';
434 //// END OF CLASS ////
436 //////////////////////////////////////////////////////////////////////////
437 //// INITIATION - Without this line the question type is not in use... ///
438 //////////////////////////////////////////////////////////////////////////
439 question_register_questiontype(new question_shortanswer_qtype());