|
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 if (!class_exists('qformat_default')) { 00031 // This is ugly, but this class is also (ab)used by mod/lesson, which defines 00032 // a different base class in mod/lesson/format.php. Thefore, we can only 00033 // include the proper base class conditionally like this. (We have to include 00034 // the base class like this, otherwise it breaks third-party question types.) 00035 // This may be reviewd, and a better fix found one day. 00036 require_once($CFG->dirroot . '/question/format.php'); 00037 } 00038 00039 00048 class qformat_xml extends qformat_default { 00049 00050 public function provide_import() { 00051 return true; 00052 } 00053 00054 public function provide_export() { 00055 return true; 00056 } 00057 00058 public function mime_type() { 00059 return 'application/xml'; 00060 } 00061 00062 // IMPORT FUNCTIONS START HERE 00063 00070 public function trans_format($name) { 00071 $name = trim($name); 00072 00073 if ($name == 'moodle_auto_format') { 00074 return FORMAT_MOODLE; 00075 } else if ($name == 'html') { 00076 return FORMAT_HTML; 00077 } else if ($name == 'plain_text') { 00078 return FORMAT_PLAIN; 00079 } else if ($name == 'wiki_like') { 00080 return FORMAT_WIKI; 00081 } else if ($name == 'markdown') { 00082 return FORMAT_MARKDOWN; 00083 } else { 00084 debugging("Unrecognised text format '{$name}' in the import file. Assuming 'html'."); 00085 return FORMAT_HTML; 00086 } 00087 } 00088 00095 public function trans_single($name) { 00096 $name = trim($name); 00097 if ($name == "false" || !$name) { 00098 return 0; 00099 } else { 00100 return 1; 00101 } 00102 } 00103 00109 public function import_text($text) { 00110 // quick sanity check 00111 if (empty($text)) { 00112 return ''; 00113 } 00114 $data = $text[0]['#']; 00115 return trim($data); 00116 } 00117 00128 public function getpath($xml, $path, $default, $istext=false, $error='') { 00129 foreach ($path as $index) { 00130 if (!isset($xml[$index])) { 00131 if (!empty($error)) { 00132 $this->error($error); 00133 return false; 00134 } else { 00135 return $default; 00136 } 00137 } 00138 00139 $xml = $xml[$index]; 00140 } 00141 00142 if ($istext) { 00143 if (!is_string($xml)) { 00144 $this->error(get_string('invalidxml', 'qformat_xml')); 00145 } 00146 $xml = trim($xml); 00147 } 00148 00149 return $xml; 00150 } 00151 00152 00158 public function import_headers($question) { 00159 global $CFG; 00160 00161 // get some error strings 00162 $error_noname = get_string('xmlimportnoname', 'qformat_xml'); 00163 $error_noquestion = get_string('xmlimportnoquestion', 'qformat_xml'); 00164 00165 // this routine initialises the question object 00166 $qo = $this->defaultquestion(); 00167 00168 // Question name 00169 $qo->name = $this->getpath($question, 00170 array('#', 'name', 0, '#', 'text', 0, '#'), '', true, 00171 get_string('xmlimportnoname', 'qformat_xml')); 00172 $qo->questiontext = $this->getpath($question, 00173 array('#', 'questiontext', 0, '#', 'text', 0, '#'), '', true); 00174 $qo->questiontextformat = $this->trans_format($this->getpath( 00175 $question, array('#', 'questiontext', 0, '@', 'format'), 'html')); 00176 00177 $qo->questiontextfiles = $this->import_files($this->getpath($question, 00178 array('#', 'questiontext', 0, '#', 'file'), array(), false)); 00179 00180 // Backwards compatibility, deal with the old image tag. 00181 $filedata = $this->getpath($question, array('#', 'image_base64', '0', '#'), null, false); 00182 $filename = $this->getpath($question, array('#', 'image', '0', '#'), null, false); 00183 if ($filedata && $filename) { 00184 $data = new stdClass(); 00185 $data->content = $filedata; 00186 $data->encoding = 'base64'; 00187 // Question file areas don't support subdirs, so convert path to filename if necessary. 00188 $data->name = clean_param(str_replace('/', '_', $filename), PARAM_FILE); 00189 $qo->questiontextfiles[] = $data; 00190 $qo->questiontext .= ' <img src="@@PLUGINFILE@@/' . $data->name . '" />'; 00191 } 00192 00193 // restore files in generalfeedback 00194 $qo->generalfeedback = $this->getpath($question, 00195 array('#', 'generalfeedback', 0, '#', 'text', 0, '#'), $qo->generalfeedback, true); 00196 $qo->generalfeedbackfiles = array(); 00197 $qo->generalfeedbackformat = $this->trans_format($this->getpath($question, 00198 array('#', 'generalfeedback', 0, '@', 'format'), $this->get_format($qo->questiontextformat))); 00199 $qo->generalfeedbackfiles = $this->import_files($this->getpath($question, 00200 array('#', 'generalfeedback', 0, '#', 'file'), array(), false)); 00201 00202 $qo->defaultmark = $this->getpath($question, 00203 array('#', 'defaultgrade', 0, '#'), $qo->defaultmark); 00204 $qo->penalty = $this->getpath($question, 00205 array('#', 'penalty', 0, '#'), $qo->penalty); 00206 00207 // Fix problematic rounding from old files: 00208 if (abs($qo->penalty - 0.3333333) < 0.005) { 00209 $qo->penalty = 0.3333333; 00210 } 00211 00212 // Read the question tags. 00213 if (!empty($CFG->usetags) && array_key_exists('tags', $question['#']) 00214 && !empty($question['#']['tags'][0]['#']['tag'])) { 00215 require_once($CFG->dirroot.'/tag/lib.php'); 00216 $qo->tags = array(); 00217 foreach ($question['#']['tags'][0]['#']['tag'] as $tagdata) { 00218 $qo->tags[] = $this->getpath($tagdata, array('#', 'text', 0, '#'), '', true); 00219 } 00220 } 00221 00222 return $qo; 00223 } 00224 00234 public function import_answer($answer, $withanswerfiles = false, $defaultformat = 'html') { 00235 $ans = new stdClass(); 00236 00237 $ans->answer = array(); 00238 $ans->answer['text'] = $this->getpath($answer, array('#', 'text', 0, '#'), '', true); 00239 $ans->answer['format'] = $this->trans_format($this->getpath($answer, 00240 array('@', 'format'), $defaultformat)); 00241 if ($withanswerfiles) { 00242 $ans->answer['files'] = $this->import_files($this->getpath($answer, 00243 array('#', 'file'), array())); 00244 } else { 00245 $ans->answer['format'] = FORMAT_PLAIN; 00246 } 00247 00248 $ans->feedback = array(); 00249 $ans->feedback['text'] = $this->getpath($answer, 00250 array('#', 'feedback', 0, '#', 'text', 0, '#'), '', true); 00251 $ans->feedback['format'] = $this->trans_format($this->getpath($answer, 00252 array('#', 'feedback', 0, '@', 'format'), $defaultformat)); 00253 $ans->feedback['files'] = $this->import_files($this->getpath($answer, 00254 array('#', 'feedback', 0, '#', 'file'), array())); 00255 00256 $ans->fraction = $this->getpath($answer, array('@', 'fraction'), 0) / 100; 00257 00258 return $ans; 00259 } 00260 00267 public function import_combined_feedback($qo, $questionxml, $withshownumpartscorrect = false) { 00268 $fields = array('correctfeedback', 'partiallycorrectfeedback', 'incorrectfeedback'); 00269 foreach ($fields as $field) { 00270 $text = array(); 00271 $text['text'] = $this->getpath($questionxml, 00272 array('#', $field, 0, '#', 'text', 0, '#'), '', true); 00273 $text['format'] = $this->trans_format($this->getpath($questionxml, 00274 array('#', $field, 0, '@', 'format'), $this->get_format($qo->questiontextformat))); 00275 $text['files'] = $this->import_files($this->getpath($questionxml, 00276 array('#', $field, 0, '#', 'file'), array(), false)); 00277 00278 $qo->$field = $text; 00279 } 00280 00281 if ($withshownumpartscorrect) { 00282 $qo->shownumcorrect = array_key_exists('shownumcorrect', $questionxml['#']); 00283 00284 // Backwards compatibility: 00285 if (array_key_exists('correctresponsesfeedback', $questionxml['#'])) { 00286 $qo->shownumcorrect = $this->trans_single($this->getpath($questionxml, 00287 array('#', 'correctresponsesfeedback', 0, '#'), 1)); 00288 } 00289 } 00290 } 00291 00298 public function import_hint($hintxml, $defaultformat) { 00299 if (array_key_exists('hintcontent', $hintxml['#'])) { 00300 // Backwards compatibility: 00301 00302 $hint = new stdClass(); 00303 $hint->hint = array('format' => FORMAT_HTML, 'files' => array()); 00304 $hint->hint['text'] = $this->getpath($hintxml, 00305 array('#', 'hintcontent', 0, '#', 'text', 0, '#'), '', true); 00306 $hint->hint['format'] = $this->trans_format($defaultformat); 00307 $hint->hint['files'] = array(); 00308 $hint->shownumcorrect = $this->getpath($hintxml, 00309 array('#', 'statenumberofcorrectresponses', 0, '#'), 0); 00310 $hint->clearwrong = $this->getpath($hintxml, 00311 array('#', 'clearincorrectresponses', 0, '#'), 0); 00312 $hint->options = $this->getpath($hintxml, 00313 array('#', 'showfeedbacktoresponses', 0, '#'), 0); 00314 00315 return $hint; 00316 } 00317 00318 $hint = new stdClass(); 00319 $hint->hint['text'] = $this->getpath($hintxml, 00320 array('#', 'text', 0, '#'), '', true); 00321 $hint->hint['format'] = $this->trans_format($this->getpath($hintxml, 00322 array('@', 'format'), $defaultformat)); 00323 $hint->hint['files'] = $this->import_files($this->getpath($hintxml, 00324 array('#', 'file'), array(), false)); 00325 $hint->shownumcorrect = array_key_exists('shownumcorrect', $hintxml['#']); 00326 $hint->clearwrong = array_key_exists('clearwrong', $hintxml['#']); 00327 $hint->options = $this->getpath($hintxml, array('#', 'options', 0, '#'), '', true); 00328 00329 return $hint; 00330 } 00331 00342 public function import_hints($qo, $questionxml, $withparts = false, 00343 $withoptions = false, $defaultformat = 'html') { 00344 if (!isset($questionxml['#']['hint'])) { 00345 return; 00346 } 00347 00348 foreach ($questionxml['#']['hint'] as $hintxml) { 00349 $hint = $this->import_hint($hintxml, $defaultformat); 00350 $qo->hint[] = $hint->hint; 00351 00352 if ($withparts) { 00353 $qo->hintshownumcorrect[] = $hint->shownumcorrect; 00354 $qo->hintclearwrong[] = $hint->clearwrong; 00355 } 00356 00357 if ($withoptions) { 00358 $qo->hintoptions[] = $hint->options; 00359 } 00360 } 00361 } 00362 00368 public function import_files($xml) { 00369 $files = array(); 00370 foreach ($xml as $file) { 00371 $data = new stdClass(); 00372 $data->content = $file['#']; 00373 $data->encoding = $file['@']['encoding']; 00374 $data->name = $file['@']['name']; 00375 $files[] = $data; 00376 } 00377 return $files; 00378 } 00379 00385 public function import_multichoice($question) { 00386 // get common parts 00387 $qo = $this->import_headers($question); 00388 00389 // 'header' parts particular to multichoice 00390 $qo->qtype = MULTICHOICE; 00391 $single = $this->getpath($question, array('#', 'single', 0, '#'), 'true'); 00392 $qo->single = $this->trans_single($single); 00393 $shuffleanswers = $this->getpath($question, 00394 array('#', 'shuffleanswers', 0, '#'), 'false'); 00395 $qo->answernumbering = $this->getpath($question, 00396 array('#', 'answernumbering', 0, '#'), 'abc'); 00397 $qo->shuffleanswers = $this->trans_single($shuffleanswers); 00398 00399 // There was a time on the 1.8 branch when it could output an empty 00400 // answernumbering tag, so fix up any found. 00401 if (empty($qo->answernumbering)) { 00402 $qo->answernumbering = 'abc'; 00403 } 00404 00405 // Run through the answers 00406 $answers = $question['#']['answer']; 00407 $acount = 0; 00408 foreach ($answers as $answer) { 00409 $ans = $this->import_answer($answer, true, $this->get_format($qo->questiontextformat)); 00410 $qo->answer[$acount] = $ans->answer; 00411 $qo->fraction[$acount] = $ans->fraction; 00412 $qo->feedback[$acount] = $ans->feedback; 00413 ++$acount; 00414 } 00415 00416 $this->import_combined_feedback($qo, $question, true); 00417 $this->import_hints($qo, $question, true, false, $this->get_format($qo->questiontextformat)); 00418 00419 return $qo; 00420 } 00421 00427 public function import_multianswer($question) { 00428 question_bank::get_qtype('multianswer'); 00429 00430 $questiontext['text'] = $this->import_text($question['#']['questiontext'][0]['#']['text']); 00431 $questiontext['format'] = FORMAT_HTML; 00432 $questiontext['itemid'] = ''; 00433 $qo = qtype_multianswer_extract_question($questiontext); 00434 00435 // 'header' parts particular to multianswer 00436 $qo->qtype = 'multianswer'; 00437 $qo->course = $this->course; 00438 $qo->generalfeedback = ''; 00439 00440 $qo->name = $this->import_text($question['#']['name'][0]['#']['text']); 00441 $qo->questiontextformat = $questiontext['format']; 00442 $qo->questiontext = $qo->questiontext['text']; 00443 $qo->questiontextfiles = array(); 00444 00445 // restore files in generalfeedback 00446 $qo->generalfeedback = $this->getpath($question, 00447 array('#', 'generalfeedback', 0, '#', 'text', 0, '#'), $qo->generalfeedback, true); 00448 $qo->generalfeedbackformat = $this->trans_format($this->getpath($question, 00449 array('#', 'generalfeedback', 0, '@', 'format'), $this->get_format($qo->questiontextformat))); 00450 $qo->generalfeedbackfiles = $this->import_files($this->getpath($question, 00451 array('#', 'generalfeedback', 0, '#', 'file'), array(), false)); 00452 00453 $qo->penalty = $this->getpath($question, 00454 array('#', 'penalty', 0, '#'), $this->defaultquestion()->penalty); 00455 // Fix problematic rounding from old files: 00456 if (abs($qo->penalty - 0.3333333) < 0.005) { 00457 $qo->penalty = 0.3333333; 00458 } 00459 00460 $this->import_hints($qo, $question, false, false, $this->get_format($qo->questiontextformat)); 00461 00462 return $qo; 00463 } 00464 00470 public function import_truefalse($question) { 00471 // get common parts 00472 global $OUTPUT; 00473 $qo = $this->import_headers($question); 00474 00475 // 'header' parts particular to true/false 00476 $qo->qtype = TRUEFALSE; 00477 00478 // In the past, it used to be assumed that the two answers were in the file 00479 // true first, then false. Howevever that was not always true. Now, we 00480 // try to match on the answer text, but in old exports, this will be a localised 00481 // string, so if we don't find true or false, we fall back to the old system. 00482 $first = true; 00483 $warning = false; 00484 foreach ($question['#']['answer'] as $answer) { 00485 $answertext = $this->getpath($answer, 00486 array('#', 'text', 0, '#'), '', true); 00487 $feedback = $this->getpath($answer, 00488 array('#', 'feedback', 0, '#', 'text', 0, '#'), '', true); 00489 $feedbackformat = $this->getpath($answer, 00490 array('#', 'feedback', 0, '@', 'format'), $this->get_format($qo->questiontextformat)); 00491 $feedbackfiles = $this->getpath($answer, 00492 array('#', 'feedback', 0, '#', 'file'), array()); 00493 $files = array(); 00494 foreach ($feedbackfiles as $file) { 00495 $data = new stdClass(); 00496 $data->content = $file['#']; 00497 $data->encoding = $file['@']['encoding']; 00498 $data->name = $file['@']['name']; 00499 $files[] = $data; 00500 } 00501 if ($answertext != 'true' && $answertext != 'false') { 00502 // Old style file, assume order is true/false. 00503 $warning = true; 00504 if ($first) { 00505 $answertext = 'true'; 00506 } else { 00507 $answertext = 'false'; 00508 } 00509 } 00510 00511 if ($answertext == 'true') { 00512 $qo->answer = ($answer['@']['fraction'] == 100); 00513 $qo->correctanswer = $qo->answer; 00514 $qo->feedbacktrue = array(); 00515 $qo->feedbacktrue['text'] = $feedback; 00516 $qo->feedbacktrue['format'] = $this->trans_format($feedbackformat); 00517 $qo->feedbacktrue['files'] = $files; 00518 } else { 00519 $qo->answer = ($answer['@']['fraction'] != 100); 00520 $qo->correctanswer = $qo->answer; 00521 $qo->feedbackfalse = array(); 00522 $qo->feedbackfalse['text'] = $feedback; 00523 $qo->feedbackfalse['format'] = $this->trans_format($feedbackformat); 00524 $qo->feedbackfalse['files'] = $files; 00525 } 00526 $first = false; 00527 } 00528 00529 if ($warning) { 00530 $a = new stdClass(); 00531 $a->questiontext = $qo->questiontext; 00532 $a->answer = get_string($qo->correctanswer ? 'true' : 'false', 'qtype_truefalse'); 00533 echo $OUTPUT->notification(get_string('truefalseimporterror', 'qformat_xml', $a)); 00534 } 00535 00536 $this->import_hints($qo, $question, false, false, $this->get_format($qo->questiontextformat)); 00537 00538 return $qo; 00539 } 00540 00546 public function import_shortanswer($question) { 00547 // get common parts 00548 $qo = $this->import_headers($question); 00549 00550 // header parts particular to shortanswer 00551 $qo->qtype = SHORTANSWER; 00552 00553 // get usecase 00554 $qo->usecase = $this->getpath($question, array('#', 'usecase', 0, '#'), $qo->usecase); 00555 00556 // Run through the answers 00557 $answers = $question['#']['answer']; 00558 $acount = 0; 00559 foreach ($answers as $answer) { 00560 $ans = $this->import_answer($answer, false, $this->get_format($qo->questiontextformat)); 00561 $qo->answer[$acount] = $ans->answer['text']; 00562 $qo->fraction[$acount] = $ans->fraction; 00563 $qo->feedback[$acount] = $ans->feedback; 00564 ++$acount; 00565 } 00566 00567 $this->import_hints($qo, $question, false, false, $this->get_format($qo->questiontextformat)); 00568 00569 return $qo; 00570 } 00571 00577 public function import_description($question) { 00578 // get common parts 00579 $qo = $this->import_headers($question); 00580 // header parts particular to shortanswer 00581 $qo->qtype = DESCRIPTION; 00582 $qo->defaultmark = 0; 00583 $qo->length = 0; 00584 return $qo; 00585 } 00586 00592 public function import_numerical($question) { 00593 // get common parts 00594 $qo = $this->import_headers($question); 00595 00596 // header parts particular to numerical 00597 $qo->qtype = NUMERICAL; 00598 00599 // get answers array 00600 $answers = $question['#']['answer']; 00601 $qo->answer = array(); 00602 $qo->feedback = array(); 00603 $qo->fraction = array(); 00604 $qo->tolerance = array(); 00605 foreach ($answers as $answer) { 00606 // answer outside of <text> is deprecated 00607 $obj = $this->import_answer($answer, false, $this->get_format($qo->questiontextformat)); 00608 $qo->answer[] = $obj->answer['text']; 00609 if (empty($qo->answer)) { 00610 $qo->answer = '*'; 00611 } 00612 $qo->feedback[] = $obj->feedback; 00613 $qo->tolerance[] = $this->getpath($answer, array('#', 'tolerance', 0, '#'), 0); 00614 00615 // fraction as a tag is deprecated 00616 $fraction = $this->getpath($answer, array('@', 'fraction'), 0) / 100; 00617 $qo->fraction[] = $this->getpath($answer, 00618 array('#', 'fraction', 0, '#'), $fraction); // deprecated 00619 } 00620 00621 // Get the units array 00622 $qo->unit = array(); 00623 $units = $this->getpath($question, array('#', 'units', 0, '#', 'unit'), array()); 00624 if (!empty($units)) { 00625 $qo->multiplier = array(); 00626 foreach ($units as $unit) { 00627 $qo->multiplier[] = $this->getpath($unit, array('#', 'multiplier', 0, '#'), 1); 00628 $qo->unit[] = $this->getpath($unit, array('#', 'unit_name', 0, '#'), '', true); 00629 } 00630 } 00631 $qo->unitgradingtype = $this->getpath($question, array('#', 'unitgradingtype', 0, '#'), 0); 00632 $qo->unitpenalty = $this->getpath($question, array('#', 'unitpenalty', 0, '#'), 0.1); 00633 $qo->showunits = $this->getpath($question, array('#', 'showunits', 0, '#'), null); 00634 $qo->unitsleft = $this->getpath($question, array('#', 'unitsleft', 0, '#'), 0); 00635 $qo->instructions['text'] = ''; 00636 $qo->instructions['format'] = FORMAT_HTML; 00637 $instructions = $this->getpath($question, array('#', 'instructions'), array()); 00638 if (!empty($instructions)) { 00639 $qo->instructions = array(); 00640 $qo->instructions['text'] = $this->getpath($instructions, 00641 array('0', '#', 'text', '0', '#'), '', true); 00642 $qo->instructions['format'] = $this->trans_format($this->getpath($instructions, 00643 array('0', '@', 'format'), $this->get_format($qo->questiontextformat))); 00644 $qo->instructions['files'] = $this->import_files($this->getpath( 00645 $instructions, array('0', '#', 'file'), array())); 00646 } 00647 00648 if (is_null($qo->showunits)) { 00649 // Set a good default, depending on whether there are any units defined. 00650 if (empty($qo->unit)) { 00651 $qo->showunits = 3; // qtype_numerical::UNITNONE; 00652 } else { 00653 $qo->showunits = 0; // qtype_numerical::UNITOPTIONAL; 00654 } 00655 } 00656 00657 $this->import_hints($qo, $question, false, false, $this->get_format($qo->questiontextformat)); 00658 00659 return $qo; 00660 } 00661 00667 public function import_match($question) { 00668 // get common parts 00669 $qo = $this->import_headers($question); 00670 00671 // header parts particular to matching 00672 $qo->qtype = 'match'; 00673 $qo->shuffleanswers = $this->trans_single($this->getpath($question, 00674 array('#', 'shuffleanswers', 0, '#'), 1)); 00675 00676 // run through subquestions 00677 $qo->subquestions = array(); 00678 $qo->subanswers = array(); 00679 foreach ($question['#']['subquestion'] as $subqxml) { 00680 $subquestion = array(); 00681 $subquestion['text'] = $this->getpath($subqxml, array('#', 'text', 0, '#'), '', true); 00682 $subquestion['format'] = $this->trans_format($this->getpath($subqxml, 00683 array('@', 'format'), $this->get_format($qo->questiontextformat))); 00684 $subquestion['files'] = $this->import_files($this->getpath($subqxml, 00685 array('#', 'file'), array())); 00686 00687 $qo->subquestions[] = $subquestion; 00688 $answers = $this->getpath($subqxml, array('#', 'answer'), array()); 00689 $qo->subanswers[] = $this->getpath($subqxml, 00690 array('#', 'answer', 0, '#', 'text', 0, '#'), '', true); 00691 } 00692 00693 $this->import_combined_feedback($qo, $question, true); 00694 $this->import_hints($qo, $question, true, false, $this->get_format($qo->questiontextformat)); 00695 00696 return $qo; 00697 } 00698 00704 public function import_essay($question) { 00705 // get common parts 00706 $qo = $this->import_headers($question); 00707 00708 // header parts particular to essay 00709 $qo->qtype = ESSAY; 00710 00711 $qo->responseformat = $this->getpath($question, 00712 array('#', 'responseformat', 0, '#'), 'editor'); 00713 $qo->responsefieldlines = $this->getpath($question, 00714 array('#', 'responsefieldlines', 0, '#'), 15); 00715 $qo->attachments = $this->getpath($question, 00716 array('#', 'attachments', 0, '#'), 0); 00717 $qo->graderinfo['text'] = $this->getpath($question, 00718 array('#', 'graderinfo', 0, '#', 'text', 0, '#'), '', true); 00719 $qo->graderinfo['format'] = $this->trans_format($this->getpath($question, 00720 array('#', 'graderinfo', 0, '@', 'format'), $this->get_format($qo->questiontextformat))); 00721 $qo->graderinfo['files'] = $this->import_files($this->getpath($question, 00722 array('#', 'graderinfo', '0', '#', 'file'), array())); 00723 00724 return $qo; 00725 } 00726 00731 public function import_calculated($question) { 00732 00733 // get common parts 00734 $qo = $this->import_headers($question); 00735 00736 // header parts particular to calculated 00737 $qo->qtype = CALCULATED; 00738 $qo->synchronize = $this->getpath($question, array('#', 'synchronize', 0, '#'), 0); 00739 $single = $this->getpath($question, array('#', 'single', 0, '#'), 'true'); 00740 $qo->single = $this->trans_single($single); 00741 $shuffleanswers = $this->getpath($question, array('#', 'shuffleanswers', 0, '#'), 'false'); 00742 $qo->answernumbering = $this->getpath($question, 00743 array('#', 'answernumbering', 0, '#'), 'abc'); 00744 $qo->shuffleanswers = $this->trans_single($shuffleanswers); 00745 00746 $this->import_combined_feedback($qo, $question); 00747 00748 $qo->unitgradingtype = $this->getpath($question, 00749 array('#', 'unitgradingtype', 0, '#'), 0); 00750 $qo->unitpenalty = $this->getpath($question, array('#', 'unitpenalty', 0, '#'), null); 00751 $qo->showunits = $this->getpath($question, array('#', 'showunits', 0, '#'), 0); 00752 $qo->unitsleft = $this->getpath($question, array('#', 'unitsleft', 0, '#'), 0); 00753 $qo->instructions = $this->getpath($question, 00754 array('#', 'instructions', 0, '#', 'text', 0, '#'), '', true); 00755 if (!empty($instructions)) { 00756 $qo->instructions = array(); 00757 $qo->instructions['text'] = $this->getpath($instructions, 00758 array('0', '#', 'text', '0', '#'), '', true); 00759 $qo->instructions['format'] = $this->trans_format($this->getpath($instructions, 00760 array('0', '@', 'format'), $this->get_format($qo->questiontextformat))); 00761 $qo->instructions['files'] = $this->import_files($this->getpath($instructions, 00762 array('0', '#', 'file'), array())); 00763 } 00764 00765 // get answers array 00766 $answers = $question['#']['answer']; 00767 $qo->answers = array(); 00768 $qo->feedback = array(); 00769 $qo->fraction = array(); 00770 $qo->tolerance = array(); 00771 $qo->tolerancetype = array(); 00772 $qo->correctanswerformat = array(); 00773 $qo->correctanswerlength = array(); 00774 $qo->feedback = array(); 00775 foreach ($answers as $answer) { 00776 $ans = $this->import_answer($answer, true, $this->get_format($qo->questiontextformat)); 00777 // answer outside of <text> is deprecated 00778 if (empty($ans->answer['text'])) { 00779 $ans->answer['text'] = '*'; 00780 } 00781 $qo->answers[] = $ans->answer; 00782 $qo->feedback[] = $ans->feedback; 00783 $qo->tolerance[] = $answer['#']['tolerance'][0]['#']; 00784 // fraction as a tag is deprecated 00785 if (!empty($answer['#']['fraction'][0]['#'])) { 00786 $qo->fraction[] = $answer['#']['fraction'][0]['#']; 00787 } else { 00788 $qo->fraction[] = $answer['@']['fraction'] / 100; 00789 } 00790 $qo->tolerancetype[] = $answer['#']['tolerancetype'][0]['#']; 00791 $qo->correctanswerformat[] = $answer['#']['correctanswerformat'][0]['#']; 00792 $qo->correctanswerlength[] = $answer['#']['correctanswerlength'][0]['#']; 00793 } 00794 // get units array 00795 $qo->unit = array(); 00796 if (isset($question['#']['units'][0]['#']['unit'])) { 00797 $units = $question['#']['units'][0]['#']['unit']; 00798 $qo->multiplier = array(); 00799 foreach ($units as $unit) { 00800 $qo->multiplier[] = $unit['#']['multiplier'][0]['#']; 00801 $qo->unit[] = $unit['#']['unit_name'][0]['#']; 00802 } 00803 } 00804 $instructions = $this->getpath($question, array('#', 'instructions'), array()); 00805 if (!empty($instructions)) { 00806 $qo->instructions = array(); 00807 $qo->instructions['text'] = $this->getpath($instructions, 00808 array('0', '#', 'text', '0', '#'), '', true); 00809 $qo->instructions['format'] = $this->trans_format($this->getpath($instructions, 00810 array('0', '@', 'format'), $this->get_format($qo->questiontextformat))); 00811 $qo->instructions['files'] = $this->import_files($this->getpath($instructions, 00812 array('0', '#', 'file'), array())); 00813 } 00814 00815 if (is_null($qo->unitpenalty)) { 00816 // Set a good default, depending on whether there are any units defined. 00817 if (empty($qo->unit)) { 00818 $qo->showunits = 3; // qtype_numerical::UNITNONE; 00819 } else { 00820 $qo->showunits = 0; // qtype_numerical::UNITOPTIONAL; 00821 } 00822 } 00823 00824 $datasets = $question['#']['dataset_definitions'][0]['#']['dataset_definition']; 00825 $qo->dataset = array(); 00826 $qo->datasetindex= 0; 00827 foreach ($datasets as $dataset) { 00828 $qo->datasetindex++; 00829 $qo->dataset[$qo->datasetindex] = new stdClass(); 00830 $qo->dataset[$qo->datasetindex]->status = 00831 $this->import_text($dataset['#']['status'][0]['#']['text']); 00832 $qo->dataset[$qo->datasetindex]->name = 00833 $this->import_text($dataset['#']['name'][0]['#']['text']); 00834 $qo->dataset[$qo->datasetindex]->type = 00835 $dataset['#']['type'][0]['#']; 00836 $qo->dataset[$qo->datasetindex]->distribution = 00837 $this->import_text($dataset['#']['distribution'][0]['#']['text']); 00838 $qo->dataset[$qo->datasetindex]->max = 00839 $this->import_text($dataset['#']['maximum'][0]['#']['text']); 00840 $qo->dataset[$qo->datasetindex]->min = 00841 $this->import_text($dataset['#']['minimum'][0]['#']['text']); 00842 $qo->dataset[$qo->datasetindex]->length = 00843 $this->import_text($dataset['#']['decimals'][0]['#']['text']); 00844 $qo->dataset[$qo->datasetindex]->distribution = 00845 $this->import_text($dataset['#']['distribution'][0]['#']['text']); 00846 $qo->dataset[$qo->datasetindex]->itemcount = $dataset['#']['itemcount'][0]['#']; 00847 $qo->dataset[$qo->datasetindex]->datasetitem = array(); 00848 $qo->dataset[$qo->datasetindex]->itemindex = 0; 00849 $qo->dataset[$qo->datasetindex]->number_of_items = 00850 $dataset['#']['number_of_items'][0]['#']; 00851 $datasetitems = $dataset['#']['dataset_items'][0]['#']['dataset_item']; 00852 foreach ($datasetitems as $datasetitem) { 00853 $qo->dataset[$qo->datasetindex]->itemindex++; 00854 $qo->dataset[$qo->datasetindex]->datasetitem[ 00855 $qo->dataset[$qo->datasetindex]->itemindex] = new stdClass(); 00856 $qo->dataset[$qo->datasetindex]->datasetitem[ 00857 $qo->dataset[$qo->datasetindex]->itemindex]->itemnumber = 00858 $datasetitem['#']['number'][0]['#']; 00859 $qo->dataset[$qo->datasetindex]->datasetitem[ 00860 $qo->dataset[$qo->datasetindex]->itemindex]->value = 00861 $datasetitem['#']['value'][0]['#']; 00862 } 00863 } 00864 00865 $this->import_hints($qo, $question, false, false, $this->get_format($qo->questiontextformat)); 00866 00867 return $qo; 00868 } 00869 00877 protected function import_category($question) { 00878 $qo = new stdClass(); 00879 $qo->qtype = 'category'; 00880 $qo->category = $this->import_text($question['#']['category'][0]['#']['text']); 00881 return $qo; 00882 } 00883 00891 protected function readquestions($lines) { 00892 // We just need it as one big string 00893 $text = implode($lines, ' '); 00894 unset($lines); 00895 00896 // This converts xml to big nasty data structure 00897 // the 0 means keep white space as it is (important for markdown format) 00898 try { 00899 $xml = xmlize($text, 0, 'UTF-8', true); 00900 } catch (xml_format_exception $e) { 00901 $this->error($e->getMessage(), ''); 00902 return false; 00903 } 00904 // Set up array to hold all our questions 00905 $questions = array(); 00906 00907 // Iterate through questions 00908 foreach ($xml['quiz']['#']['question'] as $question) { 00909 $questiontype = $question['@']['type']; 00910 00911 if ($questiontype == 'multichoice') { 00912 $qo = $this->import_multichoice($question); 00913 } else if ($questiontype == 'truefalse') { 00914 $qo = $this->import_truefalse($question); 00915 } else if ($questiontype == 'shortanswer') { 00916 $qo = $this->import_shortanswer($question); 00917 } else if ($questiontype == 'numerical') { 00918 $qo = $this->import_numerical($question); 00919 } else if ($questiontype == 'description') { 00920 $qo = $this->import_description($question); 00921 } else if ($questiontype == 'matching' || $questiontype == 'match') { 00922 $qo = $this->import_match($question); 00923 } else if ($questiontype == 'cloze' || $questiontype == 'multianswer') { 00924 $qo = $this->import_multianswer($question); 00925 } else if ($questiontype == 'essay') { 00926 $qo = $this->import_essay($question); 00927 } else if ($questiontype == 'calculated') { 00928 $qo = $this->import_calculated($question); 00929 } else if ($questiontype == 'calculatedsimple') { 00930 $qo = $this->import_calculated($question); 00931 $qo->qtype = 'calculatedsimple'; 00932 } else if ($questiontype == 'calculatedmulti') { 00933 $qo = $this->import_calculated($question); 00934 $qo->qtype = 'calculatedmulti'; 00935 } else if ($questiontype == 'category') { 00936 $qo = $this->import_category($question); 00937 00938 } else { 00939 // Not a type we handle ourselves. See if the question type wants 00940 // to handle it. 00941 if (!$qo = $this->try_importing_using_qtypes( 00942 $question, null, null, $questiontype)) { 00943 $this->error(get_string('xmltypeunsupported', 'qformat_xml', $questiontype)); 00944 $qo = null; 00945 } 00946 } 00947 00948 // Stick the result in the $questions array 00949 if ($qo) { 00950 $questions[] = $qo; 00951 } 00952 } 00953 return $questions; 00954 } 00955 00956 // EXPORT FUNCTIONS START HERE 00957 00958 public function export_file_extension() { 00959 return '.xml'; 00960 } 00961 00970 protected function get_qtype($qtype) { 00971 switch($qtype) { 00972 case 'match': 00973 return 'matching'; 00974 case 'multianswer': 00975 return 'cloze'; 00976 default: 00977 return $qtype; 00978 } 00979 } 00980 00987 public function get_format($id) { 00988 switch($id) { 00989 case FORMAT_MOODLE: 00990 return 'moodle_auto_format'; 00991 case FORMAT_HTML: 00992 return 'html'; 00993 case FORMAT_PLAIN: 00994 return 'plain_text'; 00995 case FORMAT_WIKI: 00996 return 'wiki_like'; 00997 case FORMAT_MARKDOWN: 00998 return 'markdown'; 00999 default: 01000 return 'unknown'; 01001 } 01002 } 01003 01010 public function get_single($id) { 01011 switch($id) { 01012 case 0: 01013 return 'false'; 01014 case 1: 01015 return 'true'; 01016 default: 01017 return 'unknown'; 01018 } 01019 } 01020 01027 public function xml_escape($string) { 01028 if (!empty($string) && htmlspecialchars($string) != $string) { 01029 return "<![CDATA[{$string}]]>"; 01030 } else { 01031 return $string; 01032 } 01033 } 01034 01042 public function writetext($raw, $indent = 0, $short = true) { 01043 $indent = str_repeat(' ', $indent); 01044 $raw = $this->xml_escape($raw); 01045 01046 if ($short) { 01047 $xml = "$indent<text>$raw</text>\n"; 01048 } else { 01049 $xml = "$indent<text>\n$raw\n$indent</text>\n"; 01050 } 01051 01052 return $xml; 01053 } 01054 01060 public function write_files($files) { 01061 if (empty($files)) { 01062 return ''; 01063 } 01064 $string = ''; 01065 foreach ($files as $file) { 01066 if ($file->is_directory()) { 01067 continue; 01068 } 01069 $string .= '<file name="' . $file->get_filename() . '" encoding="base64">'; 01070 $string .= base64_encode($file->get_content()); 01071 $string .= '</file>'; 01072 } 01073 return $string; 01074 } 01075 01076 protected function presave_process($content) { 01077 // Override to allow us to add xml headers and footers 01078 return '<?xml version="1.0" encoding="UTF-8"?> 01079 <quiz> 01080 ' . $content . '</quiz>'; 01081 } 01082 01088 public function writequestion($question) { 01089 global $CFG, $OUTPUT; 01090 01091 $fs = get_file_storage(); 01092 $contextid = $question->contextid; 01093 // Get files used by the questiontext. 01094 $question->questiontextfiles = $fs->get_area_files( 01095 $contextid, 'question', 'questiontext', $question->id); 01096 // Get files used by the generalfeedback. 01097 $question->generalfeedbackfiles = $fs->get_area_files( 01098 $contextid, 'question', 'generalfeedback', $question->id); 01099 if (!empty($question->options->answers)) { 01100 foreach ($question->options->answers as $answer) { 01101 $answer->answerfiles = $fs->get_area_files( 01102 $contextid, 'question', 'answer', $answer->id); 01103 $answer->feedbackfiles = $fs->get_area_files( 01104 $contextid, 'question', 'answerfeedback', $answer->id); 01105 } 01106 } 01107 01108 $expout = ''; 01109 01110 // Add a comment linking this to the original question id. 01111 $expout .= "<!-- question: $question->id -->\n"; 01112 01113 // Check question type 01114 $questiontype = $this->get_qtype($question->qtype); 01115 01116 // Categories are a special case. 01117 if ($question->qtype == 'category') { 01118 $categorypath = $this->writetext($question->category); 01119 $expout .= " <question type=\"category\">\n"; 01120 $expout .= " <category>\n"; 01121 $expout .= " $categorypath\n"; 01122 $expout .= " </category>\n"; 01123 $expout .= " </question>\n"; 01124 return $expout; 01125 } 01126 01127 // Now we know we are are handing a real question. 01128 // Output the generic information. 01129 $expout .= " <question type=\"$questiontype\">\n"; 01130 $expout .= " <name>\n"; 01131 $expout .= $this->writetext($question->name, 3); 01132 $expout .= " </name>\n"; 01133 $expout .= " <questiontext {$this->format($question->questiontextformat)}>\n"; 01134 $expout .= $this->writetext($question->questiontext, 3); 01135 $expout .= $this->write_files($question->questiontextfiles); 01136 $expout .= " </questiontext>\n"; 01137 $expout .= " <generalfeedback {$this->format($question->generalfeedbackformat)}>\n"; 01138 $expout .= $this->writetext($question->generalfeedback, 3); 01139 $expout .= $this->write_files($question->generalfeedbackfiles); 01140 $expout .= " </generalfeedback>\n"; 01141 if ($question->qtype != 'multianswer') { 01142 $expout .= " <defaultgrade>{$question->defaultmark}</defaultgrade>\n"; 01143 } 01144 $expout .= " <penalty>{$question->penalty}</penalty>\n"; 01145 $expout .= " <hidden>{$question->hidden}</hidden>\n"; 01146 01147 // The rest of the output depends on question type. 01148 switch($question->qtype) { 01149 case 'category': 01150 // not a qtype really - dummy used for category switching 01151 break; 01152 01153 case 'truefalse': 01154 $trueanswer = $question->options->answers[$question->options->trueanswer]; 01155 $trueanswer->answer = 'true'; 01156 $expout .= $this->write_answer($trueanswer); 01157 01158 $falseanswer = $question->options->answers[$question->options->falseanswer]; 01159 $falseanswer->answer = 'false'; 01160 $expout .= $this->write_answer($falseanswer); 01161 break; 01162 01163 case 'multichoice': 01164 $expout .= " <single>" . $this->get_single($question->options->single) . 01165 "</single>\n"; 01166 $expout .= " <shuffleanswers>" . 01167 $this->get_single($question->options->shuffleanswers) . 01168 "</shuffleanswers>\n"; 01169 $expout .= " <answernumbering>" . $question->options->answernumbering . 01170 "</answernumbering>\n"; 01171 $expout .= $this->write_combined_feedback($question->options, $question->id, $question->contextid); 01172 $expout .= $this->write_answers($question->options->answers); 01173 break; 01174 01175 case 'shortanswer': 01176 $expout .= " <usecase>{$question->options->usecase}</usecase>\n"; 01177 $expout .= $this->write_answers($question->options->answers); 01178 break; 01179 01180 case 'numerical': 01181 foreach ($question->options->answers as $answer) { 01182 $expout .= $this->write_answer($answer, 01183 " <tolerance>$answer->tolerance</tolerance>\n"); 01184 } 01185 01186 $units = $question->options->units; 01187 if (count($units)) { 01188 $expout .= "<units>\n"; 01189 foreach ($units as $unit) { 01190 $expout .= " <unit>\n"; 01191 $expout .= " <multiplier>{$unit->multiplier}</multiplier>\n"; 01192 $expout .= " <unit_name>{$unit->unit}</unit_name>\n"; 01193 $expout .= " </unit>\n"; 01194 } 01195 $expout .= "</units>\n"; 01196 } 01197 if (isset($question->options->unitgradingtype)) { 01198 $expout .= " <unitgradingtype>" . $question->options->unitgradingtype . 01199 "</unitgradingtype>\n"; 01200 } 01201 if (isset($question->options->unitpenalty)) { 01202 $expout .= " <unitpenalty>{$question->options->unitpenalty}</unitpenalty>\n"; 01203 } 01204 if (isset($question->options->showunits)) { 01205 $expout .= " <showunits>{$question->options->showunits}</showunits>\n"; 01206 } 01207 if (isset($question->options->unitsleft)) { 01208 $expout .= " <unitsleft>{$question->options->unitsleft}</unitsleft>\n"; 01209 } 01210 if (!empty($question->options->instructionsformat)) { 01211 $files = $fs->get_area_files($contextid, 'qtype_numerical', 01212 'instruction', $question->id); 01213 $expout .= " <instructions " . 01214 $this->format($question->options->instructionsformat) . ">\n"; 01215 $expout .= $this->writetext($question->options->instructions, 3); 01216 $expout .= $this->write_files($files); 01217 $expout .= " </instructions>\n"; 01218 } 01219 break; 01220 01221 case 'match': 01222 $expout .= " <shuffleanswers>" . 01223 $this->get_single($question->options->shuffleanswers) . 01224 "</shuffleanswers>\n"; 01225 $expout .= $this->write_combined_feedback($question->options, $question->id, $question->contextid); 01226 foreach ($question->options->subquestions as $subquestion) { 01227 $files = $fs->get_area_files($contextid, 'qtype_match', 01228 'subquestion', $subquestion->id); 01229 $expout .= " <subquestion " . 01230 $this->format($subquestion->questiontextformat) . ">\n"; 01231 $expout .= $this->writetext($subquestion->questiontext, 3); 01232 $expout .= $this->write_files($files); 01233 $expout .= " <answer>\n"; 01234 $expout .= $this->writetext($subquestion->answertext, 4); 01235 $expout .= " </answer>\n"; 01236 $expout .= " </subquestion>\n"; 01237 } 01238 break; 01239 01240 case 'description': 01241 // Nothing else to do. 01242 break; 01243 01244 case 'multianswer': 01245 foreach ($question->options->questions as $index => $subq) { 01246 $expout = preg_replace('~{#' . $index . '}~', $subq->questiontext, $expout); 01247 } 01248 break; 01249 01250 case 'essay': 01251 $expout .= " <responseformat>" . $question->options->responseformat . 01252 "</responseformat>\n"; 01253 $expout .= " <responsefieldlines>" . $question->options->responsefieldlines . 01254 "</responsefieldlines>\n"; 01255 $expout .= " <attachments>" . $question->options->attachments . 01256 "</attachments>\n"; 01257 $expout .= " <graderinfo " . 01258 $this->format($question->options->graderinfoformat) . ">\n"; 01259 $expout .= $this->writetext($question->options->graderinfo, 3); 01260 $expout .= $this->write_files($fs->get_area_files($contextid, 'qtype_essay', 01261 'graderinfo', $question->id)); 01262 $expout .= " </graderinfo>\n"; 01263 break; 01264 01265 case 'calculated': 01266 case 'calculatedsimple': 01267 case 'calculatedmulti': 01268 $expout .= " <synchronize>{$question->options->synchronize}</synchronize>\n"; 01269 $expout .= " <single>{$question->options->single}</single>\n"; 01270 $expout .= " <answernumbering>" . $question->options->answernumbering . 01271 "</answernumbering>\n"; 01272 $expout .= " <shuffleanswers>" . $question->options->shuffleanswers . 01273 "</shuffleanswers>\n"; 01274 01275 $component = 'qtype_' . $question->qtype; 01276 $files = $fs->get_area_files($contextid, $component, 01277 'correctfeedback', $question->id); 01278 $expout .= " <correctfeedback>\n"; 01279 $expout .= $this->writetext($question->options->correctfeedback, 3); 01280 $expout .= $this->write_files($files); 01281 $expout .= " </correctfeedback>\n"; 01282 01283 $files = $fs->get_area_files($contextid, $component, 01284 'partiallycorrectfeedback', $question->id); 01285 $expout .= " <partiallycorrectfeedback>\n"; 01286 $expout .= $this->writetext($question->options->partiallycorrectfeedback, 3); 01287 $expout .= $this->write_files($files); 01288 $expout .= " </partiallycorrectfeedback>\n"; 01289 01290 $files = $fs->get_area_files($contextid, $component, 01291 'incorrectfeedback', $question->id); 01292 $expout .= " <incorrectfeedback>\n"; 01293 $expout .= $this->writetext($question->options->incorrectfeedback, 3); 01294 $expout .= $this->write_files($files); 01295 $expout .= " </incorrectfeedback>\n"; 01296 01297 foreach ($question->options->answers as $answer) { 01298 $percent = 100 * $answer->fraction; 01299 $expout .= "<answer fraction=\"$percent\">\n"; 01300 // "<text/>" tags are an added feature, old files won't have them 01301 $expout .= " <text>{$answer->answer}</text>\n"; 01302 $expout .= " <tolerance>{$answer->tolerance}</tolerance>\n"; 01303 $expout .= " <tolerancetype>{$answer->tolerancetype}</tolerancetype>\n"; 01304 $expout .= " <correctanswerformat>" . 01305 $answer->correctanswerformat . "</correctanswerformat>\n"; 01306 $expout .= " <correctanswerlength>" . 01307 $answer->correctanswerlength . "</correctanswerlength>\n"; 01308 $expout .= " <feedback {$this->format($answer->feedbackformat)}>\n"; 01309 $files = $fs->get_area_files($contextid, $component, 01310 'instruction', $question->id); 01311 $expout .= $this->writetext($answer->feedback); 01312 $expout .= $this->write_files($answer->feedbackfiles); 01313 $expout .= " </feedback>\n"; 01314 $expout .= "</answer>\n"; 01315 } 01316 if (isset($question->options->unitgradingtype)) { 01317 $expout .= " <unitgradingtype>" . 01318 $question->options->unitgradingtype . "</unitgradingtype>\n"; 01319 } 01320 if (isset($question->options->unitpenalty)) { 01321 $expout .= " <unitpenalty>" . 01322 $question->options->unitpenalty . "</unitpenalty>\n"; 01323 } 01324 if (isset($question->options->showunits)) { 01325 $expout .= " <showunits>{$question->options->showunits}</showunits>\n"; 01326 } 01327 if (isset($question->options->unitsleft)) { 01328 $expout .= " <unitsleft>{$question->options->unitsleft}</unitsleft>\n"; 01329 } 01330 01331 if (isset($question->options->instructionsformat)) { 01332 $files = $fs->get_area_files($contextid, $component, 01333 'instruction', $question->id); 01334 $expout .= " <instructions " . 01335 $this->format($question->options->instructionsformat) . ">\n"; 01336 $expout .= $this->writetext($question->options->instructions, 3); 01337 $expout .= $this->write_files($files); 01338 $expout .= " </instructions>\n"; 01339 } 01340 01341 if (isset($question->options->units)) { 01342 $units = $question->options->units; 01343 if (count($units)) { 01344 $expout .= "<units>\n"; 01345 foreach ($units as $unit) { 01346 $expout .= " <unit>\n"; 01347 $expout .= " <multiplier>{$unit->multiplier}</multiplier>\n"; 01348 $expout .= " <unit_name>{$unit->unit}</unit_name>\n"; 01349 $expout .= " </unit>\n"; 01350 } 01351 $expout .= "</units>\n"; 01352 } 01353 } 01354 01355 // The tag $question->export_process has been set so we get all the 01356 // data items in the database from the function 01357 // qtype_calculated::get_question_options calculatedsimple defaults 01358 // to calculated 01359 if (isset($question->options->datasets) && count($question->options->datasets)) { 01360 $expout .= "<dataset_definitions>\n"; 01361 foreach ($question->options->datasets as $def) { 01362 $expout .= "<dataset_definition>\n"; 01363 $expout .= " <status>".$this->writetext($def->status)."</status>\n"; 01364 $expout .= " <name>".$this->writetext($def->name)."</name>\n"; 01365 if ($question->qtype == CALCULATED) { 01366 $expout .= " <type>calculated</type>\n"; 01367 } else { 01368 $expout .= " <type>calculatedsimple</type>\n"; 01369 } 01370 $expout .= " <distribution>" . $this->writetext($def->distribution) . 01371 "</distribution>\n"; 01372 $expout .= " <minimum>" . $this->writetext($def->minimum) . 01373 "</minimum>\n"; 01374 $expout .= " <maximum>" . $this->writetext($def->maximum) . 01375 "</maximum>\n"; 01376 $expout .= " <decimals>" . $this->writetext($def->decimals) . 01377 "</decimals>\n"; 01378 $expout .= " <itemcount>$def->itemcount</itemcount>\n"; 01379 if ($def->itemcount > 0) { 01380 $expout .= " <dataset_items>\n"; 01381 foreach ($def->items as $item) { 01382 $expout .= " <dataset_item>\n"; 01383 $expout .= " <number>".$item->itemnumber."</number>\n"; 01384 $expout .= " <value>".$item->value."</value>\n"; 01385 $expout .= " </dataset_item>\n"; 01386 } 01387 $expout .= " </dataset_items>\n"; 01388 $expout .= " <number_of_items>" . $def->number_of_items . 01389 "</number_of_items>\n"; 01390 } 01391 $expout .= "</dataset_definition>\n"; 01392 } 01393 $expout .= "</dataset_definitions>\n"; 01394 } 01395 break; 01396 01397 default: 01398 // try support by optional plugin 01399 if (!$data = $this->try_exporting_using_qtypes($question->qtype, $question)) { 01400 notify(get_string('unsupportedexport', 'qformat_xml', $question->qtype)); 01401 } 01402 $expout .= $data; 01403 } 01404 01405 // Output any hints. 01406 $expout .= $this->write_hints($question); 01407 01408 // Write the question tags. 01409 if (!empty($CFG->usetags)) { 01410 require_once($CFG->dirroot.'/tag/lib.php'); 01411 $tags = tag_get_tags_array('question', $question->id); 01412 if (!empty($tags)) { 01413 $expout .= " <tags>\n"; 01414 foreach ($tags as $tag) { 01415 $expout .= " <tag>" . $this->writetext($tag, 0, true) . "</tag>\n"; 01416 } 01417 $expout .= " </tags>\n"; 01418 } 01419 } 01420 01421 // close the question tag 01422 $expout .= " </question>\n"; 01423 01424 return $expout; 01425 } 01426 01427 public function write_answers($answers) { 01428 if (empty($answers)) { 01429 return; 01430 } 01431 $output = ''; 01432 foreach ($answers as $answer) { 01433 $output .= $this->write_answer($answer); 01434 } 01435 return $output; 01436 } 01437 01438 public function write_answer($answer, $extra = '') { 01439 $percent = $answer->fraction * 100; 01440 $output = ''; 01441 $output .= " <answer fraction=\"$percent\" {$this->format($answer->answerformat)}>\n"; 01442 $output .= $this->writetext($answer->answer, 3); 01443 $output .= $this->write_files($answer->answerfiles); 01444 $output .= " <feedback {$this->format($answer->feedbackformat)}>\n"; 01445 $output .= $this->writetext($answer->feedback, 4); 01446 $output .= $this->write_files($answer->feedbackfiles); 01447 $output .= " </feedback>\n"; 01448 $output .= $extra; 01449 $output .= " </answer>\n"; 01450 return $output; 01451 } 01452 01458 public function write_hints($question) { 01459 if (empty($question->hints)) { 01460 return ''; 01461 } 01462 01463 $output = ''; 01464 foreach ($question->hints as $hint) { 01465 $output .= $this->write_hint($hint, $question->contextid); 01466 } 01467 return $output; 01468 } 01469 01474 public function format($format) { 01475 return 'format="' . $this->get_format($format) . '"'; 01476 } 01477 01478 public function write_hint($hint, $contextid) { 01479 $fs = get_file_storage(); 01480 $files = $fs->get_area_files($contextid, 'question', 'hint', $hint->id); 01481 01482 $output = ''; 01483 $output .= " <hint {$this->format($hint->hintformat)}>\n"; 01484 $output .= ' ' . $this->writetext($hint->hint); 01485 01486 if (!empty($hint->shownumcorrect)) { 01487 $output .= " <shownumcorrect/>\n"; 01488 } 01489 if (!empty($hint->clearwrong)) { 01490 $output .= " <clearwrong/>\n"; 01491 } 01492 01493 if (!empty($hint->options)) { 01494 $output .= ' <options>' . $this->xml_escape($hint->options) . "</options>\n"; 01495 } 01496 $output .= $this->write_files($files); 01497 $output .= " </hint>\n"; 01498 return $output; 01499 } 01500 01508 public function write_combined_feedback($questionoptions, $questionid, $contextid) { 01509 $fs = get_file_storage(); 01510 $output = ''; 01511 01512 $fields = array('correctfeedback', 'partiallycorrectfeedback', 'incorrectfeedback'); 01513 foreach ($fields as $field) { 01514 $formatfield = $field . 'format'; 01515 $files = $fs->get_area_files($contextid, 'question', $field, $questionid); 01516 01517 $output .= " <{$field} {$this->format($questionoptions->$formatfield)}>\n"; 01518 $output .= ' ' . $this->writetext($questionoptions->$field); 01519 $output .= $this->write_files($files); 01520 $output .= " </{$field}>\n"; 01521 } 01522 01523 if (!empty($questionoptions->shownumcorrect)) { 01524 $output .= " <shownumcorrect/>\n"; 01525 } 01526 return $output; 01527 } 01528 }