|
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->dirroot . '/question/engine/lib.php'); 00030 00031 00048 class question_type { 00049 protected $fileoptions = array( 00050 'subdirs' => false, 00051 'maxfiles' => -1, 00052 'maxbytes' => 0, 00053 ); 00054 00055 public function __construct() { 00056 } 00057 00061 public function name() { 00062 return substr(get_class($this), 6); 00063 } 00064 00068 public function plugin_name() { 00069 return get_class($this); 00070 } 00071 00076 public function local_name() { 00077 if (get_string_manager()->string_exists('pluginname', $this->plugin_name())) { 00078 return get_string('pluginname', $this->plugin_name()); 00079 } else { 00080 return get_string($this->name(), $this->plugin_name()); 00081 } 00082 } 00083 00092 public function menu_name() { 00093 return $this->local_name(); 00094 } 00095 00101 public function is_real_question_type() { 00102 return true; 00103 } 00104 00108 public function is_manual_graded() { 00109 return false; 00110 } 00111 00117 public function is_question_manual_graded($question, $otherquestionsinuse) { 00118 return $this->is_manual_graded(); 00119 } 00120 00124 public function is_usable_by_random() { 00125 return true; 00126 } 00127 00139 public function can_analyse_responses() { 00140 // This works in most cases. 00141 return !$this->is_manual_graded(); 00142 } 00143 00148 public function has_html_answers() { 00149 return false; 00150 } 00151 00160 public function extra_question_fields() { 00161 return null; 00162 } 00163 00168 public function questionid_column_name() { 00169 return 'questionid'; 00170 } 00171 00179 public function extra_answer_fields() { 00180 return null; 00181 } 00182 00190 public function response_file_areas() { 00191 return array(); 00192 } 00193 00203 public function create_editing_form($submiturl, $question, $category, 00204 $contexts, $formeditable) { 00205 global $CFG; 00206 require_once($CFG->dirroot . '/question/type/edit_question_form.php'); 00207 $definitionfile = $CFG->dirroot . '/question/type/' . $this->name() . 00208 '/edit_' . $this->name() . '_form.php'; 00209 if (!is_readable($definitionfile) || !is_file($definitionfile)) { 00210 throw new coding_exception($this->plugin_name() . 00211 ' is missing the definition of its editing formin file ' . 00212 $definitionfile . '.'); 00213 } 00214 require_once($definitionfile); 00215 $classname = $this->plugin_name() . '_edit_form'; 00216 if (!class_exists($classname)) { 00217 throw new coding_exception($this->plugin_name() . 00218 ' does not define the class ' . $this->plugin_name() . 00219 '_edit_form.'); 00220 } 00221 return new $classname($submiturl, $question, $category, $contexts, $formeditable); 00222 } 00223 00227 public function plugin_dir() { 00228 global $CFG; 00229 return $CFG->dirroot . '/question/type/' . $this->name(); 00230 } 00231 00235 public function plugin_baseurl() { 00236 global $CFG; 00237 return $CFG->wwwroot . '/question/type/' . $this->name(); 00238 } 00239 00248 public function display_question_editing_page($mform, $question, $wizardnow) { 00249 global $OUTPUT; 00250 $heading = $this->get_heading(empty($question->id)); 00251 00252 if (get_string_manager()->string_exists('pluginname_help', $this->plugin_name())) { 00253 echo $OUTPUT->heading_with_help($heading, 'pluginname', $this->plugin_name()); 00254 } else { 00255 echo $OUTPUT->heading_with_help($heading, $this->name(), $this->plugin_name()); 00256 } 00257 00258 $permissionstrs = array(); 00259 if (!empty($question->id)) { 00260 if ($question->formoptions->canedit) { 00261 $permissionstrs[] = get_string('permissionedit', 'question'); 00262 } 00263 if ($question->formoptions->canmove) { 00264 $permissionstrs[] = get_string('permissionmove', 'question'); 00265 } 00266 if ($question->formoptions->cansaveasnew) { 00267 $permissionstrs[] = get_string('permissionsaveasnew', 'question'); 00268 } 00269 } 00270 if (!$question->formoptions->movecontext && count($permissionstrs)) { 00271 echo $OUTPUT->heading(get_string('permissionto', 'question'), 3); 00272 $html = '<ul>'; 00273 foreach ($permissionstrs as $permissionstr) { 00274 $html .= '<li>'.$permissionstr.'</li>'; 00275 } 00276 $html .= '</ul>'; 00277 echo $OUTPUT->box($html, 'boxwidthnarrow boxaligncenter generalbox'); 00278 } 00279 $mform->display(); 00280 } 00281 00288 public function get_heading($adding = false) { 00289 if ($adding) { 00290 $string = 'pluginnameadding'; 00291 $fallback = 'adding' . $this->name(); 00292 } else { 00293 $string = 'pluginnameediting'; 00294 $fallback = 'editing' . $this->name(); 00295 } 00296 if (get_string_manager()->string_exists($string, $this->plugin_name())) { 00297 return get_string($string, $this->plugin_name()); 00298 } else { 00299 return get_string($fallback, $this->plugin_name()); 00300 } 00301 } 00302 00311 public function set_default_options($questiondata) { 00312 } 00313 00341 public function save_question($question, $form) { 00342 global $USER, $DB, $OUTPUT; 00343 00344 list($question->category) = explode(',', $form->category); 00345 $context = $this->get_context_by_category_id($question->category); 00346 00347 // This default implementation is suitable for most 00348 // question types. 00349 00350 // First, save the basic question itself 00351 $question->name = trim($form->name); 00352 $question->parent = isset($form->parent) ? $form->parent : 0; 00353 $question->length = $this->actual_number_of_questions($question); 00354 $question->penalty = isset($form->penalty) ? $form->penalty : 0; 00355 00356 if (empty($form->questiontext['text'])) { 00357 $question->questiontext = ''; 00358 } else { 00359 $question->questiontext = trim($form->questiontext['text']);; 00360 } 00361 $question->questiontextformat = !empty($form->questiontext['format']) ? 00362 $form->questiontext['format'] : 0; 00363 00364 if (empty($form->generalfeedback['text'])) { 00365 $question->generalfeedback = ''; 00366 } else { 00367 $question->generalfeedback = trim($form->generalfeedback['text']); 00368 } 00369 $question->generalfeedbackformat = !empty($form->generalfeedback['format']) ? 00370 $form->generalfeedback['format'] : 0; 00371 00372 if (empty($question->name)) { 00373 $question->name = shorten_text(strip_tags($form->questiontext['text']), 15); 00374 if (empty($question->name)) { 00375 $question->name = '-'; 00376 } 00377 } 00378 00379 if ($question->penalty > 1 or $question->penalty < 0) { 00380 $question->errors['penalty'] = get_string('invalidpenalty', 'question'); 00381 } 00382 00383 if (isset($form->defaultmark)) { 00384 $question->defaultmark = $form->defaultmark; 00385 } 00386 00387 // If the question is new, create it. 00388 if (empty($question->id)) { 00389 // Set the unique code 00390 $question->stamp = make_unique_id_code(); 00391 $question->createdby = $USER->id; 00392 $question->timecreated = time(); 00393 $question->id = $DB->insert_record('question', $question); 00394 } 00395 00396 // Now, whether we are updating a existing question, or creating a new 00397 // one, we have to do the files processing and update the record. 00399 $question->modifiedby = $USER->id; 00400 $question->timemodified = time(); 00401 00402 if (!empty($question->questiontext) && !empty($form->questiontext['itemid'])) { 00403 $question->questiontext = file_save_draft_area_files($form->questiontext['itemid'], 00404 $context->id, 'question', 'questiontext', (int)$question->id, 00405 $this->fileoptions, $question->questiontext); 00406 } 00407 if (!empty($question->generalfeedback) && !empty($form->generalfeedback['itemid'])) { 00408 $question->generalfeedback = file_save_draft_area_files( 00409 $form->generalfeedback['itemid'], $context->id, 00410 'question', 'generalfeedback', (int)$question->id, 00411 $this->fileoptions, $question->generalfeedback); 00412 } 00413 $DB->update_record('question', $question); 00414 00415 // Now to save all the answers and type-specific options 00416 $form->id = $question->id; 00417 $form->qtype = $question->qtype; 00418 $form->category = $question->category; 00419 $form->questiontext = $question->questiontext; 00420 $form->questiontextformat = $question->questiontextformat; 00421 // current context 00422 $form->context = $context; 00423 00424 $result = $this->save_question_options($form); 00425 00426 if (!empty($result->error)) { 00427 print_error($result->error); 00428 } 00429 00430 if (!empty($result->notice)) { 00431 notice($result->notice, "question.php?id=$question->id"); 00432 } 00433 00434 if (!empty($result->noticeyesno)) { 00435 throw new coding_exception( 00436 '$result->noticeyesno no longer supported in save_question.'); 00437 } 00438 00439 // Give the question a unique version stamp determined by question_hash() 00440 $DB->set_field('question', 'version', question_hash($question), 00441 array('id' => $question->id)); 00442 00443 return $question; 00444 } 00445 00454 public function save_question_options($question) { 00455 global $DB; 00456 $extraquestionfields = $this->extra_question_fields(); 00457 00458 if (is_array($extraquestionfields)) { 00459 $question_extension_table = array_shift($extraquestionfields); 00460 00461 $function = 'update_record'; 00462 $questionidcolname = $this->questionid_column_name(); 00463 $options = $DB->get_record($question_extension_table, 00464 array($questionidcolname => $question->id)); 00465 if (!$options) { 00466 $function = 'insert_record'; 00467 $options = new stdClass(); 00468 $options->$questionidcolname = $question->id; 00469 } 00470 foreach ($extraquestionfields as $field) { 00471 if (!isset($question->$field)) { 00472 $result = new stdClass(); 00473 $result->error = "No data for field $field when saving " . 00474 $this->name() . ' question id ' . $question->id; 00475 return $result; 00476 } 00477 $options->$field = $question->$field; 00478 } 00479 00480 if (!$DB->{$function}($question_extension_table, $options)) { 00481 $result = new stdClass(); 00482 $result->error = 'Could not save question options for ' . 00483 $this->name() . ' question id ' . $question->id; 00484 return $result; 00485 } 00486 } 00487 00488 $extraanswerfields = $this->extra_answer_fields(); 00489 // TODO save the answers, with any extra data. 00490 } 00491 00492 public function save_hints($formdata, $withparts = false) { 00493 global $DB; 00494 $context = $formdata->context; 00495 00496 $oldhints = $DB->get_records('question_hints', 00497 array('questionid' => $formdata->id), 'id ASC'); 00498 00499 if (!empty($formdata->hint)) { 00500 $numhints = max(array_keys($formdata->hint)) + 1; 00501 } else { 00502 $numhints = 0; 00503 } 00504 00505 if ($withparts) { 00506 if (!empty($formdata->hintclearwrong)) { 00507 $numclears = max(array_keys($formdata->hintclearwrong)) + 1; 00508 } else { 00509 $numclears = 0; 00510 } 00511 if (!empty($formdata->hintshownumcorrect)) { 00512 $numshows = max(array_keys($formdata->hintshownumcorrect)) + 1; 00513 } else { 00514 $numshows = 0; 00515 } 00516 $numhints = max($numhints, $numclears, $numshows); 00517 } 00518 00519 for ($i = 0; $i < $numhints; $i += 1) { 00520 if (html_is_blank($formdata->hint[$i]['text'])) { 00521 $formdata->hint[$i]['text'] = ''; 00522 } 00523 00524 if ($withparts) { 00525 $clearwrong = !empty($formdata->hintclearwrong[$i]); 00526 $shownumcorrect = !empty($formdata->hintshownumcorrect[$i]); 00527 } 00528 00529 if (empty($formdata->hint[$i]['text']) && empty($clearwrong) && 00530 empty($shownumcorrect)) { 00531 continue; 00532 } 00533 00534 // Update an existing hint if possible. 00535 $hint = array_shift($oldhints); 00536 if (!$hint) { 00537 $hint = new stdClass(); 00538 $hint->questionid = $formdata->id; 00539 $hint->hint = ''; 00540 $hint->id = $DB->insert_record('question_hints', $hint); 00541 } 00542 00543 $hint->hint = $this->import_or_save_files($formdata->hint[$i], 00544 $context, 'question', 'hint', $hint->id); 00545 $hint->hintformat = $formdata->hint[$i]['format']; 00546 if ($withparts) { 00547 $hint->clearwrong = $clearwrong; 00548 $hint->shownumcorrect = $shownumcorrect; 00549 } 00550 $DB->update_record('question_hints', $hint); 00551 } 00552 00553 // Delete any remaining old hints. 00554 $fs = get_file_storage(); 00555 foreach ($oldhints as $oldhint) { 00556 $fs->delete_area_files($context->id, 'question', 'hint', $oldhint->id); 00557 $DB->delete_records('question_hints', array('id' => $oldhint->id)); 00558 } 00559 } 00560 00569 protected function save_combined_feedback_helper($options, $formdata, 00570 $context, $withparts = false) { 00571 $options->correctfeedback = $this->import_or_save_files($formdata->correctfeedback, 00572 $context, 'question', 'correctfeedback', $formdata->id); 00573 $options->correctfeedbackformat = $formdata->correctfeedback['format']; 00574 00575 $options->partiallycorrectfeedback = $this->import_or_save_files( 00576 $formdata->partiallycorrectfeedback, 00577 $context, 'question', 'partiallycorrectfeedback', $formdata->id); 00578 $options->partiallycorrectfeedbackformat = $formdata->partiallycorrectfeedback['format']; 00579 00580 $options->incorrectfeedback = $this->import_or_save_files($formdata->incorrectfeedback, 00581 $context, 'question', 'incorrectfeedback', $formdata->id); 00582 $options->incorrectfeedbackformat = $formdata->incorrectfeedback['format']; 00583 00584 if ($withparts) { 00585 $options->shownumcorrect = !empty($formdata->shownumcorrect); 00586 } 00587 00588 return $options; 00589 } 00590 00603 public function get_question_options($question) { 00604 global $CFG, $DB, $OUTPUT; 00605 00606 if (!isset($question->options)) { 00607 $question->options = new stdClass(); 00608 } 00609 00610 $extraquestionfields = $this->extra_question_fields(); 00611 if (is_array($extraquestionfields)) { 00612 $question_extension_table = array_shift($extraquestionfields); 00613 $extra_data = $DB->get_record($question_extension_table, 00614 array($this->questionid_column_name() => $question->id), 00615 implode(', ', $extraquestionfields)); 00616 if ($extra_data) { 00617 foreach ($extraquestionfields as $field) { 00618 $question->options->$field = $extra_data->$field; 00619 } 00620 } else { 00621 echo $OUTPUT->notification('Failed to load question options from the table ' . 00622 $question_extension_table . ' for questionid ' . $question->id); 00623 return false; 00624 } 00625 } 00626 00627 $extraanswerfields = $this->extra_answer_fields(); 00628 if (is_array($extraanswerfields)) { 00629 $answer_extension_table = array_shift($extraanswerfields); 00630 $question->options->answers = $DB->get_records_sql(" 00631 SELECT qa.*, qax." . implode(', qax.', $extraanswerfields) . " 00632 FROM {question_answers} qa, {{$answer_extension_table}} qax 00633 WHERE qa.question = ? AND qax.answerid = qa.id 00634 ORDER BY qa.id", array($question->id)); 00635 if (!$question->options->answers) { 00636 echo $OUTPUT->notification('Failed to load question answers from the table ' . 00637 $answer_extension_table . 'for questionid ' . $question->id); 00638 return false; 00639 } 00640 } else { 00641 // Don't check for success or failure because some question types do 00642 // not use the answers table. 00643 $question->options->answers = $DB->get_records('question_answers', 00644 array('question' => $question->id), 'id ASC'); 00645 } 00646 00647 $question->hints = $DB->get_records('question_hints', 00648 array('questionid' => $question->id), 'id ASC'); 00649 00650 return true; 00651 } 00652 00659 public function make_question($questiondata) { 00660 $question = $this->make_question_instance($questiondata); 00661 $this->initialise_question_instance($question, $questiondata); 00662 return $question; 00663 } 00664 00672 protected function make_question_instance($questiondata) { 00673 question_bank::load_question_definition_classes($this->name()); 00674 $class = 'qtype_' . $this->name() . '_question'; 00675 return new $class(); 00676 } 00677 00683 protected function initialise_question_instance(question_definition $question, $questiondata) { 00684 $question->id = $questiondata->id; 00685 $question->category = $questiondata->category; 00686 $question->contextid = $questiondata->contextid; 00687 $question->parent = $questiondata->parent; 00688 $question->qtype = $this; 00689 $question->name = $questiondata->name; 00690 $question->questiontext = $questiondata->questiontext; 00691 $question->questiontextformat = $questiondata->questiontextformat; 00692 $question->generalfeedback = $questiondata->generalfeedback; 00693 $question->generalfeedbackformat = $questiondata->generalfeedbackformat; 00694 $question->defaultmark = $questiondata->defaultmark + 0; 00695 $question->length = $questiondata->length; 00696 $question->penalty = $questiondata->penalty; 00697 $question->stamp = $questiondata->stamp; 00698 $question->version = $questiondata->version; 00699 $question->hidden = $questiondata->hidden; 00700 $question->timecreated = $questiondata->timecreated; 00701 $question->timemodified = $questiondata->timemodified; 00702 $question->createdby = $questiondata->createdby; 00703 $question->modifiedby = $questiondata->modifiedby; 00704 00705 //Fill extra question fields values 00706 $extraquestionfields = $this->extra_question_fields(); 00707 if (is_array($extraquestionfields)) { 00708 //omit table name 00709 array_shift($extraquestionfields); 00710 foreach($extraquestionfields as $field) { 00711 $question->$field = $questiondata->options->$field; 00712 } 00713 } 00714 00715 $this->initialise_question_hints($question, $questiondata); 00716 } 00717 00723 protected function initialise_question_hints(question_definition $question, $questiondata) { 00724 if (empty($questiondata->hints)) { 00725 return; 00726 } 00727 foreach ($questiondata->hints as $hint) { 00728 $question->hints[] = $this->make_hint($hint); 00729 } 00730 } 00731 00738 protected function make_hint($hint) { 00739 return question_hint::load_from_record($hint); 00740 } 00741 00748 protected function initialise_combined_feedback(question_definition $question, 00749 $questiondata, $withparts = false) { 00750 $question->correctfeedback = $questiondata->options->correctfeedback; 00751 $question->correctfeedbackformat = $questiondata->options->correctfeedbackformat; 00752 $question->partiallycorrectfeedback = $questiondata->options->partiallycorrectfeedback; 00753 $question->partiallycorrectfeedbackformat = 00754 $questiondata->options->partiallycorrectfeedbackformat; 00755 $question->incorrectfeedback = $questiondata->options->incorrectfeedback; 00756 $question->incorrectfeedbackformat = $questiondata->options->incorrectfeedbackformat; 00757 if ($withparts) { 00758 $question->shownumcorrect = $questiondata->options->shownumcorrect; 00759 } 00760 } 00761 00772 protected function initialise_question_answers(question_definition $question, 00773 $questiondata, $forceplaintextanswers = true) { 00774 $question->answers = array(); 00775 if (empty($questiondata->options->answers)) { 00776 return; 00777 } 00778 foreach ($questiondata->options->answers as $a) { 00779 $question->answers[$a->id] = new question_answer($a->id, $a->answer, 00780 $a->fraction, $a->feedback, $a->feedbackformat); 00781 if (!$forceplaintextanswers) { 00782 $question->answers[$a->id]->answerformat = $a->answerformat; 00783 } 00784 } 00785 } 00786 00792 public function delete_question($questionid, $contextid) { 00793 global $DB; 00794 00795 $this->delete_files($questionid, $contextid); 00796 00797 $extraquestionfields = $this->extra_question_fields(); 00798 if (is_array($extraquestionfields)) { 00799 $question_extension_table = array_shift($extraquestionfields); 00800 $DB->delete_records($question_extension_table, 00801 array($this->questionid_column_name() => $questionid)); 00802 } 00803 00804 $extraanswerfields = $this->extra_answer_fields(); 00805 if (is_array($extraanswerfields)) { 00806 $answer_extension_table = array_shift($extraanswerfields); 00807 $DB->delete_records_select($answer_extension_table, 00808 'answerid IN (SELECT qa.id FROM {question_answers} qa WHERE qa.question = ?)', 00809 array($questionid)); 00810 } 00811 00812 $DB->delete_records('question_answers', array('question' => $questionid)); 00813 00814 $DB->delete_records('question_hints', array('questionid' => $questionid)); 00815 } 00816 00830 public function actual_number_of_questions($question) { 00831 // By default, each question is given one number 00832 return 1; 00833 } 00834 00840 public function get_random_guess_score($questiondata) { 00841 return 0; 00842 } 00843 00871 public function get_possible_responses($questiondata) { 00872 return array(); 00873 } 00874 00880 public function find_standard_scripts() { 00881 global $PAGE; 00882 00883 $plugindir = $this->plugin_dir(); 00884 $plugindirrel = 'question/type/' . $this->name(); 00885 00886 if (file_exists($plugindir . '/script.js')) { 00887 $PAGE->requires->js('/' . $plugindirrel . '/script.js'); 00888 } 00889 if (file_exists($plugindir . '/script.php')) { 00890 $PAGE->requires->js('/' . $plugindirrel . '/script.php'); 00891 } 00892 } 00893 00910 public function finished_edit_wizard($form) { 00911 //In the default case there is only one edit page. 00912 return true; 00913 } 00914 00916 00917 /* 00918 * Imports question from the Moodle XML format 00919 * 00920 * Imports question using information from extra_question_fields function 00921 * If some of you fields contains id's you'll need to reimplement this 00922 */ 00923 public function import_from_xml($data, $question, $format, $extra=null) { 00924 $question_type = $data['@']['type']; 00925 if ($question_type != $this->name()) { 00926 return false; 00927 } 00928 00929 $extraquestionfields = $this->extra_question_fields(); 00930 if (!is_array($extraquestionfields)) { 00931 return false; 00932 } 00933 00934 //omit table name 00935 array_shift($extraquestionfields); 00936 $qo = $format->import_headers($data); 00937 $qo->qtype = $question_type; 00938 00939 foreach ($extraquestionfields as $field) { 00940 $qo->$field = $format->getpath($data, array('#', $field, 0, '#'), ''); 00941 } 00942 00943 // run through the answers 00944 $answers = $data['#']['answer']; 00945 $a_count = 0; 00946 $extraanswersfields = $this->extra_answer_fields(); 00947 if (is_array($extraanswersfields)) { 00948 array_shift($extraanswersfields); 00949 } 00950 foreach ($answers as $answer) { 00951 $ans = $format->import_answer($answer); 00952 if (!$this->has_html_answers()) { 00953 $qo->answer[$a_count] = $ans->answer['text']; 00954 } else { 00955 $qo->answer[$a_count] = $ans->answer; 00956 } 00957 $qo->fraction[$a_count] = $ans->fraction; 00958 $qo->feedback[$a_count] = $ans->feedback; 00959 if (is_array($extraanswersfields)) { 00960 foreach ($extraanswersfields as $field) { 00961 $qo->{$field}[$a_count] = 00962 $format->getpath($answer, array('#', $field, 0, '#'), ''); 00963 } 00964 } 00965 ++$a_count; 00966 } 00967 return $qo; 00968 } 00969 00970 /* 00971 * Export question to the Moodle XML format 00972 * 00973 * Export question using information from extra_question_fields function 00974 * If some of you fields contains id's you'll need to reimplement this 00975 */ 00976 public function export_to_xml($question, $format, $extra=null) { 00977 $extraquestionfields = $this->extra_question_fields(); 00978 if (!is_array($extraquestionfields)) { 00979 return false; 00980 } 00981 00982 //omit table name 00983 array_shift($extraquestionfields); 00984 $expout=''; 00985 foreach ($extraquestionfields as $field) { 00986 $exportedvalue = $format->xml_escape($question->options->$field); 00987 $expout .= " <$field>{$exportedvalue}</$field>\n"; 00988 } 00989 00990 $extraanswersfields = $this->extra_answer_fields(); 00991 if (is_array($extraanswersfields)) { 00992 array_shift($extraanswersfields); 00993 } 00994 foreach ($question->options->answers as $answer) { 00995 // TODO this should be re-factored to use $format->write_answer(). 00996 $percent = 100 * $answer->fraction; 00997 $expout .= " <answer fraction=\"$percent\" {$format->format($answer->answerformat)}>\n"; 00998 $expout .= $format->writetext($answer->answer, 3, false); 00999 $expout .= " <feedback {$format->format($answer->feedbackformat)}>\n"; 01000 $expout .= $format->writetext($answer->feedback, 4, false); 01001 $expout .= " </feedback>\n"; 01002 if (is_array($extraanswersfields)) { 01003 foreach ($extraanswersfields as $field) { 01004 $exportedvalue = $format->xml_escape($answer->$field); 01005 $expout .= " <{$field}>{$exportedvalue}</{$field}>\n"; 01006 } 01007 } 01008 01009 $expout .= " </answer>\n"; 01010 } 01011 return $expout; 01012 } 01013 01019 public function generate_test($name, $courseid=null) { 01020 $form = new stdClass(); 01021 $form->name = $name; 01022 $form->questiontextformat = 1; 01023 $form->questiontext = 'test question, generated by script'; 01024 $form->defaultmark = 1; 01025 $form->penalty = 0.3333333; 01026 $form->generalfeedback = "Well done"; 01027 01028 $context = get_context_instance(CONTEXT_COURSE, $courseid); 01029 $newcategory = question_make_default_categories(array($context)); 01030 $form->category = $newcategory->id . ',1'; 01031 01032 $question = new stdClass(); 01033 $question->courseid = $courseid; 01034 $question->qtype = $this->qtype; 01035 return array($form, $question); 01036 } 01037 01043 protected function get_context_by_category_id($category) { 01044 global $DB; 01045 $contextid = $DB->get_field('question_categories', 'contextid', array('id'=>$category)); 01046 $context = get_context_instance_by_id($contextid); 01047 return $context; 01048 } 01049 01065 protected function import_or_save_files($field, $context, $component, $filearea, $itemid) { 01066 if (!empty($field['itemid'])) { 01067 // This is the normal case. We are safing the questions editing form. 01068 return file_save_draft_area_files($field['itemid'], $context->id, $component, 01069 $filearea, $itemid, $this->fileoptions, trim($field['text'])); 01070 01071 } else if (!empty($field['files'])) { 01072 // This is the case when we are doing an import. 01073 foreach ($field['files'] as $file) { 01074 $this->import_file($context, $component, $filearea, $itemid, $file); 01075 } 01076 } 01077 return trim($field['text']); 01078 } 01079 01086 public function move_files($questionid, $oldcontextid, $newcontextid) { 01087 $fs = get_file_storage(); 01088 $fs->move_area_files_to_new_context($oldcontextid, 01089 $newcontextid, 'question', 'questiontext', $questionid); 01090 $fs->move_area_files_to_new_context($oldcontextid, 01091 $newcontextid, 'question', 'generalfeedback', $questionid); 01092 } 01093 01103 protected function move_files_in_answers($questionid, $oldcontextid, 01104 $newcontextid, $answerstoo = false) { 01105 global $DB; 01106 $fs = get_file_storage(); 01107 01108 $answerids = $DB->get_records_menu('question_answers', 01109 array('question' => $questionid), 'id', 'id,1'); 01110 foreach ($answerids as $answerid => $notused) { 01111 if ($answerstoo) { 01112 $fs->move_area_files_to_new_context($oldcontextid, 01113 $newcontextid, 'question', 'answer', $answerid); 01114 } 01115 $fs->move_area_files_to_new_context($oldcontextid, 01116 $newcontextid, 'question', 'answerfeedback', $answerid); 01117 } 01118 } 01119 01129 protected function move_files_in_combined_feedback($questionid, $oldcontextid, 01130 $newcontextid) { 01131 global $DB; 01132 $fs = get_file_storage(); 01133 01134 $fs->move_area_files_to_new_context($oldcontextid, 01135 $newcontextid, 'question', 'correctfeedback', $questionid); 01136 $fs->move_area_files_to_new_context($oldcontextid, 01137 $newcontextid, 'question', 'partiallycorrectfeedback', $questionid); 01138 $fs->move_area_files_to_new_context($oldcontextid, 01139 $newcontextid, 'question', 'incorrectfeedback', $questionid); 01140 } 01141 01147 protected function delete_files($questionid, $contextid) { 01148 $fs = get_file_storage(); 01149 $fs->delete_area_files($contextid, 'question', 'questiontext', $questionid); 01150 $fs->delete_area_files($contextid, 'question', 'generalfeedback', $questionid); 01151 } 01152 01160 protected function delete_files_in_answers($questionid, $contextid, $answerstoo = false) { 01161 global $DB; 01162 $fs = get_file_storage(); 01163 01164 $answerids = $DB->get_records_menu('question_answers', 01165 array('question' => $questionid), 'id', 'id,1'); 01166 foreach ($answerids as $answerid => $notused) { 01167 if ($answerstoo) { 01168 $fs->delete_area_files($contextid, 'question', 'answer', $answerid); 01169 } 01170 $fs->delete_area_files($contextid, 'question', 'answerfeedback', $answerid); 01171 } 01172 } 01173 01181 protected function delete_files_in_combined_feedback($questionid, $contextid) { 01182 global $DB; 01183 $fs = get_file_storage(); 01184 01185 $fs->delete_area_files($contextid, 01186 'question', 'correctfeedback', $questionid); 01187 $fs->delete_area_files($contextid, 01188 'question', 'partiallycorrectfeedback', $questionid); 01189 $fs->delete_area_files($contextid, 01190 'question', 'incorrectfeedback', $questionid); 01191 } 01192 01193 public function import_file($context, $component, $filearea, $itemid, $file) { 01194 $fs = get_file_storage(); 01195 $record = new stdClass(); 01196 if (is_object($context)) { 01197 $record->contextid = $context->id; 01198 } else { 01199 $record->contextid = $context; 01200 } 01201 $record->component = $component; 01202 $record->filearea = $filearea; 01203 $record->itemid = $itemid; 01204 $record->filename = $file->name; 01205 $record->filepath = '/'; 01206 return $fs->create_file_from_string($record, $this->decode_file($file)); 01207 } 01208 01209 protected function decode_file($file) { 01210 switch ($file->encoding) { 01211 case 'base64': 01212 default: 01213 return base64_decode($file->content); 01214 } 01215 } 01216 } 01217 01218 01226 class question_possible_response { 01232 public $responseclass; 01234 public $fraction; 01241 public function __construct($responseclass, $fraction) { 01242 $this->responseclass = $responseclass; 01243 $this->fraction = $fraction; 01244 } 01245 01246 public static function no_response() { 01247 return new question_possible_response(get_string('noresponse', 'question'), 0); 01248 } 01249 }