MDL-28364 handle question formats that support multiple file types
[moodle.git] / question / format / blackboard_six / format.php
CommitLineData
03f5a0f8 1<?php
d3603157
TH
2// This file is part of Moodle - http://moodle.org/
3//
4// Moodle is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Moodle is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
16
41a89a07 17/**
d3603157
TH
18 * Blackboard 6.0 question importer.
19 *
20 * @package qformat
21 * @subpackage blackboard_six
22 * @copyright 2005 Michael Penney
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
41a89a07 24 */
03f5a0f8 25
d3603157 26
a17b297d
TH
27defined('MOODLE_INTERNAL') || die();
28
d3603157
TH
29require_once ($CFG->libdir . '/xmlize.php');
30
31
32/**
33 * Blackboard 6.0 question importer.
34 *
35 * @copyright 2005 Michael Penney
36 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
37 */
f94902db 38class qformat_blackboard_six extends qformat_default {
03f5a0f8 39 function provide_import() {
40 return true;
41 }
7da7bfa1
JF
42
43 public function can_import_file($file) {
44 $mimetypes = array(
45 mimeinfo('type', '.dat'),
46 mimeinfo('type', '.zip')
47 );
48 return in_array($file->get_mimetype(), $mimetypes);
49 }
aeb15530
PS
50
51
03f5a0f8 52 //Function to check and create the needed dir to unzip file to
53 function check_and_create_import_dir($unique_code) {
54
aeb15530 55 global $CFG;
03f5a0f8 56
7aa06e6d 57 $status = $this->check_dir_exists($CFG->tempdir."",true);
03f5a0f8 58 if ($status) {
7aa06e6d 59 $status = $this->check_dir_exists($CFG->tempdir."/bbquiz_import",true);
03f5a0f8 60 }
61 if ($status) {
7aa06e6d 62 $status = $this->check_dir_exists($CFG->tempdir."/bbquiz_import/".$unique_code,true);
03f5a0f8 63 }
aeb15530 64
03f5a0f8 65 return $status;
66 }
aeb15530 67
03f5a0f8 68 function clean_temp_dir($dir='') {
4756e9c9
PS
69 global $CFG;
70
aeb15530 71 // for now we will just say everything happened okay note
7aa06e6d 72 // that a mess may be piling up in $CFG->tempdir/bbquiz_import
0cd25539 73 // TODO return true at top of the function renders all the following code useless
03f5a0f8 74 return true;
aeb15530 75
03f5a0f8 76 if ($dir == '') {
aeb15530 77 $dir = $this->temp_dir;
03f5a0f8 78 }
79 $slash = "/";
80
81 // Create arrays to store files and directories
82 $dir_files = array();
83 $dir_subdirs = array();
84
85 // Make sure we can delete it
4756e9c9 86 chmod($dir, $CFG->directorypermissions);
03f5a0f8 87
88 if ((($handle = opendir($dir))) == FALSE) {
89 // The directory could not be opened
90 return false;
91 }
92
93 // Loop through all directory entries, and construct two temporary arrays containing files and sub directories
ed818bbd 94 while(false !== ($entry = readdir($handle))) {
03f5a0f8 95 if (is_dir($dir. $slash .$entry) && $entry != ".." && $entry != ".") {
96 $dir_subdirs[] = $dir. $slash .$entry;
97 }
98 else if ($entry != ".." && $entry != ".") {
99 $dir_files[] = $dir. $slash .$entry;
100 }
101 }
102
103 // Delete all files in the curent directory return false and halt if a file cannot be removed
b692e4aa
AB
104 $countdir_files = count($dir_files);
105 for($i=0; $i<$countdir_files; $i++) {
4756e9c9 106 chmod($dir_files[$i], $CFG->directorypermissions);
03f5a0f8 107 if (((unlink($dir_files[$i]))) == FALSE) {
108 return false;
109 }
110 }
111
112 // Empty sub directories and then remove the directory
b692e4aa
AB
113 $countdir_subdirs = count($dir_subdirs);
114 for($i=0; $i<$countdir_subdirs; $i++) {
4756e9c9 115 chmod($dir_subdirs[$i], $CFG->directorypermissions);
03f5a0f8 116 if ($this->clean_temp_dir($dir_subdirs[$i]) == FALSE) {
117 return false;
118 }
119 else {
120 if (rmdir($dir_subdirs[$i]) == FALSE) {
121 return false;
122 }
123 }
124 }
125
126 // Close directory
127 closedir($handle);
128 if (rmdir($this->temp_dir) == FALSE) {
aeb15530 129 return false;
03f5a0f8 130 }
131 // Success, every thing is gone return true
132 return true;
133 }
aeb15530 134
03f5a0f8 135 //Function to check if a directory exists and, optionally, create it
136 function check_dir_exists($dir,$create=false) {
137
aeb15530 138 global $CFG;
03f5a0f8 139
140 $status = true;
141 if(!is_dir($dir)) {
142 if (!$create) {
143 $status = false;
144 } else {
145 umask(0000);
146 $status = mkdir ($dir,$CFG->directorypermissions);
147 }
148 }
149 return $status;
150 }
151
152 function importpostprocess() {
153 /// Does any post-processing that may be desired
aeb15530 154 /// Argument is a simple array of question ids that
03f5a0f8 155 /// have just been added.
aeb15530 156
03f5a0f8 157 // need to clean up temporary directory
158 return $this->clean_temp_dir();
159 }
160
161 function copy_file_to_course($filename) {
60f9e36e 162 global $CFG, $COURSE;
03f5a0f8 163 $filename = str_replace('\\','/',$filename);
164 $fullpath = $this->temp_dir.'/res00001/'.$filename;
165 $basename = basename($filename);
aeb15530 166
60f9e36e 167 $copy_to = $CFG->dataroot.'/'.$COURSE->id.'/bb_import';
aeb15530 168
03f5a0f8 169 if ($this->check_dir_exists($copy_to,true)) {
170 if(is_readable($fullpath)) {
171 $copy_to.= '/'.$basename;
172 if (!copy($fullpath, $copy_to)) {
173 return false;
174 }
175 else {
176 return $copy_to;
177 }
178 }
179 }
180 else {
aeb15530 181 return false;
03f5a0f8 182 }
183 }
184
185 function readdata($filename) {
186 /// Returns complete file with an array, one item per line
187 global $CFG;
aeb15530 188
73b7b195 189 // if the extension is .dat we just return that,
190 // if .zip we unzip the file and get the data
aeb15530 191 $ext = substr($this->realfilename, strpos($this->realfilename,'.'), strlen($this->realfilename)-1);
73b7b195 192 if ($ext=='.dat') {
193 if (!is_readable($filename)) {
aeb15530
PS
194 print_error('filenotreadable', 'error');
195 }
73b7b195 196 return file($filename);
aeb15530
PS
197 }
198
03f5a0f8 199 $unique_code = time();
7aa06e6d 200 $temp_dir = $CFG->tempdir."/bbquiz_import/".$unique_code;
03f5a0f8 201 $this->temp_dir = $temp_dir;
202 if ($this->check_and_create_import_dir($unique_code)) {
203 if(is_readable($filename)) {
204 if (!copy($filename, "$temp_dir/bboard.zip")) {
0be2c858 205 print_error('cannotcopybackup', 'question');
03f5a0f8 206 }
207 if(unzip_file("$temp_dir/bboard.zip", '', false)) {
208 // assuming that the information is in res0001.dat
209 // after looking at 6 examples this was always the case
210 $q_file = "$temp_dir/res00001.dat";
211 if (is_file($q_file)) {
212 if (is_readable($q_file)) {
213 $filearray = file($q_file);
214 /// Check for Macintosh OS line returns (ie file on one line), and fix
6dbcacee 215 if (preg_match("~\r~", $filearray[0]) AND !preg_match("~\n~", $filearray[0])) {
03f5a0f8 216 return explode("\r", $filearray[0]);
217 } else {
218 return $filearray;
219 }
03f5a0f8 220 }
221 }
222 else {
aeb15530 223 print_error('cannotfindquestionfile', 'questioni');
03f5a0f8 224 }
225 }
226 else {
227 print "filename: $filename<br />tempdir: $temp_dir <br />";
aeb15530 228 print_error('cannotunzip', 'question');
03f5a0f8 229 }
230 }
231 else {
aeb15530 232 print_error('cannotreaduploadfile');
03f5a0f8 233 }
234 }
235 else {
aeb15530 236 print_error('cannotcreatetempdir');
03f5a0f8 237 }
238 }
aeb15530 239
03f5a0f8 240 function save_question_options($question) {
aeb15530 241 return true;
03f5a0f8 242 }
aeb15530
PS
243
244
245
03f5a0f8 246 function readquestions ($lines) {
247 /// Parses an array of lines into an array of questions,
248 /// where each item is a question object as defined by
aeb15530 249 /// readquestion().
03f5a0f8 250
251 $text = implode($lines, " ");
252 $xml = xmlize($text, 0);
253
254 $raw_questions = $xml['questestinterop']['#']['assessment'][0]['#']['section'][0]['#']['item'];
255 $questions = array();
256
257 foreach($raw_questions as $quest) {
258 $question = $this->create_raw_question($quest);
259
260 switch($question->qtype) {
261 case "Matching":
262 $this->process_matching($question, $questions);
263 break;
264 case "Multiple Choice":
265 $this->process_mc($question, $questions);
266 break;
267 case "Essay":
268 $this->process_essay($question, $questions);
269 break;
270 case "Multiple Answer":
271 $this->process_ma($question, $questions);
272 break;
273 case "True/False":
274 $this->process_tf($question, $questions);
275 break;
276 case 'Fill in the Blank':
277 $this->process_fblank($question, $questions);
278 break;
3e60818b 279 case 'Short Response':
280 $this->process_essay($question, $questions);
281 break;
03f5a0f8 282 default:
283 print "Unknown or unhandled question type: \"$question->qtype\"<br />";
284 break;
285 }
286
287 }
288 return $questions;
289 }
290
291
292// creates a cleaner object to deal with for processing into moodle
293// the object created is NOT a moodle question object
294function create_raw_question($quest) {
aeb15530 295
0ff4bd08 296 $question = new stdClass();
03f5a0f8 297 $question->qtype = $quest['#']['itemmetadata'][0]['#']['bbmd_questiontype'][0]['#'];
e53ff7f6 298 $question->id = $quest['#']['itemmetadata'][0]['#']['bbmd_asi_object_id'][0]['#'];
03f5a0f8 299 $presentation->blocks = $quest['#']['presentation'][0]['#']['flow'][0]['#']['flow'];
300
301 foreach($presentation->blocks as $pblock) {
aeb15530 302
03f5a0f8 303 $block = NULL;
304 $block->type = $pblock['@']['class'];
305
306 switch($block->type) {
307 case 'QUESTION_BLOCK':
308 $sub_blocks = $pblock['#']['flow'];
309 foreach($sub_blocks as $sblock) {
310 //echo "Calling process_block from line 263<br>";
aeb15530 311 $this->process_block($sblock, $block);
03f5a0f8 312 }
313 break;
314
315 case 'RESPONSE_BLOCK':
316 $choices = NULL;
317 switch($question->qtype) {
318 case 'Matching':
319 $bb_subquestions = $pblock['#']['flow'];
320 $sub_questions = array();
321 foreach($bb_subquestions as $bb_subquestion) {
322 $sub_question = NULL;
323 $sub_question->ident = $bb_subquestion['#']['response_lid'][0]['@']['ident'];
324 $this->process_block($bb_subquestion['#']['flow'][0], $sub_question);
325 $bb_choices = $bb_subquestion['#']['response_lid'][0]['#']['render_choice'][0]['#']['flow_label'][0]['#']['response_label'];
326 $choices = array();
327 $this->process_choices($bb_choices, $choices);
328 $sub_question->choices = $choices;
329 if (!isset($block->subquestions)) {
330 $block->subquestions = array();
331 }
332 $block->subquestions[] = $sub_question;
333 }
334 break;
335 case 'Multiple Answer':
336 $bb_choices = $pblock['#']['response_lid'][0]['#']['render_choice'][0]['#']['flow_label'];
337 $choices = array();
338 $this->process_choices($bb_choices, $choices);
339 $block->choices = $choices;
340 break;
341 case 'Essay':
342 // Doesn't apply since the user responds with text input
343 break;
344 case 'Multiple Choice':
345 $mc_choices = $pblock['#']['response_lid'][0]['#']['render_choice'][0]['#']['flow_label'];
346 foreach($mc_choices as $mc_choice) {
347 $choices = NULL;
348 $choices = $this->process_block($mc_choice, $choices);
aeb15530 349 $block->choices[] = $choices;
03f5a0f8 350 }
351 break;
3e60818b 352 case 'Short Response':
353 // do nothing?
354 break;
03f5a0f8 355 case 'Fill in the Blank':
356 // do nothing?
357 break;
358 default:
359 $bb_choices = $pblock['#']['response_lid'][0]['#']['render_choice'][0]['#']['flow_label'][0]['#']['response_label'];
360 $choices = array();
361 $this->process_choices($bb_choices, $choices);
362 $block->choices = $choices;
363 }
364 break;
365 case 'RIGHT_MATCH_BLOCK':
366 $matching_answerset = $pblock['#']['flow'];
1d1e8f2b 367
03f5a0f8 368 $answerset = array();
369 foreach($matching_answerset as $answer) {
1d1e8f2b 370 // $answerset[] = $this->process_block($answer, $bb_answer);
371 $bb_answer = null;
372 $bb_answer->text = $answer['#']['flow'][0]['#']['material'][0]['#']['mat_extension'][0]['#']['mat_formattedtext'][0]['#'];
03f5a0f8 373 $answerset[] = $bb_answer;
374 }
375 $block->matching_answerset = $answerset;
376 break;
377 default:
378 print "UNHANDLED PRESENTATION BLOCK";
379 break;
380 }
381 $question->{$block->type} = $block;
382 }
aeb15530
PS
383
384 // determine response processing
03f5a0f8 385 // there is a section called 'outcomes' that I don't know what to do with
386 $resprocessing = $quest['#']['resprocessing'];
387 $respconditions = $resprocessing[0]['#']['respcondition'];
388 $reponses = array();
389 if ($question->qtype == 'Matching') {
390 $this->process_matching_responses($respconditions, $responses);
391 }
392 else {
393 $this->process_responses($respconditions, $responses);
394 }
395 $question->responses = $responses;
396 $feedbackset = $quest['#']['itemfeedback'];
397 $feedbacks = array();
398 $this->process_feedback($feedbackset, $feedbacks);
399 $question->feedback = $feedbacks;
400 return $question;
401}
402
403function process_block($cur_block, &$block) {
60f9e36e 404 global $COURSE, $CFG;
03f5a0f8 405
406 $cur_type = $cur_block['@']['class'];
407 switch($cur_type) {
408 case 'FORMATTED_TEXT_BLOCK':
aeb15530 409 $block->text = $this->strip_applet_tags_get_mathml($cur_block['#']['material'][0]['#']['mat_extension'][0]['#']['mat_formattedtext'][0]['#']);
03f5a0f8 410 break;
411 case 'FILE_BLOCK':
412 //revisit this to make sure it is working correctly
aeb15530 413 // Commented out ['matapplication']..., etc. because I
03f5a0f8 414 // noticed that when I imported a new Blackboard 6 file
415 // and printed out the block, the tree did not extend past ['material'][0]['#'] - CT 8/3/06
416 $block->file = $cur_block['#']['material'][0]['#'];//['matapplication'][0]['@']['uri'];
417 if ($block->file != '') {
418 // if we have a file copy it to the course dir and adjust its name to be visible over the web.
419 $block->file = $this->copy_file_to_course($block->file);
60f9e36e 420 $block->file = $CFG->wwwroot.'/file.php/'.$COURSE->id.'/bb_import/'.basename($block->file);
03f5a0f8 421 }
422 break;
423 case 'Block':
424 if (isset($cur_block['#']['material'][0]['#']['mattext'][0]['#'])) {
425 $block->text = $cur_block['#']['material'][0]['#']['mattext'][0]['#'];
426 }
427 else if (isset($cur_block['#']['material'][0]['#']['mat_extension'][0]['#']['mat_formattedtext'][0]['#'])) {
428 $block->text = $cur_block['#']['material'][0]['#']['mat_extension'][0]['#']['mat_formattedtext'][0]['#'];
429 }
430 else if (isset($cur_block['#']['response_label'])) {
431 // this is a response label block
432 $sub_blocks = $cur_block['#']['response_label'][0];
433 if(!isset($block->ident)) {
434 if(isset($sub_blocks['@']['ident'])) {
435 $block->ident = $sub_blocks['@']['ident'];
436 }
437 }
438 foreach($sub_blocks['#']['flow_mat'] as $sub_block) {
aeb15530 439 $this->process_block($sub_block, $block);
03f5a0f8 440 }
441 }
442 else {
443 if (isset($cur_block['#']['flow_mat']) || isset($cur_block['#']['flow'])) {
444 if (isset($cur_block['#']['flow_mat'])) {
445 $sub_blocks = $cur_block['#']['flow_mat'];
446 }
447 elseif (isset($cur_block['#']['flow'])) {
448 $sub_blocks = $cur_block['#']['flow'];
449 }
450 foreach ($sub_blocks as $sblock) {
451 // this will recursively grab the sub blocks which should be of one of the other types
452 $this->process_block($sblock, $block);
453 }
454 }
455 }
456 break;
457 case 'LINK_BLOCK':
458 // not sure how this should be included
459 if (!empty($cur_block['#']['material'][0]['#']['mattext'][0]['@']['uri'])) {
460 $block->link = $cur_block['#']['material'][0]['#']['mattext'][0]['@']['uri'];
461 }
462 else {
463 $block->link = '';
464 }
aeb15530
PS
465 break;
466 }
03f5a0f8 467 return $block;
468}
469
470function process_choices($bb_choices, &$choices) {
471 foreach($bb_choices as $choice) {
472 if (isset($choice['@']['ident'])) {
473 $cur_choice = $choice['@']['ident'];
474 }
475 else { //for multiple answer
476 $cur_choice = $choice['#']['response_label'][0];//['@']['ident'];
477 }
478 if (isset($choice['#']['flow_mat'][0])) { //for multiple answer
479 $cur_block = $choice['#']['flow_mat'][0];
480 // Reset $cur_choice to NULL because process_block is expecting an object
481 // for the second argument and not a string, which is what is was set as
482 // originally - CT 8/7/06
aeb15530 483 $cur_choice = null;
03f5a0f8 484 $this->process_block($cur_block, $cur_choice);
485 }
486 elseif (isset($choice['#']['response_label'])) {
487 // Reset $cur_choice to NULL because process_block is expecting an object
488 // for the second argument and not a string, which is what is was set as
489 // originally - CT 8/7/06
aeb15530 490 $cur_choice = null;
03f5a0f8 491 $this->process_block($choice, $cur_choice);
492 }
493 $choices[] = $cur_choice;
aeb15530 494 }
03f5a0f8 495}
496
497function process_matching_responses($bb_responses, &$responses) {
498 foreach($bb_responses as $bb_response) {
499 $response = NULL;
500 if (isset($bb_response['#']['conditionvar'][0]['#']['varequal'])) {
501 $response->correct = $bb_response['#']['conditionvar'][0]['#']['varequal'][0]['#'];
502 $response->ident = $bb_response['#']['conditionvar'][0]['#']['varequal'][0]['@']['respident'];
503 }
504 else {
505 $response->correct = 'Broken Question?';
506 $response->ident = 'Broken Question?';
507 }
508 $response->feedback = $bb_response['#']['displayfeedback'][0]['@']['linkrefid'];
509 $responses[] = $response;
510 }
511}
512
513function process_responses($bb_responses, &$responses) {
514 foreach($bb_responses as $bb_response) {
515 //Added this line to instantiate $response.
516 // Without instantiating the $response variable, the same object
517 // gets added to the array
92701024 518 $response = new stdClass();
03f5a0f8 519 if (isset($bb_response['@']['title'])) {
aeb15530 520 $response->title = $bb_response['@']['title'];
03f5a0f8 521 }
522 else {
523 $reponse->title = $bb_response['#']['displayfeedback'][0]['@']['linkrefid'];
524 }
525 $reponse->ident = array();
526 if (isset($bb_response['#']['conditionvar'][0]['#'])){//['varequal'][0]['#'])) {
aeb15530 527 $response->ident[0] = $bb_response['#']['conditionvar'][0]['#'];//['varequal'][0]['#'];
03f5a0f8 528 }
529 else if (isset($bb_response['#']['conditionvar'][0]['#']['other'][0]['#'])) {
aeb15530 530 $response->ident[0] = $bb_response['#']['conditionvar'][0]['#']['other'][0]['#'];
03f5a0f8 531 }
aeb15530 532
03f5a0f8 533 if (isset($bb_response['#']['conditionvar'][0]['#']['and'])){//[0]['#'])) {
534 $responseset = $bb_response['#']['conditionvar'][0]['#']['and'];//[0]['#']['varequal'];
535 foreach($responseset as $rs) {
536 $response->ident[] = $rs['#'];
537 if(!isset($response->feedback) and isset( $rs['@'] ) ) {
538 $response->feedback = $rs['@']['respident'];
aeb15530 539 }
03f5a0f8 540 }
541 }
542 else {
aeb15530 543 $response->feedback = $bb_response['#']['displayfeedback'][0]['@']['linkrefid'];
03f5a0f8 544 }
545
546 // determine what point value to give response
547 if (isset($bb_response['#']['setvar'])) {
548 switch ($bb_response['#']['setvar'][0]['#']) {
549 case "SCORE.max":
550 $response->fraction = 1;
551 break;
552 default:
aeb15530 553 // I have only seen this being 0 or unset
03f5a0f8 554 // there are probably fractional values of SCORE.max, but I'm not sure what they look like
555 $response->fraction = 0;
556 break;
557 }
558 }
559 else {
560 // just going to assume this is the case this is probably not correct.
561 $response->fraction = 0;
562 }
aeb15530 563
03f5a0f8 564 $responses[] = $response;
565 }
566}
567
568function process_feedback($feedbackset, &$feedbacks) {
569 foreach($feedbackset as $bb_feedback) {
570 // Added line $feedback=null so that $feedback does not get reused in the loop
571 // and added the the $feedbacks[] array multiple times
aeb15530 572 $feedback = null;
03f5a0f8 573 $feedback->ident = $bb_feedback['@']['ident'];
574 if (isset($bb_feedback['#']['flow_mat'][0])) {
575 $this->process_block($bb_feedback['#']['flow_mat'][0], $feedback);
576 }
577 elseif (isset($bb_feedback['#']['solution'][0]['#']['solutionmaterial'][0]['#']['flow_mat'][0])) {
578 $this->process_block($bb_feedback['#']['solution'][0]['#']['solutionmaterial'][0]['#']['flow_mat'][0], $feedback);
579 }
580 $feedbacks[] = $feedback;
581 }
582}
583
e53ff7f6 584/**
585 * Create common parts of question
586 */
587function process_common( $quest ) {
588 $question = $this->defaultquestion();
294ce987 589 $question->questiontext = $quest->QUESTION_BLOCK->text;
e53ff7f6 590 $question->name = shorten_text( $quest->id, 250 );
591
592 return $question;
593}
594
03f5a0f8 595//----------------------------------------
596// Process True / False Questions
597//----------------------------------------
598function process_tf($quest, &$questions) {
e53ff7f6 599 $question = $this->process_common( $quest );
03f5a0f8 600
601 $question->qtype = TRUEFALSE;
03f5a0f8 602 $question->single = 1; // Only one answer is allowed
1b36a585 603 // 0th [response] is the correct answer.
604 $responses = $quest->responses;
605 $correctresponse = $responses[0]->ident[0]['varequal'][0]['#'];
606 if ($correctresponse != 'false') {
aeb15530 607 $correct = true;
03f5a0f8 608 }
609 else {
aeb15530 610 $correct = false;
03f5a0f8 611 }
aeb15530 612
03f5a0f8 613 foreach($quest->feedback as $fb) {
aeb15530 614 $fback->{$fb->ident} = $fb->text;
03f5a0f8 615 }
aeb15530 616
03f5a0f8 617 if ($correct) { // true is correct
618 $question->answer = 1;
294ce987 619 $question->feedbacktrue = $fback->correct;
620 $question->feedbackfalse = $fback->incorrect;
03f5a0f8 621 } else { // false is correct
622 $question->answer = 0;
294ce987 623 $question->feedbacktrue = $fback->incorrect;
624 $question->feedbackfalse = $fback->correct;
03f5a0f8 625 }
d022bf65 626 $question->correctanswer = $question->answer;
03f5a0f8 627 $questions[] = $question;
628}
629
630
631//----------------------------------------
632// Process Fill in the Blank
633//----------------------------------------
634function process_fblank($quest, &$questions) {
e53ff7f6 635 $question = $this->process_common( $quest );
03f5a0f8 636 $question->qtype = SHORTANSWER;
03f5a0f8 637 $question->single = 1;
e53ff7f6 638
03f5a0f8 639 $answers = array();
640 $fractions = array();
641 $feedbacks = array();
aeb15530 642
03f5a0f8 643 // extract the feedback
644 $feedback = array();
645 foreach($quest->feedback as $fback) {
646 if (isset($fback->ident)) {
647 if ($fback->ident == 'correct' || $fback->ident == 'incorrect') {
648 $feedback[$fback->ident] = $fback->text;
649 }
650 }
651 }
aeb15530 652
03f5a0f8 653 foreach($quest->responses as $response) {
654 if(isset($response->title)) {
655 if (isset($response->ident[0]['varequal'][0]['#'])) {
656 //for BB Fill in the Blank, only interested in correct answers
657 if ($response->feedback = 'correct') {
294ce987 658 $answers[] = $response->ident[0]['varequal'][0]['#'];
03f5a0f8 659 $fractions[] = 1;
660 if (isset($feedback['correct'])) {
294ce987 661 $feedbacks[] = $feedback['correct'];
03f5a0f8 662 }
663 else {
664 $feedbacks[] = '';
665 }
666 }
667 }
aeb15530 668
03f5a0f8 669 }
670 }
671
aeb15530 672 //Adding catchall to so that students can see feedback for incorrect answers when they enter something the
03f5a0f8 673 //instructor did not enter
674 $answers[] = '*';
675 $fractions[] = 0;
676 if (isset($feedback['incorrect'])) {
294ce987 677 $feedbacks[] = $feedback['incorrect'];
03f5a0f8 678 }
679 else {
680 $feedbacks[] = '';
681 }
aeb15530 682
03f5a0f8 683 $question->answer = $answers;
684 $question->fraction = $fractions;
685 $question->feedback = $feedbacks; // Changed to assign $feedbacks to $question->feedback instead of
686
687 if (!empty($question)) {
688 $questions[] = $question;
689 }
690
691}
692
693//----------------------------------------
694// Process Multiple Choice Questions
695//----------------------------------------
696function process_mc($quest, &$questions) {
e53ff7f6 697 $question = $this->process_common( $quest );
03f5a0f8 698 $question->qtype = MULTICHOICE;
03f5a0f8 699 $question->single = 1;
aeb15530 700
03f5a0f8 701 $feedback = array();
702 foreach($quest->feedback as $fback) {
294ce987 703 $feedback[$fback->ident] = $fback->text;
03f5a0f8 704 }
aeb15530 705
03f5a0f8 706 foreach($quest->responses as $response) {
707 if (isset($response->title)) {
708 if ($response->title == 'correct') {
709 // only one answer possible for this qtype so first index is correct answer
710 $correct = $response->ident[0]['varequal'][0]['#'];
711 }
712 }
713 else {
714 // fallback method for when the title is not set
715 if ($response->feedback == 'correct') {
716 // only one answer possible for this qtype so first index is correct answer
717 $correct = $response->ident[0]['varequal'][0]['#']; // added [0]['varequal'][0]['#'] to $response->ident - CT 8/9/06
718 }
719 }
720 }
721
722 $i = 0;
723 foreach($quest->RESPONSE_BLOCK->choices as $response) {
294ce987 724 $question->answer[$i] = $response->text;
03f5a0f8 725 if ($correct == $response->ident) {
726 $question->fraction[$i] = 1;
727 // this is a bit of a hack to catch the feedback... first we see if a 'correct' feedback exists
728 // then specific feedback for this question (maybe this should be switched?, but from my example
729 // question pools I have not seen response specific feedback, only correct or incorrect feedback
730 if (!empty($feedback['correct'])) {
731 $question->feedback[$i] = $feedback['correct'];
732 }
733 elseif (!empty($feedback[$i])) {
734 $question->feedback[$i] = $feedback[$i];
735 }
736 else {
737 // failsafe feedback (should be '' instead?)
aeb15530 738 $question->feedback[$i] = "correct";
03f5a0f8 739 }
aeb15530 740 }
03f5a0f8 741 else {
742 $question->fraction[$i] = 0;
743 if (!empty($feedback['incorrect'])) {
744 $question->feedback[$i] = $feedback['incorrect'];
745 }
746 elseif (!empty($feedback[$i])) {
747 $question->feedback[$i] = $feedback[$i];
748 }
749 else {
750 // failsafe feedback (should be '' instead?)
751 $question->feedback[$i] = 'incorrect';
752 }
753 }
754 $i++;
755 }
756
757 if (!empty($question)) {
758 $questions[] = $question;
759 }
760}
761
762//----------------------------------------
763// Process Multiple Choice Questions With Multiple Answers
764//----------------------------------------
765function process_ma($quest, &$questions) {
e53ff7f6 766 $question = $this->process_common( $quest ); // copied this from process_mc
03f5a0f8 767 $question->qtype = MULTICHOICE;
03f5a0f8 768 $question->single = 0; // More than one answer allowed
03f5a0f8 769
770 $answers = $quest->responses;
771 $correct_answers = array();
772 foreach($answers as $answer) {
773 if($answer->title == 'correct') {
774 $answerset = $answer->ident[0]['and'][0]['#']['varequal'];
775 foreach($answerset as $ans) {
776 $correct_answers[] = $ans['#'];
777 }
778 }
779 }
aeb15530 780
03f5a0f8 781 foreach ($quest->feedback as $fb) {
294ce987 782 $feedback->{$fb->ident} = trim($fb->text);
03f5a0f8 783 }
aeb15530 784
03f5a0f8 785 $correct_answer_count = count($correct_answers);
786 $choiceset = $quest->RESPONSE_BLOCK->choices;
787 $i = 0;
788 foreach($choiceset as $choice) {
294ce987 789 $question->answer[$i] = trim($choice->text);
03f5a0f8 790 if (in_array($choice->ident, $correct_answers)) {
791 // correct answer
792 $question->fraction[$i] = floor(100000/$correct_answer_count)/100000; // strange behavior if we have more than 5 decimal places
793 $question->feedback[$i] = $feedback->correct;
794 }
795 else {
aeb15530 796 // wrong answer
03f5a0f8 797 $question->fraction[$i] = 0;
798 $question->feedback[$i] = $feedback->incorrect;
799 }
800 $i++;
801 }
802
803 $questions[] = $question;
804}
805
806//----------------------------------------
807// Process Essay Questions
808//----------------------------------------
809function process_essay($quest, &$questions) {
810// this should be rewritten to accomodate moodle 1.6 essay question type eventually
811
812 if (defined("ESSAY")) {
813 // treat as short answer
e53ff7f6 814 $question = $this->process_common( $quest ); // copied this from process_mc
03f5a0f8 815 $question->qtype = ESSAY;
aeb15530 816
03f5a0f8 817 $question->feedback = array();
818 // not sure where to get the correct answer from
819 foreach($quest->feedback as $feedback) {
820 // Added this code to put the possible solution that the
821 // instructor gives as the Moodle answer for an essay question
822 if ($feedback->ident == 'solution') {
294ce987 823 $question->feedback = $feedback->text;
03f5a0f8 824 }
825 }
aeb15530 826 //Added because essay/questiontype.php:save_question_option is expecting a
03f5a0f8 827 //fraction property - CT 8/10/06
aeb15530 828 $question->fraction[] = 1;
03f5a0f8 829 if (!empty($question)) {
830 $questions[]=$question;
831 }
832 }
833 else {
834 print "Essay question types are not handled because the quiz question type 'Essay' does not exist in this installation of Moodle<br/>";
835 print "&nbsp;&nbsp;&nbsp;&nbsp;Omitted Question: ".$quest->QUESTION_BLOCK->text.'<br/><br/>';
836 }
837}
838
839//----------------------------------------
840// Process Matching Questions
841//----------------------------------------
842function process_matching($quest, &$questions) {
1d1e8f2b 843 // renderedmatch is an optional plugin, so we need to check if it is defined
d649fb02
TH
844 if (question_bank::is_qtype_installed('renderedmatch')) {
845 $question = $this->process_common($quest);
03f5a0f8 846 $question->valid = true;
1d1e8f2b 847 $question->qtype = 'renderedmatch';
aeb15530 848
03f5a0f8 849 foreach($quest->RESPONSE_BLOCK->subquestions as $qid => $subq) {
850 foreach($quest->responses as $rid => $resp) {
851 if ($resp->ident == $subq->ident) {
294ce987 852 $correct = $resp->correct;
aeb15530 853 $feedback = $resp->feedback;
03f5a0f8 854 }
855 }
aeb15530 856
03f5a0f8 857 foreach($subq->choices as $cid => $choice) {
858 if ($choice == $correct) {
294ce987 859 $question->subquestions[] = $subq->text;
860 $question->subanswers[] = $quest->RIGHT_MATCH_BLOCK->matching_answerset[$cid]->text;
03f5a0f8 861 }
862 }
863 }
aeb15530 864
03f5a0f8 865 // check format
866 $status = true;
867 if ( count($quest->RESPONSE_BLOCK->subquestions) > count($quest->RIGHT_MATCH_BLOCK->matching_answerset) || count($question->subquestions) < 2) {
868 $status = false;
869 }
870 else {
871 // need to redo to make sure that no two questions have the same answer (rudimentary now)
872 foreach($question->subanswers as $qstn) {
873 if(isset($previous)) {
874 if ($qstn == $previous) {
aeb15530
PS
875 $status = false;
876 }
03f5a0f8 877 }
878 $previous = $qstn;
879 if ($qstn == '') {
aeb15530 880 $status = false;
03f5a0f8 881 }
882 }
883 }
aeb15530 884
03f5a0f8 885 if ($status) {
aeb15530 886 $questions[] = $question;
03f5a0f8 887 }
888 else {
60f9e36e 889 global $COURSE, $CFG;
09275894 890 print '<table class="boxaligncenter" border="1">';
aeb15530
PS
891 print '<tr><td colspan="2" style="background-color:#FF8888;">This matching question is malformed. Please ensure there are no blank answers, no two questions have the same answer, and/or there are correct answers for each question. There must be at least as many subanswers as subquestions, and at least one subquestion.</td></tr>';
892
03f5a0f8 893 print "<tr><td>Question:</td><td>".$quest->QUESTION_BLOCK->text;
894 if (isset($quest->QUESTION_BLOCK->file)) {
895 print '<br/><font color="red">There is a subfile contained in the zipfile that has been copied to course files: bb_import/'.basename($quest->QUESTION_BLOCK->file).'</font>';
896 if (preg_match('/(gif|jpg|jpeg|png)$/i', $quest->QUESTION_BLOCK->file)) {
60f9e36e 897 print '<img src="'.$CFG->wwwroot.'/file.php/'.$COURSE->id.'/bb_import/'.basename($quest->QUESTION_BLOCK->file).'" />';
03f5a0f8 898 }
899 }
900 print "</td></tr>";
901 print "<tr><td>Subquestions:</td><td><ul>";
902 foreach($quest->responses as $rs) {
aeb15530 903 $correct_responses->{$rs->ident} = $rs->correct;
03f5a0f8 904 }
905 foreach($quest->RESPONSE_BLOCK->subquestions as $subq) {
906 print '<li>'.$subq->text.'<ul>';
907 foreach($subq->choices as $id=>$choice) {
908 print '<li>';
909 if ($choice == $correct_responses->{$subq->ident}) {
910 print '<font color="green">';
911 }
912 else {
913 print '<font color="red">';
914 }
915 print $quest->RIGHT_MATCH_BLOCK->matching_answerset[$id]->text.'</font></li>';
916 }
917 print '</ul>';
918 }
919 print '</ul></td></tr>';
aeb15530 920
03f5a0f8 921 print '<tr><td>Feedback:</td><td><ul>';
922 foreach($quest->feedback as $fb) {
923 print '<li>'.$fb->ident.': '.$fb->text.'</li>';
924 }
925 print '</ul></td></tr></table>';
926 }
927 }
928 else {
929 print "Matching question types are not handled because the quiz question type 'Rendered Matching' does not exist in this installation of Moodle<br/>";
930 print "&nbsp;&nbsp;&nbsp;&nbsp;Omitted Question: ".$quest->QUESTION_BLOCK->text.'<br/><br/>';
931 }
932}
933
934
935function strip_applet_tags_get_mathml($string) {
936 if(stristr($string, '</APPLET>') === FALSE) {
aeb15530 937 return $string;
03f5a0f8 938 }
939 else {
940 // strip all applet tags keeping stuff before/after and inbetween (if mathml) them
941 while (stristr($string, '</APPLET>') !== FALSE) {
942 preg_match("/(.*)\<applet.*value=\"(\<math\>.*\<\/math\>)\".*\<\/applet\>(.*)/i",$string, $mathmls);
943 $string = $mathmls[1].$mathmls[2].$mathmls[3];
944 }
aeb15530 945 return $string;
03f5a0f8 946 }
947}
948
949} // close object
aeb15530 950