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