Commit | Line | Data |
---|---|---|
03f5a0f8 | 1 | <?php |
2 | ||
3 | //////////////////////////////////////////////////////////////////////////// | |
4 | /// Blackboard 6.x Format | |
5 | /// | |
6 | /// This Moodle class provides all functions necessary to import and export | |
7 | /// | |
8 | /// | |
9 | //////////////////////////////////////////////////////////////////////////// | |
10 | ||
11 | // Based on default.php, included by ../import.php | |
41a89a07 | 12 | /** |
13 | * @package questionbank | |
14 | * @subpackage importexport | |
15 | */ | |
03f5a0f8 | 16 | require_once ("$CFG->libdir/xmlize.php"); |
17 | ||
f94902db | 18 | class qformat_blackboard_six extends qformat_default { |
03f5a0f8 | 19 | function provide_import() { |
20 | return true; | |
21 | } | |
aeb15530 PS |
22 | |
23 | ||
03f5a0f8 | 24 | //Function to check and create the needed dir to unzip file to |
25 | function check_and_create_import_dir($unique_code) { | |
26 | ||
aeb15530 | 27 | global $CFG; |
03f5a0f8 | 28 | |
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 | } | |
aeb15530 | 36 | |
03f5a0f8 | 37 | return $status; |
38 | } | |
aeb15530 | 39 | |
03f5a0f8 | 40 | function clean_temp_dir($dir='') { |
4756e9c9 PS |
41 | global $CFG; |
42 | ||
aeb15530 | 43 | // for now we will just say everything happened okay note |
03f5a0f8 | 44 | // that a mess may be piling up in $CFG->dataroot/temp/bbquiz_import |
0cd25539 | 45 | // TODO return true at top of the function renders all the following code useless |
03f5a0f8 | 46 | return true; |
aeb15530 | 47 | |
03f5a0f8 | 48 | if ($dir == '') { |
aeb15530 | 49 | $dir = $this->temp_dir; |
03f5a0f8 | 50 | } |
51 | $slash = "/"; | |
52 | ||
53 | // Create arrays to store files and directories | |
54 | $dir_files = array(); | |
55 | $dir_subdirs = array(); | |
56 | ||
57 | // Make sure we can delete it | |
4756e9c9 | 58 | chmod($dir, $CFG->directorypermissions); |
03f5a0f8 | 59 | |
60 | if ((($handle = opendir($dir))) == FALSE) { | |
61 | // The directory could not be opened | |
62 | return false; | |
63 | } | |
64 | ||
65 | // Loop through all directory entries, and construct two temporary arrays containing files and sub directories | |
ed818bbd | 66 | while(false !== ($entry = readdir($handle))) { |
03f5a0f8 | 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 | } | |
74 | ||
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++) { | |
4756e9c9 | 77 | chmod($dir_files[$i], $CFG->directorypermissions); |
03f5a0f8 | 78 | if (((unlink($dir_files[$i]))) == FALSE) { |
79 | return false; | |
80 | } | |
81 | } | |
82 | ||
83 | // Empty sub directories and then remove the directory | |
84 | for($i=0; $i<count($dir_subdirs); $i++) { | |
4756e9c9 | 85 | chmod($dir_subdirs[$i], $CFG->directorypermissions); |
03f5a0f8 | 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 | } | |
95 | ||
96 | // Close directory | |
97 | closedir($handle); | |
98 | if (rmdir($this->temp_dir) == FALSE) { | |
aeb15530 | 99 | return false; |
03f5a0f8 | 100 | } |
101 | // Success, every thing is gone return true | |
102 | return true; | |
103 | } | |
aeb15530 | 104 | |
03f5a0f8 | 105 | //Function to check if a directory exists and, optionally, create it |
106 | function check_dir_exists($dir,$create=false) { | |
107 | ||
aeb15530 | 108 | global $CFG; |
03f5a0f8 | 109 | |
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 | } | |
121 | ||
122 | function importpostprocess() { | |
123 | /// Does any post-processing that may be desired | |
aeb15530 | 124 | /// Argument is a simple array of question ids that |
03f5a0f8 | 125 | /// have just been added. |
aeb15530 | 126 | |
03f5a0f8 | 127 | // need to clean up temporary directory |
128 | return $this->clean_temp_dir(); | |
129 | } | |
130 | ||
131 | function copy_file_to_course($filename) { | |
60f9e36e | 132 | global $CFG, $COURSE; |
03f5a0f8 | 133 | $filename = str_replace('\\','/',$filename); |
134 | $fullpath = $this->temp_dir.'/res00001/'.$filename; | |
135 | $basename = basename($filename); | |
aeb15530 | 136 | |
60f9e36e | 137 | $copy_to = $CFG->dataroot.'/'.$COURSE->id.'/bb_import'; |
aeb15530 | 138 | |
03f5a0f8 | 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 { | |
aeb15530 | 151 | return false; |
03f5a0f8 | 152 | } |
153 | } | |
154 | ||
155 | function readdata($filename) { | |
156 | /// Returns complete file with an array, one item per line | |
157 | global $CFG; | |
aeb15530 | 158 | |
73b7b195 | 159 | // if the extension is .dat we just return that, |
160 | // if .zip we unzip the file and get the data | |
aeb15530 | 161 | $ext = substr($this->realfilename, strpos($this->realfilename,'.'), strlen($this->realfilename)-1); |
73b7b195 | 162 | if ($ext=='.dat') { |
163 | if (!is_readable($filename)) { | |
aeb15530 PS |
164 | print_error('filenotreadable', 'error'); |
165 | } | |
73b7b195 | 166 | return file($filename); |
aeb15530 PS |
167 | } |
168 | ||
03f5a0f8 | 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")) { | |
0be2c858 | 175 | print_error('cannotcopybackup', 'question'); |
03f5a0f8 | 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 | |
6dbcacee | 185 | if (preg_match("~\r~", $filearray[0]) AND !preg_match("~\n~", $filearray[0])) { |
03f5a0f8 | 186 | return explode("\r", $filearray[0]); |
187 | } else { | |
188 | return $filearray; | |
189 | } | |
03f5a0f8 | 190 | } |
191 | } | |
192 | else { | |
aeb15530 | 193 | print_error('cannotfindquestionfile', 'questioni'); |
03f5a0f8 | 194 | } |
195 | } | |
196 | else { | |
197 | print "filename: $filename<br />tempdir: $temp_dir <br />"; | |
aeb15530 | 198 | print_error('cannotunzip', 'question'); |
03f5a0f8 | 199 | } |
200 | } | |
201 | else { | |
aeb15530 | 202 | print_error('cannotreaduploadfile'); |
03f5a0f8 | 203 | } |
204 | } | |
205 | else { | |
aeb15530 | 206 | print_error('cannotcreatetempdir'); |
03f5a0f8 | 207 | } |
208 | } | |
aeb15530 | 209 | |
03f5a0f8 | 210 | function save_question_options($question) { |
aeb15530 | 211 | return true; |
03f5a0f8 | 212 | } |
aeb15530 PS |
213 | |
214 | ||
215 | ||
03f5a0f8 | 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 | |
aeb15530 | 219 | /// readquestion(). |
03f5a0f8 | 220 | |
221 | $text = implode($lines, " "); | |
222 | $xml = xmlize($text, 0); | |
223 | ||
224 | $raw_questions = $xml['questestinterop']['#']['assessment'][0]['#']['section'][0]['#']['item']; | |
225 | $questions = array(); | |
226 | ||
227 | foreach($raw_questions as $quest) { | |
228 | $question = $this->create_raw_question($quest); | |
229 | ||
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; | |
3e60818b | 249 | case 'Short Response': |
250 | $this->process_essay($question, $questions); | |
251 | break; | |
03f5a0f8 | 252 | default: |
253 | print "Unknown or unhandled question type: \"$question->qtype\"<br />"; | |
254 | break; | |
255 | } | |
256 | ||
257 | } | |
258 | return $questions; | |
259 | } | |
260 | ||
261 | ||
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) { | |
aeb15530 | 265 | |
e53ff7f6 | 266 | $question = new StdClass; |
03f5a0f8 | 267 | $question->qtype = $quest['#']['itemmetadata'][0]['#']['bbmd_questiontype'][0]['#']; |
e53ff7f6 | 268 | $question->id = $quest['#']['itemmetadata'][0]['#']['bbmd_asi_object_id'][0]['#']; |
03f5a0f8 | 269 | $presentation->blocks = $quest['#']['presentation'][0]['#']['flow'][0]['#']['flow']; |
270 | ||
271 | foreach($presentation->blocks as $pblock) { | |
aeb15530 | 272 | |
03f5a0f8 | 273 | $block = NULL; |
274 | $block->type = $pblock['@']['class']; | |
275 | ||
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>"; | |
aeb15530 | 281 | $this->process_block($sblock, $block); |
03f5a0f8 | 282 | } |
283 | break; | |
284 | ||
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); | |
aeb15530 | 319 | $block->choices[] = $choices; |
03f5a0f8 | 320 | } |
321 | break; | |
3e60818b | 322 | case 'Short Response': |
323 | // do nothing? | |
324 | break; | |
03f5a0f8 | 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']; | |
1d1e8f2b | 337 | |
03f5a0f8 | 338 | $answerset = array(); |
339 | foreach($matching_answerset as $answer) { | |
1d1e8f2b | 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]['#']; | |
03f5a0f8 | 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 | } | |
aeb15530 PS |
353 | |
354 | // determine response processing | |
03f5a0f8 | 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; | |
371 | } | |
372 | ||
373 | function process_block($cur_block, &$block) { | |
60f9e36e | 374 | global $COURSE, $CFG; |
03f5a0f8 | 375 | |
376 | $cur_type = $cur_block['@']['class']; | |
377 | switch($cur_type) { | |
378 | case 'FORMATTED_TEXT_BLOCK': | |
aeb15530 | 379 | $block->text = $this->strip_applet_tags_get_mathml($cur_block['#']['material'][0]['#']['mat_extension'][0]['#']['mat_formattedtext'][0]['#']); |
03f5a0f8 | 380 | break; |
381 | case 'FILE_BLOCK': | |
382 | //revisit this to make sure it is working correctly | |
aeb15530 | 383 | // Commented out ['matapplication']..., etc. because I |
03f5a0f8 | 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); | |
60f9e36e | 390 | $block->file = $CFG->wwwroot.'/file.php/'.$COURSE->id.'/bb_import/'.basename($block->file); |
03f5a0f8 | 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) { | |
aeb15530 | 409 | $this->process_block($sub_block, $block); |
03f5a0f8 | 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 | } | |
aeb15530 PS |
435 | break; |
436 | } | |
03f5a0f8 | 437 | return $block; |
438 | } | |
439 | ||
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 | |
aeb15530 | 453 | $cur_choice = null; |
03f5a0f8 | 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 | |
aeb15530 | 460 | $cur_choice = null; |
03f5a0f8 | 461 | $this->process_block($choice, $cur_choice); |
462 | } | |
463 | $choices[] = $cur_choice; | |
aeb15530 | 464 | } |
03f5a0f8 | 465 | } |
466 | ||
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 | } | |
481 | } | |
482 | ||
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'])) { | |
aeb15530 | 490 | $response->title = $bb_response['@']['title']; |
03f5a0f8 | 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]['#'])) { | |
aeb15530 | 497 | $response->ident[0] = $bb_response['#']['conditionvar'][0]['#'];//['varequal'][0]['#']; |
03f5a0f8 | 498 | } |
499 | else if (isset($bb_response['#']['conditionvar'][0]['#']['other'][0]['#'])) { | |
aeb15530 | 500 | $response->ident[0] = $bb_response['#']['conditionvar'][0]['#']['other'][0]['#']; |
03f5a0f8 | 501 | } |
aeb15530 | 502 | |
03f5a0f8 | 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']; | |
aeb15530 | 509 | } |
03f5a0f8 | 510 | } |
511 | } | |
512 | else { | |
aeb15530 | 513 | $response->feedback = $bb_response['#']['displayfeedback'][0]['@']['linkrefid']; |
03f5a0f8 | 514 | } |
515 | ||
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: | |
aeb15530 | 523 | // I have only seen this being 0 or unset |
03f5a0f8 | 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 | } | |
aeb15530 | 533 | |
03f5a0f8 | 534 | $responses[] = $response; |
535 | } | |
536 | } | |
537 | ||
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 | |
aeb15530 | 542 | $feedback = null; |
03f5a0f8 | 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 | } | |
552 | } | |
553 | ||
e53ff7f6 | 554 | /** |
555 | * Create common parts of question | |
556 | */ | |
557 | function process_common( $quest ) { | |
558 | $question = $this->defaultquestion(); | |
294ce987 | 559 | $question->questiontext = $quest->QUESTION_BLOCK->text; |
e53ff7f6 | 560 | $question->name = shorten_text( $quest->id, 250 ); |
561 | ||
562 | return $question; | |
563 | } | |
564 | ||
03f5a0f8 | 565 | //---------------------------------------- |
566 | // Process True / False Questions | |
567 | //---------------------------------------- | |
568 | function process_tf($quest, &$questions) { | |
e53ff7f6 | 569 | $question = $this->process_common( $quest ); |
03f5a0f8 | 570 | |
571 | $question->qtype = TRUEFALSE; | |
03f5a0f8 | 572 | $question->single = 1; // Only one answer is allowed |
1b36a585 | 573 | // 0th [response] is the correct answer. |
574 | $responses = $quest->responses; | |
575 | $correctresponse = $responses[0]->ident[0]['varequal'][0]['#']; | |
576 | if ($correctresponse != 'false') { | |
aeb15530 | 577 | $correct = true; |
03f5a0f8 | 578 | } |
579 | else { | |
aeb15530 | 580 | $correct = false; |
03f5a0f8 | 581 | } |
aeb15530 | 582 | |
03f5a0f8 | 583 | foreach($quest->feedback as $fb) { |
aeb15530 | 584 | $fback->{$fb->ident} = $fb->text; |
03f5a0f8 | 585 | } |
aeb15530 | 586 | |
03f5a0f8 | 587 | if ($correct) { // true is correct |
588 | $question->answer = 1; | |
294ce987 | 589 | $question->feedbacktrue = $fback->correct; |
590 | $question->feedbackfalse = $fback->incorrect; | |
03f5a0f8 | 591 | } else { // false is correct |
592 | $question->answer = 0; | |
294ce987 | 593 | $question->feedbacktrue = $fback->incorrect; |
594 | $question->feedbackfalse = $fback->correct; | |
03f5a0f8 | 595 | } |
d022bf65 | 596 | $question->correctanswer = $question->answer; |
03f5a0f8 | 597 | $questions[] = $question; |
598 | } | |
599 | ||
600 | ||
601 | //---------------------------------------- | |
602 | // Process Fill in the Blank | |
603 | //---------------------------------------- | |
604 | function process_fblank($quest, &$questions) { | |
e53ff7f6 | 605 | $question = $this->process_common( $quest ); |
03f5a0f8 | 606 | $question->qtype = SHORTANSWER; |
03f5a0f8 | 607 | $question->single = 1; |
e53ff7f6 | 608 | |
03f5a0f8 | 609 | $answers = array(); |
610 | $fractions = array(); | |
611 | $feedbacks = array(); | |
aeb15530 | 612 | |
03f5a0f8 | 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 | } | |
aeb15530 | 622 | |
03f5a0f8 | 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') { | |
294ce987 | 628 | $answers[] = $response->ident[0]['varequal'][0]['#']; |
03f5a0f8 | 629 | $fractions[] = 1; |
630 | if (isset($feedback['correct'])) { | |
294ce987 | 631 | $feedbacks[] = $feedback['correct']; |
03f5a0f8 | 632 | } |
633 | else { | |
634 | $feedbacks[] = ''; | |
635 | } | |
636 | } | |
637 | } | |
aeb15530 | 638 | |
03f5a0f8 | 639 | } |
640 | } | |
641 | ||
aeb15530 | 642 | //Adding catchall to so that students can see feedback for incorrect answers when they enter something the |
03f5a0f8 | 643 | //instructor did not enter |
644 | $answers[] = '*'; | |
645 | $fractions[] = 0; | |
646 | if (isset($feedback['incorrect'])) { | |
294ce987 | 647 | $feedbacks[] = $feedback['incorrect']; |
03f5a0f8 | 648 | } |
649 | else { | |
650 | $feedbacks[] = ''; | |
651 | } | |
aeb15530 | 652 | |
03f5a0f8 | 653 | $question->answer = $answers; |
654 | $question->fraction = $fractions; | |
655 | $question->feedback = $feedbacks; // Changed to assign $feedbacks to $question->feedback instead of | |
656 | ||
657 | if (!empty($question)) { | |
658 | $questions[] = $question; | |
659 | } | |
660 | ||
661 | } | |
662 | ||
663 | //---------------------------------------- | |
664 | // Process Multiple Choice Questions | |
665 | //---------------------------------------- | |
666 | function process_mc($quest, &$questions) { | |
e53ff7f6 | 667 | $question = $this->process_common( $quest ); |
03f5a0f8 | 668 | $question->qtype = MULTICHOICE; |
03f5a0f8 | 669 | $question->single = 1; |
aeb15530 | 670 | |
03f5a0f8 | 671 | $feedback = array(); |
672 | foreach($quest->feedback as $fback) { | |
294ce987 | 673 | $feedback[$fback->ident] = $fback->text; |
03f5a0f8 | 674 | } |
aeb15530 | 675 | |
03f5a0f8 | 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 | } | |
691 | ||
692 | $i = 0; | |
693 | foreach($quest->RESPONSE_BLOCK->choices as $response) { | |
294ce987 | 694 | $question->answer[$i] = $response->text; |
03f5a0f8 | 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?) | |
aeb15530 | 708 | $question->feedback[$i] = "correct"; |
03f5a0f8 | 709 | } |
aeb15530 | 710 | } |
03f5a0f8 | 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 | } | |
726 | ||
727 | if (!empty($question)) { | |
728 | $questions[] = $question; | |
729 | } | |
730 | } | |
731 | ||
732 | //---------------------------------------- | |
733 | // Process Multiple Choice Questions With Multiple Answers | |
734 | //---------------------------------------- | |
735 | function process_ma($quest, &$questions) { | |
e53ff7f6 | 736 | $question = $this->process_common( $quest ); // copied this from process_mc |
03f5a0f8 | 737 | $question->qtype = MULTICHOICE; |
03f5a0f8 | 738 | $question->single = 0; // More than one answer allowed |
03f5a0f8 | 739 | |
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 | } | |
aeb15530 | 750 | |
03f5a0f8 | 751 | foreach ($quest->feedback as $fb) { |
294ce987 | 752 | $feedback->{$fb->ident} = trim($fb->text); |
03f5a0f8 | 753 | } |
aeb15530 | 754 | |
03f5a0f8 | 755 | $correct_answer_count = count($correct_answers); |
756 | $choiceset = $quest->RESPONSE_BLOCK->choices; | |
757 | $i = 0; | |
758 | foreach($choiceset as $choice) { | |
294ce987 | 759 | $question->answer[$i] = trim($choice->text); |
03f5a0f8 | 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 { | |
aeb15530 | 766 | // wrong answer |
03f5a0f8 | 767 | $question->fraction[$i] = 0; |
768 | $question->feedback[$i] = $feedback->incorrect; | |
769 | } | |
770 | $i++; | |
771 | } | |
772 | ||
773 | $questions[] = $question; | |
774 | } | |
775 | ||
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 | |
781 | ||
782 | if (defined("ESSAY")) { | |
783 | // treat as short answer | |
e53ff7f6 | 784 | $question = $this->process_common( $quest ); // copied this from process_mc |
03f5a0f8 | 785 | $question->qtype = ESSAY; |
aeb15530 | 786 | |
03f5a0f8 | 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') { | |
294ce987 | 793 | $question->feedback = $feedback->text; |
03f5a0f8 | 794 | } |
795 | } | |
aeb15530 | 796 | //Added because essay/questiontype.php:save_question_option is expecting a |
03f5a0f8 | 797 | //fraction property - CT 8/10/06 |
aeb15530 | 798 | $question->fraction[] = 1; |
03f5a0f8 | 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 " Omitted Question: ".$quest->QUESTION_BLOCK->text.'<br/><br/>'; | |
806 | } | |
807 | } | |
808 | ||
809 | //---------------------------------------- | |
810 | // Process Matching Questions | |
811 | //---------------------------------------- | |
812 | function process_matching($quest, &$questions) { | |
1d1e8f2b | 813 | global $QTYPES; |
814 | ||
815 | // renderedmatch is an optional plugin, so we need to check if it is defined | |
e53ff7f6 | 816 | if (array_key_exists('renderedmatch', $QTYPES)) { |
817 | $question = $this->process_common( $quest ); | |
03f5a0f8 | 818 | $question->valid = true; |
1d1e8f2b | 819 | $question->qtype = 'renderedmatch'; |
aeb15530 | 820 | |
03f5a0f8 | 821 | foreach($quest->RESPONSE_BLOCK->subquestions as $qid => $subq) { |
822 | foreach($quest->responses as $rid => $resp) { | |
823 | if ($resp->ident == $subq->ident) { | |
294ce987 | 824 | $correct = $resp->correct; |
aeb15530 | 825 | $feedback = $resp->feedback; |
03f5a0f8 | 826 | } |
827 | } | |
aeb15530 | 828 | |
03f5a0f8 | 829 | foreach($subq->choices as $cid => $choice) { |
830 | if ($choice == $correct) { | |
294ce987 | 831 | $question->subquestions[] = $subq->text; |
832 | $question->subanswers[] = $quest->RIGHT_MATCH_BLOCK->matching_answerset[$cid]->text; | |
03f5a0f8 | 833 | } |
834 | } | |
835 | } | |
aeb15530 | 836 | |
03f5a0f8 | 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) { | |
aeb15530 PS |
847 | $status = false; |
848 | } | |
03f5a0f8 | 849 | } |
850 | $previous = $qstn; | |
851 | if ($qstn == '') { | |
aeb15530 | 852 | $status = false; |
03f5a0f8 | 853 | } |
854 | } | |
855 | } | |
aeb15530 | 856 | |
03f5a0f8 | 857 | if ($status) { |
aeb15530 | 858 | $questions[] = $question; |
03f5a0f8 | 859 | } |
860 | else { | |
60f9e36e | 861 | global $COURSE, $CFG; |
09275894 | 862 | print '<table class="boxaligncenter" border="1">'; |
aeb15530 PS |
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>'; |
864 | ||
03f5a0f8 | 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)) { | |
60f9e36e | 869 | print '<img src="'.$CFG->wwwroot.'/file.php/'.$COURSE->id.'/bb_import/'.basename($quest->QUESTION_BLOCK->file).'" />'; |
03f5a0f8 | 870 | } |
871 | } | |
872 | print "</td></tr>"; | |
873 | print "<tr><td>Subquestions:</td><td><ul>"; | |
874 | foreach($quest->responses as $rs) { | |
aeb15530 | 875 | $correct_responses->{$rs->ident} = $rs->correct; |
03f5a0f8 | 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>'; | |
aeb15530 | 892 | |
03f5a0f8 | 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 " Omitted Question: ".$quest->QUESTION_BLOCK->text.'<br/><br/>'; | |
903 | } | |
904 | } | |
905 | ||
906 | ||
907 | function strip_applet_tags_get_mathml($string) { | |
908 | if(stristr($string, '</APPLET>') === FALSE) { | |
aeb15530 | 909 | return $string; |
03f5a0f8 | 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 | } | |
aeb15530 | 917 | return $string; |
03f5a0f8 | 918 | } |
919 | } | |
920 | ||
921 | } // close object | |
aeb15530 | 922 |