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