Moodle  2.2.1
http://www.collinsharper.com
C:/xampp/htdocs/moodle/question/format/xml/format.php
Go to the documentation of this file.
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 }
 All Data Structures Namespaces Files Functions Variables Enumerations