|
Moodle
2.2.1
http://www.collinsharper.com
|
00001 <?php 00002 // This file is part of Moodle - http://moodle.org/ 00003 // 00004 // Moodle is free software: you can redistribute it and/or modify 00005 // it under the terms of the GNU General Public License as published by 00006 // the Free Software Foundation, either version 3 of the License, or 00007 // (at your option) any later version. 00008 // 00009 // Moodle is distributed in the hope that it will be useful, 00010 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00011 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00012 // GNU General Public License for more details. 00013 // 00014 // You should have received a copy of the GNU General Public License 00015 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 00016 00027 defined('MOODLE_INTERNAL') || die(); 00028 00029 require_once ($CFG->libdir . '/xmlize.php'); 00030 00031 00038 class qformat_blackboard_six extends qformat_default { 00039 function provide_import() { 00040 return true; 00041 } 00042 00043 00044 //Function to check and create the needed dir to unzip file to 00045 function check_and_create_import_dir($unique_code) { 00046 00047 global $CFG; 00048 00049 $status = $this->check_dir_exists($CFG->tempdir."",true); 00050 if ($status) { 00051 $status = $this->check_dir_exists($CFG->tempdir."/bbquiz_import",true); 00052 } 00053 if ($status) { 00054 $status = $this->check_dir_exists($CFG->tempdir."/bbquiz_import/".$unique_code,true); 00055 } 00056 00057 return $status; 00058 } 00059 00060 function clean_temp_dir($dir='') { 00061 global $CFG; 00062 00063 // for now we will just say everything happened okay note 00064 // that a mess may be piling up in $CFG->tempdir/bbquiz_import 00065 // TODO return true at top of the function renders all the following code useless 00066 return true; 00067 00068 if ($dir == '') { 00069 $dir = $this->temp_dir; 00070 } 00071 $slash = "/"; 00072 00073 // Create arrays to store files and directories 00074 $dir_files = array(); 00075 $dir_subdirs = array(); 00076 00077 // Make sure we can delete it 00078 chmod($dir, $CFG->directorypermissions); 00079 00080 if ((($handle = opendir($dir))) == FALSE) { 00081 // The directory could not be opened 00082 return false; 00083 } 00084 00085 // Loop through all directory entries, and construct two temporary arrays containing files and sub directories 00086 while(false !== ($entry = readdir($handle))) { 00087 if (is_dir($dir. $slash .$entry) && $entry != ".." && $entry != ".") { 00088 $dir_subdirs[] = $dir. $slash .$entry; 00089 } 00090 else if ($entry != ".." && $entry != ".") { 00091 $dir_files[] = $dir. $slash .$entry; 00092 } 00093 } 00094 00095 // Delete all files in the curent directory return false and halt if a file cannot be removed 00096 $countdir_files = count($dir_files); 00097 for($i=0; $i<$countdir_files; $i++) { 00098 chmod($dir_files[$i], $CFG->directorypermissions); 00099 if (((unlink($dir_files[$i]))) == FALSE) { 00100 return false; 00101 } 00102 } 00103 00104 // Empty sub directories and then remove the directory 00105 $countdir_subdirs = count($dir_subdirs); 00106 for($i=0; $i<$countdir_subdirs; $i++) { 00107 chmod($dir_subdirs[$i], $CFG->directorypermissions); 00108 if ($this->clean_temp_dir($dir_subdirs[$i]) == FALSE) { 00109 return false; 00110 } 00111 else { 00112 if (rmdir($dir_subdirs[$i]) == FALSE) { 00113 return false; 00114 } 00115 } 00116 } 00117 00118 // Close directory 00119 closedir($handle); 00120 if (rmdir($this->temp_dir) == FALSE) { 00121 return false; 00122 } 00123 // Success, every thing is gone return true 00124 return true; 00125 } 00126 00127 //Function to check if a directory exists and, optionally, create it 00128 function check_dir_exists($dir,$create=false) { 00129 00130 global $CFG; 00131 00132 $status = true; 00133 if(!is_dir($dir)) { 00134 if (!$create) { 00135 $status = false; 00136 } else { 00137 umask(0000); 00138 $status = mkdir ($dir,$CFG->directorypermissions); 00139 } 00140 } 00141 return $status; 00142 } 00143 00144 function importpostprocess() { 00148 00149 // need to clean up temporary directory 00150 return $this->clean_temp_dir(); 00151 } 00152 00153 function copy_file_to_course($filename) { 00154 global $CFG, $COURSE; 00155 $filename = str_replace('\\','/',$filename); 00156 $fullpath = $this->temp_dir.'/res00001/'.$filename; 00157 $basename = basename($filename); 00158 00159 $copy_to = $CFG->dataroot.'/'.$COURSE->id.'/bb_import'; 00160 00161 if ($this->check_dir_exists($copy_to,true)) { 00162 if(is_readable($fullpath)) { 00163 $copy_to.= '/'.$basename; 00164 if (!copy($fullpath, $copy_to)) { 00165 return false; 00166 } 00167 else { 00168 return $copy_to; 00169 } 00170 } 00171 } 00172 else { 00173 return false; 00174 } 00175 } 00176 00177 function readdata($filename) { 00179 global $CFG; 00180 00181 // if the extension is .dat we just return that, 00182 // if .zip we unzip the file and get the data 00183 $ext = substr($this->realfilename, strpos($this->realfilename,'.'), strlen($this->realfilename)-1); 00184 if ($ext=='.dat') { 00185 if (!is_readable($filename)) { 00186 print_error('filenotreadable', 'error'); 00187 } 00188 return file($filename); 00189 } 00190 00191 $unique_code = time(); 00192 $temp_dir = $CFG->tempdir."/bbquiz_import/".$unique_code; 00193 $this->temp_dir = $temp_dir; 00194 if ($this->check_and_create_import_dir($unique_code)) { 00195 if(is_readable($filename)) { 00196 if (!copy($filename, "$temp_dir/bboard.zip")) { 00197 print_error('cannotcopybackup', 'question'); 00198 } 00199 if(unzip_file("$temp_dir/bboard.zip", '', false)) { 00200 // assuming that the information is in res0001.dat 00201 // after looking at 6 examples this was always the case 00202 $q_file = "$temp_dir/res00001.dat"; 00203 if (is_file($q_file)) { 00204 if (is_readable($q_file)) { 00205 $filearray = file($q_file); 00207 if (preg_match("~\r~", $filearray[0]) AND !preg_match("~\n~", $filearray[0])) { 00208 return explode("\r", $filearray[0]); 00209 } else { 00210 return $filearray; 00211 } 00212 } 00213 } 00214 else { 00215 print_error('cannotfindquestionfile', 'questioni'); 00216 } 00217 } 00218 else { 00219 print "filename: $filename<br />tempdir: $temp_dir <br />"; 00220 print_error('cannotunzip', 'question'); 00221 } 00222 } 00223 else { 00224 print_error('cannotreaduploadfile'); 00225 } 00226 } 00227 else { 00228 print_error('cannotcreatetempdir'); 00229 } 00230 } 00231 00232 function save_question_options($question) { 00233 return true; 00234 } 00235 00236 00237 00238 function readquestions ($lines) { 00242 00243 $text = implode($lines, " "); 00244 $xml = xmlize($text, 0); 00245 00246 $raw_questions = $xml['questestinterop']['#']['assessment'][0]['#']['section'][0]['#']['item']; 00247 $questions = array(); 00248 00249 foreach($raw_questions as $quest) { 00250 $question = $this->create_raw_question($quest); 00251 00252 switch($question->qtype) { 00253 case "Matching": 00254 $this->process_matching($question, $questions); 00255 break; 00256 case "Multiple Choice": 00257 $this->process_mc($question, $questions); 00258 break; 00259 case "Essay": 00260 $this->process_essay($question, $questions); 00261 break; 00262 case "Multiple Answer": 00263 $this->process_ma($question, $questions); 00264 break; 00265 case "True/False": 00266 $this->process_tf($question, $questions); 00267 break; 00268 case 'Fill in the Blank': 00269 $this->process_fblank($question, $questions); 00270 break; 00271 case 'Short Response': 00272 $this->process_essay($question, $questions); 00273 break; 00274 default: 00275 print "Unknown or unhandled question type: \"$question->qtype\"<br />"; 00276 break; 00277 } 00278 00279 } 00280 return $questions; 00281 } 00282 00283 00284 // creates a cleaner object to deal with for processing into moodle 00285 // the object created is NOT a moodle question object 00286 function create_raw_question($quest) { 00287 00288 $question = new stdClass(); 00289 $question->qtype = $quest['#']['itemmetadata'][0]['#']['bbmd_questiontype'][0]['#']; 00290 $question->id = $quest['#']['itemmetadata'][0]['#']['bbmd_asi_object_id'][0]['#']; 00291 $presentation->blocks = $quest['#']['presentation'][0]['#']['flow'][0]['#']['flow']; 00292 00293 foreach($presentation->blocks as $pblock) { 00294 00295 $block = NULL; 00296 $block->type = $pblock['@']['class']; 00297 00298 switch($block->type) { 00299 case 'QUESTION_BLOCK': 00300 $sub_blocks = $pblock['#']['flow']; 00301 foreach($sub_blocks as $sblock) { 00302 //echo "Calling process_block from line 263<br>"; 00303 $this->process_block($sblock, $block); 00304 } 00305 break; 00306 00307 case 'RESPONSE_BLOCK': 00308 $choices = NULL; 00309 switch($question->qtype) { 00310 case 'Matching': 00311 $bb_subquestions = $pblock['#']['flow']; 00312 $sub_questions = array(); 00313 foreach($bb_subquestions as $bb_subquestion) { 00314 $sub_question = NULL; 00315 $sub_question->ident = $bb_subquestion['#']['response_lid'][0]['@']['ident']; 00316 $this->process_block($bb_subquestion['#']['flow'][0], $sub_question); 00317 $bb_choices = $bb_subquestion['#']['response_lid'][0]['#']['render_choice'][0]['#']['flow_label'][0]['#']['response_label']; 00318 $choices = array(); 00319 $this->process_choices($bb_choices, $choices); 00320 $sub_question->choices = $choices; 00321 if (!isset($block->subquestions)) { 00322 $block->subquestions = array(); 00323 } 00324 $block->subquestions[] = $sub_question; 00325 } 00326 break; 00327 case 'Multiple Answer': 00328 $bb_choices = $pblock['#']['response_lid'][0]['#']['render_choice'][0]['#']['flow_label']; 00329 $choices = array(); 00330 $this->process_choices($bb_choices, $choices); 00331 $block->choices = $choices; 00332 break; 00333 case 'Essay': 00334 // Doesn't apply since the user responds with text input 00335 break; 00336 case 'Multiple Choice': 00337 $mc_choices = $pblock['#']['response_lid'][0]['#']['render_choice'][0]['#']['flow_label']; 00338 foreach($mc_choices as $mc_choice) { 00339 $choices = NULL; 00340 $choices = $this->process_block($mc_choice, $choices); 00341 $block->choices[] = $choices; 00342 } 00343 break; 00344 case 'Short Response': 00345 // do nothing? 00346 break; 00347 case 'Fill in the Blank': 00348 // do nothing? 00349 break; 00350 default: 00351 $bb_choices = $pblock['#']['response_lid'][0]['#']['render_choice'][0]['#']['flow_label'][0]['#']['response_label']; 00352 $choices = array(); 00353 $this->process_choices($bb_choices, $choices); 00354 $block->choices = $choices; 00355 } 00356 break; 00357 case 'RIGHT_MATCH_BLOCK': 00358 $matching_answerset = $pblock['#']['flow']; 00359 00360 $answerset = array(); 00361 foreach($matching_answerset as $answer) { 00362 // $answerset[] = $this->process_block($answer, $bb_answer); 00363 $bb_answer = null; 00364 $bb_answer->text = $answer['#']['flow'][0]['#']['material'][0]['#']['mat_extension'][0]['#']['mat_formattedtext'][0]['#']; 00365 $answerset[] = $bb_answer; 00366 } 00367 $block->matching_answerset = $answerset; 00368 break; 00369 default: 00370 print "UNHANDLED PRESENTATION BLOCK"; 00371 break; 00372 } 00373 $question->{$block->type} = $block; 00374 } 00375 00376 // determine response processing 00377 // there is a section called 'outcomes' that I don't know what to do with 00378 $resprocessing = $quest['#']['resprocessing']; 00379 $respconditions = $resprocessing[0]['#']['respcondition']; 00380 $reponses = array(); 00381 if ($question->qtype == 'Matching') { 00382 $this->process_matching_responses($respconditions, $responses); 00383 } 00384 else { 00385 $this->process_responses($respconditions, $responses); 00386 } 00387 $question->responses = $responses; 00388 $feedbackset = $quest['#']['itemfeedback']; 00389 $feedbacks = array(); 00390 $this->process_feedback($feedbackset, $feedbacks); 00391 $question->feedback = $feedbacks; 00392 return $question; 00393 } 00394 00395 function process_block($cur_block, &$block) { 00396 global $COURSE, $CFG; 00397 00398 $cur_type = $cur_block['@']['class']; 00399 switch($cur_type) { 00400 case 'FORMATTED_TEXT_BLOCK': 00401 $block->text = $this->strip_applet_tags_get_mathml($cur_block['#']['material'][0]['#']['mat_extension'][0]['#']['mat_formattedtext'][0]['#']); 00402 break; 00403 case 'FILE_BLOCK': 00404 //revisit this to make sure it is working correctly 00405 // Commented out ['matapplication']..., etc. because I 00406 // noticed that when I imported a new Blackboard 6 file 00407 // and printed out the block, the tree did not extend past ['material'][0]['#'] - CT 8/3/06 00408 $block->file = $cur_block['#']['material'][0]['#'];//['matapplication'][0]['@']['uri']; 00409 if ($block->file != '') { 00410 // if we have a file copy it to the course dir and adjust its name to be visible over the web. 00411 $block->file = $this->copy_file_to_course($block->file); 00412 $block->file = $CFG->wwwroot.'/file.php/'.$COURSE->id.'/bb_import/'.basename($block->file); 00413 } 00414 break; 00415 case 'Block': 00416 if (isset($cur_block['#']['material'][0]['#']['mattext'][0]['#'])) { 00417 $block->text = $cur_block['#']['material'][0]['#']['mattext'][0]['#']; 00418 } 00419 else if (isset($cur_block['#']['material'][0]['#']['mat_extension'][0]['#']['mat_formattedtext'][0]['#'])) { 00420 $block->text = $cur_block['#']['material'][0]['#']['mat_extension'][0]['#']['mat_formattedtext'][0]['#']; 00421 } 00422 else if (isset($cur_block['#']['response_label'])) { 00423 // this is a response label block 00424 $sub_blocks = $cur_block['#']['response_label'][0]; 00425 if(!isset($block->ident)) { 00426 if(isset($sub_blocks['@']['ident'])) { 00427 $block->ident = $sub_blocks['@']['ident']; 00428 } 00429 } 00430 foreach($sub_blocks['#']['flow_mat'] as $sub_block) { 00431 $this->process_block($sub_block, $block); 00432 } 00433 } 00434 else { 00435 if (isset($cur_block['#']['flow_mat']) || isset($cur_block['#']['flow'])) { 00436 if (isset($cur_block['#']['flow_mat'])) { 00437 $sub_blocks = $cur_block['#']['flow_mat']; 00438 } 00439 elseif (isset($cur_block['#']['flow'])) { 00440 $sub_blocks = $cur_block['#']['flow']; 00441 } 00442 foreach ($sub_blocks as $sblock) { 00443 // this will recursively grab the sub blocks which should be of one of the other types 00444 $this->process_block($sblock, $block); 00445 } 00446 } 00447 } 00448 break; 00449 case 'LINK_BLOCK': 00450 // not sure how this should be included 00451 if (!empty($cur_block['#']['material'][0]['#']['mattext'][0]['@']['uri'])) { 00452 $block->link = $cur_block['#']['material'][0]['#']['mattext'][0]['@']['uri']; 00453 } 00454 else { 00455 $block->link = ''; 00456 } 00457 break; 00458 } 00459 return $block; 00460 } 00461 00462 function process_choices($bb_choices, &$choices) { 00463 foreach($bb_choices as $choice) { 00464 if (isset($choice['@']['ident'])) { 00465 $cur_choice = $choice['@']['ident']; 00466 } 00467 else { //for multiple answer 00468 $cur_choice = $choice['#']['response_label'][0];//['@']['ident']; 00469 } 00470 if (isset($choice['#']['flow_mat'][0])) { //for multiple answer 00471 $cur_block = $choice['#']['flow_mat'][0]; 00472 // Reset $cur_choice to NULL because process_block is expecting an object 00473 // for the second argument and not a string, which is what is was set as 00474 // originally - CT 8/7/06 00475 $cur_choice = null; 00476 $this->process_block($cur_block, $cur_choice); 00477 } 00478 elseif (isset($choice['#']['response_label'])) { 00479 // Reset $cur_choice to NULL because process_block is expecting an object 00480 // for the second argument and not a string, which is what is was set as 00481 // originally - CT 8/7/06 00482 $cur_choice = null; 00483 $this->process_block($choice, $cur_choice); 00484 } 00485 $choices[] = $cur_choice; 00486 } 00487 } 00488 00489 function process_matching_responses($bb_responses, &$responses) { 00490 foreach($bb_responses as $bb_response) { 00491 $response = NULL; 00492 if (isset($bb_response['#']['conditionvar'][0]['#']['varequal'])) { 00493 $response->correct = $bb_response['#']['conditionvar'][0]['#']['varequal'][0]['#']; 00494 $response->ident = $bb_response['#']['conditionvar'][0]['#']['varequal'][0]['@']['respident']; 00495 } 00496 else { 00497 $response->correct = 'Broken Question?'; 00498 $response->ident = 'Broken Question?'; 00499 } 00500 $response->feedback = $bb_response['#']['displayfeedback'][0]['@']['linkrefid']; 00501 $responses[] = $response; 00502 } 00503 } 00504 00505 function process_responses($bb_responses, &$responses) { 00506 foreach($bb_responses as $bb_response) { 00507 //Added this line to instantiate $response. 00508 // Without instantiating the $response variable, the same object 00509 // gets added to the array 00510 $response = new stdClass(); 00511 if (isset($bb_response['@']['title'])) { 00512 $response->title = $bb_response['@']['title']; 00513 } 00514 else { 00515 $reponse->title = $bb_response['#']['displayfeedback'][0]['@']['linkrefid']; 00516 } 00517 $reponse->ident = array(); 00518 if (isset($bb_response['#']['conditionvar'][0]['#'])){//['varequal'][0]['#'])) { 00519 $response->ident[0] = $bb_response['#']['conditionvar'][0]['#'];//['varequal'][0]['#']; 00520 } 00521 else if (isset($bb_response['#']['conditionvar'][0]['#']['other'][0]['#'])) { 00522 $response->ident[0] = $bb_response['#']['conditionvar'][0]['#']['other'][0]['#']; 00523 } 00524 00525 if (isset($bb_response['#']['conditionvar'][0]['#']['and'])){//[0]['#'])) { 00526 $responseset = $bb_response['#']['conditionvar'][0]['#']['and'];//[0]['#']['varequal']; 00527 foreach($responseset as $rs) { 00528 $response->ident[] = $rs['#']; 00529 if(!isset($response->feedback) and isset( $rs['@'] ) ) { 00530 $response->feedback = $rs['@']['respident']; 00531 } 00532 } 00533 } 00534 else { 00535 $response->feedback = $bb_response['#']['displayfeedback'][0]['@']['linkrefid']; 00536 } 00537 00538 // determine what point value to give response 00539 if (isset($bb_response['#']['setvar'])) { 00540 switch ($bb_response['#']['setvar'][0]['#']) { 00541 case "SCORE.max": 00542 $response->fraction = 1; 00543 break; 00544 default: 00545 // I have only seen this being 0 or unset 00546 // there are probably fractional values of SCORE.max, but I'm not sure what they look like 00547 $response->fraction = 0; 00548 break; 00549 } 00550 } 00551 else { 00552 // just going to assume this is the case this is probably not correct. 00553 $response->fraction = 0; 00554 } 00555 00556 $responses[] = $response; 00557 } 00558 } 00559 00560 function process_feedback($feedbackset, &$feedbacks) { 00561 foreach($feedbackset as $bb_feedback) { 00562 // Added line $feedback=null so that $feedback does not get reused in the loop 00563 // and added the the $feedbacks[] array multiple times 00564 $feedback = null; 00565 $feedback->ident = $bb_feedback['@']['ident']; 00566 if (isset($bb_feedback['#']['flow_mat'][0])) { 00567 $this->process_block($bb_feedback['#']['flow_mat'][0], $feedback); 00568 } 00569 elseif (isset($bb_feedback['#']['solution'][0]['#']['solutionmaterial'][0]['#']['flow_mat'][0])) { 00570 $this->process_block($bb_feedback['#']['solution'][0]['#']['solutionmaterial'][0]['#']['flow_mat'][0], $feedback); 00571 } 00572 $feedbacks[] = $feedback; 00573 } 00574 } 00575 00579 function process_common( $quest ) { 00580 $question = $this->defaultquestion(); 00581 $question->questiontext = $quest->QUESTION_BLOCK->text; 00582 $question->name = shorten_text( $quest->id, 250 ); 00583 00584 return $question; 00585 } 00586 00587 //---------------------------------------- 00588 // Process True / False Questions 00589 //---------------------------------------- 00590 function process_tf($quest, &$questions) { 00591 $question = $this->process_common( $quest ); 00592 00593 $question->qtype = TRUEFALSE; 00594 $question->single = 1; // Only one answer is allowed 00595 // 0th [response] is the correct answer. 00596 $responses = $quest->responses; 00597 $correctresponse = $responses[0]->ident[0]['varequal'][0]['#']; 00598 if ($correctresponse != 'false') { 00599 $correct = true; 00600 } 00601 else { 00602 $correct = false; 00603 } 00604 00605 foreach($quest->feedback as $fb) { 00606 $fback->{$fb->ident} = $fb->text; 00607 } 00608 00609 if ($correct) { // true is correct 00610 $question->answer = 1; 00611 $question->feedbacktrue = $fback->correct; 00612 $question->feedbackfalse = $fback->incorrect; 00613 } else { // false is correct 00614 $question->answer = 0; 00615 $question->feedbacktrue = $fback->incorrect; 00616 $question->feedbackfalse = $fback->correct; 00617 } 00618 $question->correctanswer = $question->answer; 00619 $questions[] = $question; 00620 } 00621 00622 00623 //---------------------------------------- 00624 // Process Fill in the Blank 00625 //---------------------------------------- 00626 function process_fblank($quest, &$questions) { 00627 $question = $this->process_common( $quest ); 00628 $question->qtype = SHORTANSWER; 00629 $question->single = 1; 00630 00631 $answers = array(); 00632 $fractions = array(); 00633 $feedbacks = array(); 00634 00635 // extract the feedback 00636 $feedback = array(); 00637 foreach($quest->feedback as $fback) { 00638 if (isset($fback->ident)) { 00639 if ($fback->ident == 'correct' || $fback->ident == 'incorrect') { 00640 $feedback[$fback->ident] = $fback->text; 00641 } 00642 } 00643 } 00644 00645 foreach($quest->responses as $response) { 00646 if(isset($response->title)) { 00647 if (isset($response->ident[0]['varequal'][0]['#'])) { 00648 //for BB Fill in the Blank, only interested in correct answers 00649 if ($response->feedback = 'correct') { 00650 $answers[] = $response->ident[0]['varequal'][0]['#']; 00651 $fractions[] = 1; 00652 if (isset($feedback['correct'])) { 00653 $feedbacks[] = $feedback['correct']; 00654 } 00655 else { 00656 $feedbacks[] = ''; 00657 } 00658 } 00659 } 00660 00661 } 00662 } 00663 00664 //Adding catchall to so that students can see feedback for incorrect answers when they enter something the 00665 //instructor did not enter 00666 $answers[] = '*'; 00667 $fractions[] = 0; 00668 if (isset($feedback['incorrect'])) { 00669 $feedbacks[] = $feedback['incorrect']; 00670 } 00671 else { 00672 $feedbacks[] = ''; 00673 } 00674 00675 $question->answer = $answers; 00676 $question->fraction = $fractions; 00677 $question->feedback = $feedbacks; // Changed to assign $feedbacks to $question->feedback instead of 00678 00679 if (!empty($question)) { 00680 $questions[] = $question; 00681 } 00682 00683 } 00684 00685 //---------------------------------------- 00686 // Process Multiple Choice Questions 00687 //---------------------------------------- 00688 function process_mc($quest, &$questions) { 00689 $question = $this->process_common( $quest ); 00690 $question->qtype = MULTICHOICE; 00691 $question->single = 1; 00692 00693 $feedback = array(); 00694 foreach($quest->feedback as $fback) { 00695 $feedback[$fback->ident] = $fback->text; 00696 } 00697 00698 foreach($quest->responses as $response) { 00699 if (isset($response->title)) { 00700 if ($response->title == 'correct') { 00701 // only one answer possible for this qtype so first index is correct answer 00702 $correct = $response->ident[0]['varequal'][0]['#']; 00703 } 00704 } 00705 else { 00706 // fallback method for when the title is not set 00707 if ($response->feedback == 'correct') { 00708 // only one answer possible for this qtype so first index is correct answer 00709 $correct = $response->ident[0]['varequal'][0]['#']; // added [0]['varequal'][0]['#'] to $response->ident - CT 8/9/06 00710 } 00711 } 00712 } 00713 00714 $i = 0; 00715 foreach($quest->RESPONSE_BLOCK->choices as $response) { 00716 $question->answer[$i] = $response->text; 00717 if ($correct == $response->ident) { 00718 $question->fraction[$i] = 1; 00719 // this is a bit of a hack to catch the feedback... first we see if a 'correct' feedback exists 00720 // then specific feedback for this question (maybe this should be switched?, but from my example 00721 // question pools I have not seen response specific feedback, only correct or incorrect feedback 00722 if (!empty($feedback['correct'])) { 00723 $question->feedback[$i] = $feedback['correct']; 00724 } 00725 elseif (!empty($feedback[$i])) { 00726 $question->feedback[$i] = $feedback[$i]; 00727 } 00728 else { 00729 // failsafe feedback (should be '' instead?) 00730 $question->feedback[$i] = "correct"; 00731 } 00732 } 00733 else { 00734 $question->fraction[$i] = 0; 00735 if (!empty($feedback['incorrect'])) { 00736 $question->feedback[$i] = $feedback['incorrect']; 00737 } 00738 elseif (!empty($feedback[$i])) { 00739 $question->feedback[$i] = $feedback[$i]; 00740 } 00741 else { 00742 // failsafe feedback (should be '' instead?) 00743 $question->feedback[$i] = 'incorrect'; 00744 } 00745 } 00746 $i++; 00747 } 00748 00749 if (!empty($question)) { 00750 $questions[] = $question; 00751 } 00752 } 00753 00754 //---------------------------------------- 00755 // Process Multiple Choice Questions With Multiple Answers 00756 //---------------------------------------- 00757 function process_ma($quest, &$questions) { 00758 $question = $this->process_common( $quest ); // copied this from process_mc 00759 $question->qtype = MULTICHOICE; 00760 $question->single = 0; // More than one answer allowed 00761 00762 $answers = $quest->responses; 00763 $correct_answers = array(); 00764 foreach($answers as $answer) { 00765 if($answer->title == 'correct') { 00766 $answerset = $answer->ident[0]['and'][0]['#']['varequal']; 00767 foreach($answerset as $ans) { 00768 $correct_answers[] = $ans['#']; 00769 } 00770 } 00771 } 00772 00773 foreach ($quest->feedback as $fb) { 00774 $feedback->{$fb->ident} = trim($fb->text); 00775 } 00776 00777 $correct_answer_count = count($correct_answers); 00778 $choiceset = $quest->RESPONSE_BLOCK->choices; 00779 $i = 0; 00780 foreach($choiceset as $choice) { 00781 $question->answer[$i] = trim($choice->text); 00782 if (in_array($choice->ident, $correct_answers)) { 00783 // correct answer 00784 $question->fraction[$i] = floor(100000/$correct_answer_count)/100000; // strange behavior if we have more than 5 decimal places 00785 $question->feedback[$i] = $feedback->correct; 00786 } 00787 else { 00788 // wrong answer 00789 $question->fraction[$i] = 0; 00790 $question->feedback[$i] = $feedback->incorrect; 00791 } 00792 $i++; 00793 } 00794 00795 $questions[] = $question; 00796 } 00797 00798 //---------------------------------------- 00799 // Process Essay Questions 00800 //---------------------------------------- 00801 function process_essay($quest, &$questions) { 00802 // this should be rewritten to accomodate moodle 1.6 essay question type eventually 00803 00804 if (defined("ESSAY")) { 00805 // treat as short answer 00806 $question = $this->process_common( $quest ); // copied this from process_mc 00807 $question->qtype = ESSAY; 00808 00809 $question->feedback = array(); 00810 // not sure where to get the correct answer from 00811 foreach($quest->feedback as $feedback) { 00812 // Added this code to put the possible solution that the 00813 // instructor gives as the Moodle answer for an essay question 00814 if ($feedback->ident == 'solution') { 00815 $question->feedback = $feedback->text; 00816 } 00817 } 00818 //Added because essay/questiontype.php:save_question_option is expecting a 00819 //fraction property - CT 8/10/06 00820 $question->fraction[] = 1; 00821 if (!empty($question)) { 00822 $questions[]=$question; 00823 } 00824 } 00825 else { 00826 print "Essay question types are not handled because the quiz question type 'Essay' does not exist in this installation of Moodle<br/>"; 00827 print " Omitted Question: ".$quest->QUESTION_BLOCK->text.'<br/><br/>'; 00828 } 00829 } 00830 00831 //---------------------------------------- 00832 // Process Matching Questions 00833 //---------------------------------------- 00834 function process_matching($quest, &$questions) { 00835 // renderedmatch is an optional plugin, so we need to check if it is defined 00836 if (question_bank::is_qtype_installed('renderedmatch')) { 00837 $question = $this->process_common($quest); 00838 $question->valid = true; 00839 $question->qtype = 'renderedmatch'; 00840 00841 foreach($quest->RESPONSE_BLOCK->subquestions as $qid => $subq) { 00842 foreach($quest->responses as $rid => $resp) { 00843 if ($resp->ident == $subq->ident) { 00844 $correct = $resp->correct; 00845 $feedback = $resp->feedback; 00846 } 00847 } 00848 00849 foreach($subq->choices as $cid => $choice) { 00850 if ($choice == $correct) { 00851 $question->subquestions[] = $subq->text; 00852 $question->subanswers[] = $quest->RIGHT_MATCH_BLOCK->matching_answerset[$cid]->text; 00853 } 00854 } 00855 } 00856 00857 // check format 00858 $status = true; 00859 if ( count($quest->RESPONSE_BLOCK->subquestions) > count($quest->RIGHT_MATCH_BLOCK->matching_answerset) || count($question->subquestions) < 2) { 00860 $status = false; 00861 } 00862 else { 00863 // need to redo to make sure that no two questions have the same answer (rudimentary now) 00864 foreach($question->subanswers as $qstn) { 00865 if(isset($previous)) { 00866 if ($qstn == $previous) { 00867 $status = false; 00868 } 00869 } 00870 $previous = $qstn; 00871 if ($qstn == '') { 00872 $status = false; 00873 } 00874 } 00875 } 00876 00877 if ($status) { 00878 $questions[] = $question; 00879 } 00880 else { 00881 global $COURSE, $CFG; 00882 print '<table class="boxaligncenter" border="1">'; 00883 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>'; 00884 00885 print "<tr><td>Question:</td><td>".$quest->QUESTION_BLOCK->text; 00886 if (isset($quest->QUESTION_BLOCK->file)) { 00887 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>'; 00888 if (preg_match('/(gif|jpg|jpeg|png)$/i', $quest->QUESTION_BLOCK->file)) { 00889 print '<img src="'.$CFG->wwwroot.'/file.php/'.$COURSE->id.'/bb_import/'.basename($quest->QUESTION_BLOCK->file).'" />'; 00890 } 00891 } 00892 print "</td></tr>"; 00893 print "<tr><td>Subquestions:</td><td><ul>"; 00894 foreach($quest->responses as $rs) { 00895 $correct_responses->{$rs->ident} = $rs->correct; 00896 } 00897 foreach($quest->RESPONSE_BLOCK->subquestions as $subq) { 00898 print '<li>'.$subq->text.'<ul>'; 00899 foreach($subq->choices as $id=>$choice) { 00900 print '<li>'; 00901 if ($choice == $correct_responses->{$subq->ident}) { 00902 print '<font color="green">'; 00903 } 00904 else { 00905 print '<font color="red">'; 00906 } 00907 print $quest->RIGHT_MATCH_BLOCK->matching_answerset[$id]->text.'</font></li>'; 00908 } 00909 print '</ul>'; 00910 } 00911 print '</ul></td></tr>'; 00912 00913 print '<tr><td>Feedback:</td><td><ul>'; 00914 foreach($quest->feedback as $fb) { 00915 print '<li>'.$fb->ident.': '.$fb->text.'</li>'; 00916 } 00917 print '</ul></td></tr></table>'; 00918 } 00919 } 00920 else { 00921 print "Matching question types are not handled because the quiz question type 'Rendered Matching' does not exist in this installation of Moodle<br/>"; 00922 print " Omitted Question: ".$quest->QUESTION_BLOCK->text.'<br/><br/>'; 00923 } 00924 } 00925 00926 00927 function strip_applet_tags_get_mathml($string) { 00928 if(stristr($string, '</APPLET>') === FALSE) { 00929 return $string; 00930 } 00931 else { 00932 // strip all applet tags keeping stuff before/after and inbetween (if mathml) them 00933 while (stristr($string, '</APPLET>') !== FALSE) { 00934 preg_match("/(.*)<applet.*value=\"(<math>.*<\/math>)\".*<\/applet>(.*)/i",$string, $mathmls); 00935 $string = $mathmls[1].$mathmls[2].$mathmls[3]; 00936 } 00937 return $string; 00938 } 00939 } 00940 00941 } // close object 00942