MDL-21915 fixing remaining chmod and mkdir to use moodle file permissions
[moodle.git] / question / format / blackboard_six / format.php
CommitLineData
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 16require_once ("$CFG->libdir/xmlize.php");
17
f94902db 18class 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
264function 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
373function 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
440function 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
467function 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
483function 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
538function 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 */
557function 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//----------------------------------------
568function 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//----------------------------------------
604function 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//----------------------------------------
666function 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//----------------------------------------
735function 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//----------------------------------------
779function 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 "&nbsp;&nbsp;&nbsp;&nbsp;Omitted Question: ".$quest->QUESTION_BLOCK->text.'<br/><br/>';
806 }
807}
808
809//----------------------------------------
810// Process Matching Questions
811//----------------------------------------
812function 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 "&nbsp;&nbsp;&nbsp;&nbsp;Omitted Question: ".$quest->QUESTION_BLOCK->text.'<br/><br/>';
903 }
904}
905
906
907function 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