3 ////////////////////////////////////////////////////////////////////////////
4 /// Blackboard 6.x Format
6 /// This Moodle class provides all functions necessary to import and export
9 ////////////////////////////////////////////////////////////////////////////
11 // Based on default.php, included by ../import.php
13 * @package questionbank
14 * @subpackage importexport
16 require_once ("$CFG->libdir/xmlize.php");
18 class qformat_blackboard_6
extends qformat_default
{
19 function provide_import() {
24 //Function to check and create the needed dir to unzip file to
25 function check_and_create_import_dir($unique_code) {
29 $status = $this->check_dir_exists($CFG->dataroot
."/temp",true);
31 $status = $this->check_dir_exists($CFG->dataroot
."/temp/bbquiz_import",true);
34 $status = $this->check_dir_exists($CFG->dataroot
."/temp/bbquiz_import/".$unique_code,true);
40 function clean_temp_dir($dir='') {
41 // for now we will just say everything happened okay note
42 // that a mess may be piling up in $CFG->dataroot/temp/bbquiz_import
46 $dir = $this->temp_dir
;
50 // Create arrays to store files and directories
52 $dir_subdirs = array();
54 // Make sure we can delete it
57 if ((($handle = opendir($dir))) == FALSE) {
58 // The directory could not be opened
62 // Loop through all directory entries, and construct two temporary arrays containing files and sub directories
63 while($entry = readdir($handle)) {
64 if (is_dir($dir. $slash .$entry) && $entry != ".." && $entry != ".") {
65 $dir_subdirs[] = $dir. $slash .$entry;
67 else if ($entry != ".." && $entry != ".") {
68 $dir_files[] = $dir. $slash .$entry;
72 // Delete all files in the curent directory return false and halt if a file cannot be removed
73 for($i=0; $i<count($dir_files); $i++
) {
74 chmod($dir_files[$i], 0777);
75 if (((unlink($dir_files[$i]))) == FALSE) {
80 // Empty sub directories and then remove the directory
81 for($i=0; $i<count($dir_subdirs); $i++
) {
82 chmod($dir_subdirs[$i], 0777);
83 if ($this->clean_temp_dir($dir_subdirs[$i]) == FALSE) {
87 if (rmdir($dir_subdirs[$i]) == FALSE) {
95 if (rmdir($this->temp_dir
) == FALSE) {
98 // Success, every thing is gone return true
102 //Function to check if a directory exists and, optionally, create it
103 function check_dir_exists($dir,$create=false) {
113 $status = mkdir ($dir,$CFG->directorypermissions
);
119 function importpostprocess() {
120 /// Does any post-processing that may be desired
121 /// Argument is a simple array of question ids that
122 /// have just been added.
124 // need to clean up temporary directory
125 return $this->clean_temp_dir();
128 function copy_file_to_course($filename) {
129 global $CFG, $COURSE;
130 $filename = str_replace('\\','/',$filename);
131 $fullpath = $this->temp_dir
.'/res00001/'.$filename;
132 $basename = basename($filename);
134 $copy_to = $CFG->dataroot
.'/'.$COURSE->id
.'/bb_import';
136 if ($this->check_dir_exists($copy_to,true)) {
137 if(is_readable($fullpath)) {
138 $copy_to.= '/'.$basename;
139 if (!copy($fullpath, $copy_to)) {
152 function readdata($filename) {
153 /// Returns complete file with an array, one item per line
156 $unique_code = time();
157 $temp_dir = $CFG->dataroot
."/temp/bbquiz_import/".$unique_code;
158 $this->temp_dir
= $temp_dir;
159 if ($this->check_and_create_import_dir($unique_code)) {
160 if(is_readable($filename)) {
161 if (!copy($filename, "$temp_dir/bboard.zip")) {
162 error("Could not copy backup file");
164 if(unzip_file("$temp_dir/bboard.zip", '', false)) {
165 // assuming that the information is in res0001.dat
166 // after looking at 6 examples this was always the case
167 $q_file = "$temp_dir/res00001.dat";
168 if (is_file($q_file)) {
169 if (is_readable($q_file)) {
170 $filearray = file($q_file);
171 /// Check for Macintosh OS line returns (ie file on one line), and fix
172 if (ereg("\r", $filearray[0]) AND !ereg("\n", $filearray[0])) {
173 return explode("\r", $filearray[0]);
181 error("Could not find question data file in zip");
185 print "filename: $filename<br />tempdir: $temp_dir <br />";
186 error("Could not unzip file.");
190 error ("Could not read uploaded file");
194 error("Could not create temporary directory");
198 function save_question_options($question) {
204 function readquestions ($lines) {
205 /// Parses an array of lines into an array of questions,
206 /// where each item is a question object as defined by
209 $text = implode($lines, " ");
210 $xml = xmlize($text, 0);
212 $raw_questions = $xml['questestinterop']['#']['assessment'][0]['#']['section'][0]['#']['item'];
213 $questions = array();
215 foreach($raw_questions as $quest) {
216 $question = $this->create_raw_question($quest);
218 switch($question->qtype
) {
220 $this->process_matching($question, $questions);
222 case "Multiple Choice":
223 $this->process_mc($question, $questions);
226 $this->process_essay($question, $questions);
228 case "Multiple Answer":
229 $this->process_ma($question, $questions);
232 $this->process_tf($question, $questions);
234 case 'Fill in the Blank':
235 $this->process_fblank($question, $questions);
237 case 'Short Response':
238 $this->process_essay($question, $questions);
241 print "Unknown or unhandled question type: \"$question->qtype\"<br />";
250 // creates a cleaner object to deal with for processing into moodle
251 // the object created is NOT a moodle question object
252 function create_raw_question($quest) {
254 $question = new StdClass
;
255 $question->qtype
= $quest['#']['itemmetadata'][0]['#']['bbmd_questiontype'][0]['#'];
256 $question->id
= $quest['#']['itemmetadata'][0]['#']['bbmd_asi_object_id'][0]['#'];
257 $presentation->blocks
= $quest['#']['presentation'][0]['#']['flow'][0]['#']['flow'];
259 foreach($presentation->blocks
as $pblock) {
262 $block->type
= $pblock['@']['class'];
264 switch($block->type
) {
265 case 'QUESTION_BLOCK':
266 $sub_blocks = $pblock['#']['flow'];
267 foreach($sub_blocks as $sblock) {
268 //echo "Calling process_block from line 263<br>";
269 $this->process_block($sblock, $block);
273 case 'RESPONSE_BLOCK':
275 switch($question->qtype
) {
277 $bb_subquestions = $pblock['#']['flow'];
278 $sub_questions = array();
279 foreach($bb_subquestions as $bb_subquestion) {
280 $sub_question = NULL;
281 $sub_question->ident
= $bb_subquestion['#']['response_lid'][0]['@']['ident'];
282 $this->process_block($bb_subquestion['#']['flow'][0], $sub_question);
283 $bb_choices = $bb_subquestion['#']['response_lid'][0]['#']['render_choice'][0]['#']['flow_label'][0]['#']['response_label'];
285 $this->process_choices($bb_choices, $choices);
286 $sub_question->choices
= $choices;
287 if (!isset($block->subquestions
)) {
288 $block->subquestions
= array();
290 $block->subquestions
[] = $sub_question;
293 case 'Multiple Answer':
294 $bb_choices = $pblock['#']['response_lid'][0]['#']['render_choice'][0]['#']['flow_label'];
296 $this->process_choices($bb_choices, $choices);
297 $block->choices
= $choices;
300 // Doesn't apply since the user responds with text input
302 case 'Multiple Choice':
303 $mc_choices = $pblock['#']['response_lid'][0]['#']['render_choice'][0]['#']['flow_label'];
304 foreach($mc_choices as $mc_choice) {
306 $choices = $this->process_block($mc_choice, $choices);
307 $block->choices
[] = $choices;
310 case 'Short Response':
313 case 'Fill in the Blank':
317 $bb_choices = $pblock['#']['response_lid'][0]['#']['render_choice'][0]['#']['flow_label'][0]['#']['response_label'];
319 $this->process_choices($bb_choices, $choices);
320 $block->choices
= $choices;
323 case 'RIGHT_MATCH_BLOCK':
324 $matching_answerset = $pblock['#']['flow'];
326 $answerset = array();
327 foreach($matching_answerset as $answer) {
328 // $answerset[] = $this->process_block($answer, $bb_answer);
330 $bb_answer->text
= $answer['#']['flow'][0]['#']['material'][0]['#']['mat_extension'][0]['#']['mat_formattedtext'][0]['#'];
331 $answerset[] = $bb_answer;
333 $block->matching_answerset
= $answerset;
336 print "UNHANDLED PRESENTATION BLOCK";
339 $question->{$block->type
} = $block;
342 // determine response processing
343 // there is a section called 'outcomes' that I don't know what to do with
344 $resprocessing = $quest['#']['resprocessing'];
345 $respconditions = $resprocessing[0]['#']['respcondition'];
347 if ($question->qtype
== 'Matching') {
348 $this->process_matching_responses($respconditions, $responses);
351 $this->process_responses($respconditions, $responses);
353 $question->responses
= $responses;
354 $feedbackset = $quest['#']['itemfeedback'];
355 $feedbacks = array();
356 $this->process_feedback($feedbackset, $feedbacks);
357 $question->feedback
= $feedbacks;
361 function process_block($cur_block, &$block) {
362 global $COURSE, $CFG;
364 $cur_type = $cur_block['@']['class'];
366 case 'FORMATTED_TEXT_BLOCK':
367 $block->text
= $this->strip_applet_tags_get_mathml($cur_block['#']['material'][0]['#']['mat_extension'][0]['#']['mat_formattedtext'][0]['#']);
370 //revisit this to make sure it is working correctly
371 // Commented out ['matapplication']..., etc. because I
372 // noticed that when I imported a new Blackboard 6 file
373 // and printed out the block, the tree did not extend past ['material'][0]['#'] - CT 8/3/06
374 $block->file
= $cur_block['#']['material'][0]['#'];//['matapplication'][0]['@']['uri'];
375 if ($block->file
!= '') {
376 // if we have a file copy it to the course dir and adjust its name to be visible over the web.
377 $block->file
= $this->copy_file_to_course($block->file
);
378 $block->file
= $CFG->wwwroot
.'/file.php/'.$COURSE->id
.'/bb_import/'.basename($block->file
);
382 if (isset($cur_block['#']['material'][0]['#']['mattext'][0]['#'])) {
383 $block->text
= $cur_block['#']['material'][0]['#']['mattext'][0]['#'];
385 else if (isset($cur_block['#']['material'][0]['#']['mat_extension'][0]['#']['mat_formattedtext'][0]['#'])) {
386 $block->text
= $cur_block['#']['material'][0]['#']['mat_extension'][0]['#']['mat_formattedtext'][0]['#'];
388 else if (isset($cur_block['#']['response_label'])) {
389 // this is a response label block
390 $sub_blocks = $cur_block['#']['response_label'][0];
391 if(!isset($block->ident
)) {
392 if(isset($sub_blocks['@']['ident'])) {
393 $block->ident
= $sub_blocks['@']['ident'];
396 foreach($sub_blocks['#']['flow_mat'] as $sub_block) {
397 $this->process_block($sub_block, $block);
401 if (isset($cur_block['#']['flow_mat']) ||
isset($cur_block['#']['flow'])) {
402 if (isset($cur_block['#']['flow_mat'])) {
403 $sub_blocks = $cur_block['#']['flow_mat'];
405 elseif (isset($cur_block['#']['flow'])) {
406 $sub_blocks = $cur_block['#']['flow'];
408 foreach ($sub_blocks as $sblock) {
409 // this will recursively grab the sub blocks which should be of one of the other types
410 $this->process_block($sblock, $block);
416 // not sure how this should be included
417 if (!empty($cur_block['#']['material'][0]['#']['mattext'][0]['@']['uri'])) {
418 $block->link
= $cur_block['#']['material'][0]['#']['mattext'][0]['@']['uri'];
428 function process_choices($bb_choices, &$choices) {
429 foreach($bb_choices as $choice) {
430 if (isset($choice['@']['ident'])) {
431 $cur_choice = $choice['@']['ident'];
433 else { //for multiple answer
434 $cur_choice = $choice['#']['response_label'][0];//['@']['ident'];
436 if (isset($choice['#']['flow_mat'][0])) { //for multiple answer
437 $cur_block = $choice['#']['flow_mat'][0];
438 // Reset $cur_choice to NULL because process_block is expecting an object
439 // for the second argument and not a string, which is what is was set as
440 // originally - CT 8/7/06
442 $this->process_block($cur_block, $cur_choice);
444 elseif (isset($choice['#']['response_label'])) {
445 // Reset $cur_choice to NULL because process_block is expecting an object
446 // for the second argument and not a string, which is what is was set as
447 // originally - CT 8/7/06
449 $this->process_block($choice, $cur_choice);
451 $choices[] = $cur_choice;
455 function process_matching_responses($bb_responses, &$responses) {
456 foreach($bb_responses as $bb_response) {
458 if (isset($bb_response['#']['conditionvar'][0]['#']['varequal'])) {
459 $response->correct
= $bb_response['#']['conditionvar'][0]['#']['varequal'][0]['#'];
460 $response->ident
= $bb_response['#']['conditionvar'][0]['#']['varequal'][0]['@']['respident'];
463 $response->correct
= 'Broken Question?';
464 $response->ident
= 'Broken Question?';
466 $response->feedback
= $bb_response['#']['displayfeedback'][0]['@']['linkrefid'];
467 $responses[] = $response;
471 function process_responses($bb_responses, &$responses) {
472 foreach($bb_responses as $bb_response) {
473 //Added this line to instantiate $response.
474 // Without instantiating the $response variable, the same object
475 // gets added to the array
477 if (isset($bb_response['@']['title'])) {
478 $response->title
= $bb_response['@']['title'];
481 $reponse->title
= $bb_response['#']['displayfeedback'][0]['@']['linkrefid'];
483 $reponse->ident
= array();
484 if (isset($bb_response['#']['conditionvar'][0]['#'])){//['varequal'][0]['#'])) {
485 $response->ident
[0] = $bb_response['#']['conditionvar'][0]['#'];//['varequal'][0]['#'];
487 else if (isset($bb_response['#']['conditionvar'][0]['#']['other'][0]['#'])) {
488 $response->ident
[0] = $bb_response['#']['conditionvar'][0]['#']['other'][0]['#'];
491 if (isset($bb_response['#']['conditionvar'][0]['#']['and'])){//[0]['#'])) {
492 $responseset = $bb_response['#']['conditionvar'][0]['#']['and'];//[0]['#']['varequal'];
493 foreach($responseset as $rs) {
494 $response->ident
[] = $rs['#'];
495 if(!isset($response->feedback
) and isset( $rs['@'] ) ) {
496 $response->feedback
= $rs['@']['respident'];
501 $response->feedback
= $bb_response['#']['displayfeedback'][0]['@']['linkrefid'];
504 // determine what point value to give response
505 if (isset($bb_response['#']['setvar'])) {
506 switch ($bb_response['#']['setvar'][0]['#']) {
508 $response->fraction
= 1;
511 // I have only seen this being 0 or unset
512 // there are probably fractional values of SCORE.max, but I'm not sure what they look like
513 $response->fraction
= 0;
518 // just going to assume this is the case this is probably not correct.
519 $response->fraction
= 0;
522 $responses[] = $response;
526 function process_feedback($feedbackset, &$feedbacks) {
527 foreach($feedbackset as $bb_feedback) {
528 // Added line $feedback=null so that $feedback does not get reused in the loop
529 // and added the the $feedbacks[] array multiple times
531 $feedback->ident
= $bb_feedback['@']['ident'];
532 if (isset($bb_feedback['#']['flow_mat'][0])) {
533 $this->process_block($bb_feedback['#']['flow_mat'][0], $feedback);
535 elseif (isset($bb_feedback['#']['solution'][0]['#']['solutionmaterial'][0]['#']['flow_mat'][0])) {
536 $this->process_block($bb_feedback['#']['solution'][0]['#']['solutionmaterial'][0]['#']['flow_mat'][0], $feedback);
538 $feedbacks[] = $feedback;
543 * Create common parts of question
545 function process_common( $quest ) {
546 $question = $this->defaultquestion();
547 $question->questiontext
= addslashes($quest->QUESTION_BLOCK
->text
);
548 $question->name
= shorten_text( $quest->id
, 250 );
553 //----------------------------------------
554 // Process True / False Questions
555 //----------------------------------------
556 function process_tf($quest, &$questions) {
557 $question = $this->process_common( $quest );
559 $question->qtype
= TRUEFALSE
;
560 $question->single
= 1; // Only one answer is allowed
562 // first choice is true, second is false.
563 if ($quest->responses
[0]->fraction
== 1) {
570 foreach($quest->feedback
as $fb) {
571 $fback->{$fb->ident
} = $fb->text
;
574 if ($correct) { // true is correct
575 $question->answer
= 1;
576 $question->feedbacktrue
= addslashes($fback->correct
);
577 $question->feedbackfalse
= addslashes($fback->incorrect
);
578 } else { // false is correct
579 $question->answer
= 0;
580 $question->feedbacktrue
= addslashes($fback->incorrect
);
581 $question->feedbackfalse
= addslashes($fback->correct
);
583 $question->correctanswer
= $question->answer
;
584 $questions[] = $question;
588 //----------------------------------------
589 // Process Fill in the Blank
590 //----------------------------------------
591 function process_fblank($quest, &$questions) {
592 $question = $this->process_common( $quest );
593 $question->qtype
= SHORTANSWER
;
594 $question->single
= 1;
597 $fractions = array();
598 $feedbacks = array();
600 // extract the feedback
602 foreach($quest->feedback
as $fback) {
603 if (isset($fback->ident
)) {
604 if ($fback->ident
== 'correct' ||
$fback->ident
== 'incorrect') {
605 $feedback[$fback->ident
] = $fback->text
;
610 foreach($quest->responses
as $response) {
611 if(isset($response->title
)) {
612 if (isset($response->ident
[0]['varequal'][0]['#'])) {
613 //for BB Fill in the Blank, only interested in correct answers
614 if ($response->feedback
= 'correct') {
615 $answers[] = addslashes($response->ident
[0]['varequal'][0]['#']);
617 if (isset($feedback['correct'])) {
618 $feedbacks[] = addslashes($feedback['correct']);
629 //Adding catchall to so that students can see feedback for incorrect answers when they enter something the
630 //instructor did not enter
633 if (isset($feedback['incorrect'])) {
634 $feedbacks[] = addslashes($feedback['incorrect']);
640 $question->answer
= $answers;
641 $question->fraction
= $fractions;
642 $question->feedback
= $feedbacks; // Changed to assign $feedbacks to $question->feedback instead of
644 if (!empty($question)) {
645 $questions[] = $question;
650 //----------------------------------------
651 // Process Multiple Choice Questions
652 //----------------------------------------
653 function process_mc($quest, &$questions) {
654 $question = $this->process_common( $quest );
655 $question->qtype
= MULTICHOICE
;
656 $question->single
= 1;
659 foreach($quest->feedback
as $fback) {
660 $feedback[$fback->ident
] = addslashes($fback->text
);
663 foreach($quest->responses
as $response) {
664 if (isset($response->title
)) {
665 if ($response->title
== 'correct') {
666 // only one answer possible for this qtype so first index is correct answer
667 $correct = $response->ident
[0]['varequal'][0]['#'];
671 // fallback method for when the title is not set
672 if ($response->feedback
== 'correct') {
673 // only one answer possible for this qtype so first index is correct answer
674 $correct = $response->ident
[0]['varequal'][0]['#']; // added [0]['varequal'][0]['#'] to $response->ident - CT 8/9/06
680 foreach($quest->RESPONSE_BLOCK
->choices
as $response) {
681 $question->answer
[$i] = addslashes($response->text
);
682 if ($correct == $response->ident
) {
683 $question->fraction
[$i] = 1;
684 // this is a bit of a hack to catch the feedback... first we see if a 'correct' feedback exists
685 // then specific feedback for this question (maybe this should be switched?, but from my example
686 // question pools I have not seen response specific feedback, only correct or incorrect feedback
687 if (!empty($feedback['correct'])) {
688 $question->feedback
[$i] = $feedback['correct'];
690 elseif (!empty($feedback[$i])) {
691 $question->feedback
[$i] = $feedback[$i];
694 // failsafe feedback (should be '' instead?)
695 $question->feedback
[$i] = "correct";
699 $question->fraction
[$i] = 0;
700 if (!empty($feedback['incorrect'])) {
701 $question->feedback
[$i] = $feedback['incorrect'];
703 elseif (!empty($feedback[$i])) {
704 $question->feedback
[$i] = $feedback[$i];
707 // failsafe feedback (should be '' instead?)
708 $question->feedback
[$i] = 'incorrect';
714 if (!empty($question)) {
715 $questions[] = $question;
719 //----------------------------------------
720 // Process Multiple Choice Questions With Multiple Answers
721 //----------------------------------------
722 function process_ma($quest, &$questions) {
723 $question = $this->process_common( $quest ); // copied this from process_mc
724 $question->qtype
= MULTICHOICE
;
725 $question->single
= 0; // More than one answer allowed
727 $answers = $quest->responses
;
728 $correct_answers = array();
729 foreach($answers as $answer) {
730 if($answer->title
== 'correct') {
731 $answerset = $answer->ident
[0]['and'][0]['#']['varequal'];
732 foreach($answerset as $ans) {
733 $correct_answers[] = $ans['#'];
738 foreach ($quest->feedback
as $fb) {
739 $feedback->{$fb->ident
} = addslashes(trim($fb->text
));
742 $correct_answer_count = count($correct_answers);
743 $choiceset = $quest->RESPONSE_BLOCK
->choices
;
745 foreach($choiceset as $choice) {
746 $question->answer
[$i] = addslashes(trim($choice->text
));
747 if (in_array($choice->ident
, $correct_answers)) {
749 $question->fraction
[$i] = floor(100000/$correct_answer_count)/100000; // strange behavior if we have more than 5 decimal places
750 $question->feedback
[$i] = $feedback->correct
;
754 $question->fraction
[$i] = 0;
755 $question->feedback
[$i] = $feedback->incorrect
;
760 $questions[] = $question;
763 //----------------------------------------
764 // Process Essay Questions
765 //----------------------------------------
766 function process_essay($quest, &$questions) {
767 // this should be rewritten to accomodate moodle 1.6 essay question type eventually
769 if (defined("ESSAY")) {
770 // treat as short answer
771 $question = $this->process_common( $quest ); // copied this from process_mc
772 $question->qtype
= ESSAY
;
774 $question->feedback
= array();
775 // not sure where to get the correct answer from
776 foreach($quest->feedback
as $feedback) {
777 // Added this code to put the possible solution that the
778 // instructor gives as the Moodle answer for an essay question
779 if ($feedback->ident
== 'solution') {
780 $question->feedback
= addslashes($feedback->text
);
783 //Added because essay/questiontype.php:save_question_option is expecting a
784 //fraction property - CT 8/10/06
785 $question->fraction
[] = 1;
786 if (!empty($question)) {
787 $questions[]=$question;
791 print "Essay question types are not handled because the quiz question type 'Essay' does not exist in this installation of Moodle<br/>";
792 print " Omitted Question: ".$quest->QUESTION_BLOCK
->text
.'<br/><br/>';
796 //----------------------------------------
797 // Process Matching Questions
798 //----------------------------------------
799 function process_matching($quest, &$questions) {
802 // renderedmatch is an optional plugin, so we need to check if it is defined
803 if (array_key_exists('renderedmatch', $QTYPES)) {
804 $question = $this->process_common( $quest );
805 $question->valid
= true;
806 $question->qtype
= 'renderedmatch';
808 foreach($quest->RESPONSE_BLOCK
->subquestions
as $qid => $subq) {
809 foreach($quest->responses
as $rid => $resp) {
810 if ($resp->ident
== $subq->ident
) {
811 $correct = addslashes($resp->correct
);
812 $feedback = addslashes($resp->feedback
);
816 foreach($subq->choices
as $cid => $choice) {
817 if ($choice == $correct) {
818 $question->subquestions
[] = addslashes($subq->text
);
819 $question->subanswers
[] = addslashes($quest->RIGHT_MATCH_BLOCK
->matching_answerset
[$cid]->text
);
826 if ( count($quest->RESPONSE_BLOCK
->subquestions
) > count($quest->RIGHT_MATCH_BLOCK
->matching_answerset
) ||
count($question->subquestions
) < 2) {
830 // need to redo to make sure that no two questions have the same answer (rudimentary now)
831 foreach($question->subanswers
as $qstn) {
832 if(isset($previous)) {
833 if ($qstn == $previous) {
845 $questions[] = $question;
848 global $COURSE, $CFG;
849 print '<table class="boxaligncenter" border="1">';
850 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>';
852 print "<tr><td>Question:</td><td>".$quest->QUESTION_BLOCK
->text
;
853 if (isset($quest->QUESTION_BLOCK
->file
)) {
854 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>';
855 if (preg_match('/(gif|jpg|jpeg|png)$/i', $quest->QUESTION_BLOCK
->file
)) {
856 print '<img src="'.$CFG->wwwroot
.'/file.php/'.$COURSE->id
.'/bb_import/'.basename($quest->QUESTION_BLOCK
->file
).'" />';
860 print "<tr><td>Subquestions:</td><td><ul>";
861 foreach($quest->responses
as $rs) {
862 $correct_responses->{$rs->ident
} = $rs->correct
;
864 foreach($quest->RESPONSE_BLOCK
->subquestions
as $subq) {
865 print '<li>'.$subq->text
.'<ul>';
866 foreach($subq->choices
as $id=>$choice) {
868 if ($choice == $correct_responses->{$subq->ident
}) {
869 print '<font color="green">';
872 print '<font color="red">';
874 print $quest->RIGHT_MATCH_BLOCK
->matching_answerset
[$id]->text
.'</font></li>';
878 print '</ul></td></tr>';
880 print '<tr><td>Feedback:</td><td><ul>';
881 foreach($quest->feedback
as $fb) {
882 print '<li>'.$fb->ident
.': '.$fb->text
.'</li>';
884 print '</ul></td></tr></table>';
888 print "Matching question types are not handled because the quiz question type 'Rendered Matching' does not exist in this installation of Moodle<br/>";
889 print " Omitted Question: ".$quest->QUESTION_BLOCK
->text
.'<br/><br/>';
894 function strip_applet_tags_get_mathml($string) {
895 if(stristr($string, '</APPLET>') === FALSE) {
899 // strip all applet tags keeping stuff before/after and inbetween (if mathml) them
900 while (stristr($string, '</APPLET>') !== FALSE) {
901 preg_match("/(.*)\<applet.*value=\"(\<math\>.*\<\/math\>)\".*\<\/applet\>(.*)/i",$string, $mathmls);
902 $string = $mathmls[1].$mathmls[2].$mathmls[3];