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(false !== ($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
561 // 0th [response] is the correct answer.
562 $responses = $quest->responses
;
563 $correctresponse = $responses[0]->ident
[0]['varequal'][0]['#'];
564 if ($correctresponse != 'false') {
571 foreach($quest->feedback
as $fb) {
572 $fback->{$fb->ident
} = $fb->text
;
575 if ($correct) { // true is correct
576 $question->answer
= 1;
577 $question->feedbacktrue
= addslashes($fback->correct
);
578 $question->feedbackfalse
= addslashes($fback->incorrect
);
579 } else { // false is correct
580 $question->answer
= 0;
581 $question->feedbacktrue
= addslashes($fback->incorrect
);
582 $question->feedbackfalse
= addslashes($fback->correct
);
584 $question->correctanswer
= $question->answer
;
585 $questions[] = $question;
589 //----------------------------------------
590 // Process Fill in the Blank
591 //----------------------------------------
592 function process_fblank($quest, &$questions) {
593 $question = $this->process_common( $quest );
594 $question->qtype
= SHORTANSWER
;
595 $question->single
= 1;
598 $fractions = array();
599 $feedbacks = array();
601 // extract the feedback
603 foreach($quest->feedback
as $fback) {
604 if (isset($fback->ident
)) {
605 if ($fback->ident
== 'correct' ||
$fback->ident
== 'incorrect') {
606 $feedback[$fback->ident
] = $fback->text
;
611 foreach($quest->responses
as $response) {
612 if(isset($response->title
)) {
613 if (isset($response->ident
[0]['varequal'][0]['#'])) {
614 //for BB Fill in the Blank, only interested in correct answers
615 if ($response->feedback
= 'correct') {
616 $answers[] = addslashes($response->ident
[0]['varequal'][0]['#']);
618 if (isset($feedback['correct'])) {
619 $feedbacks[] = addslashes($feedback['correct']);
630 //Adding catchall to so that students can see feedback for incorrect answers when they enter something the
631 //instructor did not enter
634 if (isset($feedback['incorrect'])) {
635 $feedbacks[] = addslashes($feedback['incorrect']);
641 $question->answer
= $answers;
642 $question->fraction
= $fractions;
643 $question->feedback
= $feedbacks; // Changed to assign $feedbacks to $question->feedback instead of
645 if (!empty($question)) {
646 $questions[] = $question;
651 //----------------------------------------
652 // Process Multiple Choice Questions
653 //----------------------------------------
654 function process_mc($quest, &$questions) {
655 $question = $this->process_common( $quest );
656 $question->qtype
= MULTICHOICE
;
657 $question->single
= 1;
660 foreach($quest->feedback
as $fback) {
661 $feedback[$fback->ident
] = addslashes($fback->text
);
664 foreach($quest->responses
as $response) {
665 if (isset($response->title
)) {
666 if ($response->title
== 'correct') {
667 // only one answer possible for this qtype so first index is correct answer
668 $correct = $response->ident
[0]['varequal'][0]['#'];
672 // fallback method for when the title is not set
673 if ($response->feedback
== 'correct') {
674 // only one answer possible for this qtype so first index is correct answer
675 $correct = $response->ident
[0]['varequal'][0]['#']; // added [0]['varequal'][0]['#'] to $response->ident - CT 8/9/06
681 foreach($quest->RESPONSE_BLOCK
->choices
as $response) {
682 $question->answer
[$i] = addslashes($response->text
);
683 if ($correct == $response->ident
) {
684 $question->fraction
[$i] = 1;
685 // this is a bit of a hack to catch the feedback... first we see if a 'correct' feedback exists
686 // then specific feedback for this question (maybe this should be switched?, but from my example
687 // question pools I have not seen response specific feedback, only correct or incorrect feedback
688 if (!empty($feedback['correct'])) {
689 $question->feedback
[$i] = $feedback['correct'];
691 elseif (!empty($feedback[$i])) {
692 $question->feedback
[$i] = $feedback[$i];
695 // failsafe feedback (should be '' instead?)
696 $question->feedback
[$i] = "correct";
700 $question->fraction
[$i] = 0;
701 if (!empty($feedback['incorrect'])) {
702 $question->feedback
[$i] = $feedback['incorrect'];
704 elseif (!empty($feedback[$i])) {
705 $question->feedback
[$i] = $feedback[$i];
708 // failsafe feedback (should be '' instead?)
709 $question->feedback
[$i] = 'incorrect';
715 if (!empty($question)) {
716 $questions[] = $question;
720 //----------------------------------------
721 // Process Multiple Choice Questions With Multiple Answers
722 //----------------------------------------
723 function process_ma($quest, &$questions) {
724 $question = $this->process_common( $quest ); // copied this from process_mc
725 $question->qtype
= MULTICHOICE
;
726 $question->single
= 0; // More than one answer allowed
728 $answers = $quest->responses
;
729 $correct_answers = array();
730 foreach($answers as $answer) {
731 if($answer->title
== 'correct') {
732 $answerset = $answer->ident
[0]['and'][0]['#']['varequal'];
733 foreach($answerset as $ans) {
734 $correct_answers[] = $ans['#'];
739 foreach ($quest->feedback
as $fb) {
740 $feedback->{$fb->ident
} = addslashes(trim($fb->text
));
743 $correct_answer_count = count($correct_answers);
744 $choiceset = $quest->RESPONSE_BLOCK
->choices
;
746 foreach($choiceset as $choice) {
747 $question->answer
[$i] = addslashes(trim($choice->text
));
748 if (in_array($choice->ident
, $correct_answers)) {
750 $question->fraction
[$i] = floor(100000/$correct_answer_count)/100000; // strange behavior if we have more than 5 decimal places
751 $question->feedback
[$i] = $feedback->correct
;
755 $question->fraction
[$i] = 0;
756 $question->feedback
[$i] = $feedback->incorrect
;
761 $questions[] = $question;
764 //----------------------------------------
765 // Process Essay Questions
766 //----------------------------------------
767 function process_essay($quest, &$questions) {
768 // this should be rewritten to accomodate moodle 1.6 essay question type eventually
770 if (defined("ESSAY")) {
771 // treat as short answer
772 $question = $this->process_common( $quest ); // copied this from process_mc
773 $question->qtype
= ESSAY
;
775 $question->feedback
= array();
776 // not sure where to get the correct answer from
777 foreach($quest->feedback
as $feedback) {
778 // Added this code to put the possible solution that the
779 // instructor gives as the Moodle answer for an essay question
780 if ($feedback->ident
== 'solution') {
781 $question->feedback
= addslashes($feedback->text
);
784 //Added because essay/questiontype.php:save_question_option is expecting a
785 //fraction property - CT 8/10/06
786 $question->fraction
[] = 1;
787 if (!empty($question)) {
788 $questions[]=$question;
792 print "Essay question types are not handled because the quiz question type 'Essay' does not exist in this installation of Moodle<br/>";
793 print " Omitted Question: ".$quest->QUESTION_BLOCK
->text
.'<br/><br/>';
797 //----------------------------------------
798 // Process Matching Questions
799 //----------------------------------------
800 function process_matching($quest, &$questions) {
803 // renderedmatch is an optional plugin, so we need to check if it is defined
804 if (array_key_exists('renderedmatch', $QTYPES)) {
805 $question = $this->process_common( $quest );
806 $question->valid
= true;
807 $question->qtype
= 'renderedmatch';
809 foreach($quest->RESPONSE_BLOCK
->subquestions
as $qid => $subq) {
810 foreach($quest->responses
as $rid => $resp) {
811 if ($resp->ident
== $subq->ident
) {
812 $correct = addslashes($resp->correct
);
813 $feedback = addslashes($resp->feedback
);
817 foreach($subq->choices
as $cid => $choice) {
818 if ($choice == $correct) {
819 $question->subquestions
[] = addslashes($subq->text
);
820 $question->subanswers
[] = addslashes($quest->RIGHT_MATCH_BLOCK
->matching_answerset
[$cid]->text
);
827 if ( count($quest->RESPONSE_BLOCK
->subquestions
) > count($quest->RIGHT_MATCH_BLOCK
->matching_answerset
) ||
count($question->subquestions
) < 2) {
831 // need to redo to make sure that no two questions have the same answer (rudimentary now)
832 foreach($question->subanswers
as $qstn) {
833 if(isset($previous)) {
834 if ($qstn == $previous) {
846 $questions[] = $question;
849 global $COURSE, $CFG;
850 print '<table class="boxaligncenter" border="1">';
851 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>';
853 print "<tr><td>Question:</td><td>".$quest->QUESTION_BLOCK
->text
;
854 if (isset($quest->QUESTION_BLOCK
->file
)) {
855 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>';
856 if (preg_match('/(gif|jpg|jpeg|png)$/i', $quest->QUESTION_BLOCK
->file
)) {
857 print '<img src="'.$CFG->wwwroot
.'/file.php/'.$COURSE->id
.'/bb_import/'.basename($quest->QUESTION_BLOCK
->file
).'" />';
861 print "<tr><td>Subquestions:</td><td><ul>";
862 foreach($quest->responses
as $rs) {
863 $correct_responses->{$rs->ident
} = $rs->correct
;
865 foreach($quest->RESPONSE_BLOCK
->subquestions
as $subq) {
866 print '<li>'.$subq->text
.'<ul>';
867 foreach($subq->choices
as $id=>$choice) {
869 if ($choice == $correct_responses->{$subq->ident
}) {
870 print '<font color="green">';
873 print '<font color="red">';
875 print $quest->RIGHT_MATCH_BLOCK
->matching_answerset
[$id]->text
.'</font></li>';
879 print '</ul></td></tr>';
881 print '<tr><td>Feedback:</td><td><ul>';
882 foreach($quest->feedback
as $fb) {
883 print '<li>'.$fb->ident
.': '.$fb->text
.'</li>';
885 print '</ul></td></tr></table>';
889 print "Matching question types are not handled because the quiz question type 'Rendered Matching' does not exist in this installation of Moodle<br/>";
890 print " Omitted Question: ".$quest->QUESTION_BLOCK
->text
.'<br/><br/>';
895 function strip_applet_tags_get_mathml($string) {
896 if(stristr($string, '</APPLET>') === FALSE) {
900 // strip all applet tags keeping stuff before/after and inbetween (if mathml) them
901 while (stristr($string, '</APPLET>') !== FALSE) {
902 preg_match("/(.*)\<applet.*value=\"(\<math\>.*\<\/math\>)\".*\<\/applet\>(.*)/i",$string, $mathmls);
903 $string = $mathmls[1].$mathmls[2].$mathmls[3];