Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.

/**
 * Question type class for the calculated multiple-choice question type.
 *
 * @package    qtype
 * @subpackage calculatedmulti
 * @copyright  2009 Pierre Pichet
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

> use qtype_calculatedmulti\qtype_calculatedmulti_answer;
defined('MOODLE_INTERNAL') || die(); require_once($CFG->dirroot . '/question/type/multichoice/questiontype.php'); require_once($CFG->dirroot . '/question/type/calculated/questiontype.php'); /** * The calculated multiple-choice question type. * * @copyright 2009 Pierre Pichet * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class qtype_calculatedmulti extends qtype_calculated { public function save_question_options($question) { global $CFG, $DB; $context = $question->context; // Make it impossible to save bad formulas anywhere. $this->validate_question_data($question); // Calculated options. $update = true; $options = $DB->get_record('question_calculated_options', array('question' => $question->id)); if (!$options) { $options = new stdClass(); $options->question = $question->id; $options->correctfeedback = ''; $options->partiallycorrectfeedback = ''; $options->incorrectfeedback = ''; $options->id = $DB->insert_record('question_calculated_options', $options); } $options->synchronize = $question->synchronize; $options->single = $question->single; $options->answernumbering = $question->answernumbering; $options->shuffleanswers = $question->shuffleanswers; $options = $this->save_combined_feedback_helper($options, $question, $context, true); $DB->update_record('question_calculated_options', $options); // Get old versions of the objects. if (!$oldanswers = $DB->get_records('question_answers', array('question' => $question->id), 'id ASC')) { $oldanswers = array(); } if (!$oldoptions = $DB->get_records('question_calculated', array('question' => $question->id), 'answer ASC')) { $oldoptions = array(); } // Insert all the new answers. foreach ($question->answer as $key => $answerdata) { if (is_array($answerdata)) { $answerdata = $answerdata['text']; } if (trim($answerdata) == '') { continue; } // Update an existing answer if possible. $answer = array_shift($oldanswers); if (!$answer) { $answer = new stdClass(); $answer->question = $question->id; $answer->answer = ''; $answer->feedback = ''; $answer->id = $DB->insert_record('question_answers', $answer); } if (is_array($answerdata)) { // Doing an import. $answer->answer = $this->import_or_save_files($answerdata, $context, 'question', 'answer', $answer->id); $answer->answerformat = $answerdata['format']; } else { // Saving the form. $answer->answer = $answerdata; $answer->answerformat = FORMAT_HTML; } $answer->fraction = $question->fraction[$key]; $answer->feedback = $this->import_or_save_files($question->feedback[$key], $context, 'question', 'answerfeedback', $answer->id); $answer->feedbackformat = $question->feedback[$key]['format']; $DB->update_record("question_answers", $answer); // Set up the options object. if (!$options = array_shift($oldoptions)) { $options = new stdClass(); } $options->question = $question->id; $options->answer = $answer->id; $options->tolerance = trim($question->tolerance[$key]); $options->tolerancetype = trim($question->tolerancetype[$key]); $options->correctanswerlength = trim($question->correctanswerlength[$key]); $options->correctanswerformat = trim($question->correctanswerformat[$key]); // Save options. if (isset($options->id)) { // Reusing existing record. $DB->update_record('question_calculated', $options); } else { // New options. $DB->insert_record('question_calculated', $options); } } // Delete old answer records. if (!empty($oldanswers)) { foreach ($oldanswers as $oa) { $DB->delete_records('question_answers', array('id' => $oa->id)); } } if (!empty($oldoptions)) { foreach ($oldoptions as $oo) { $DB->delete_records('question_calculated', array('id' => $oo->id)); } } $this->save_hints($question, true); if (isset($question->import_process) && $question->import_process) { $this->import_datasets($question); } // Report any problems. if (!empty($result->notice)) { return $result; } return true; } protected function validate_answer($answer) { $error = qtype_calculated_find_formula_errors_in_text($answer); if ($error) { throw new coding_exception($error); } } protected function validate_question_data($question) { parent::validate_question_data($question); $this->validate_text($question->correctfeedback['text']); $this->validate_text($question->partiallycorrectfeedback['text']); $this->validate_text($question->incorrectfeedback['text']); } protected function make_question_instance($questiondata) { question_bank::load_question_definition_classes($this->name()); if ($questiondata->options->single) { $class = 'qtype_calculatedmulti_single_question'; } else { $class = 'qtype_calculatedmulti_multi_question'; } return new $class(); } protected function initialise_question_instance(question_definition $question, $questiondata) { question_type::initialise_question_instance($question, $questiondata); $question->shuffleanswers = $questiondata->options->shuffleanswers; $question->answernumbering = $questiondata->options->answernumbering; if (!empty($questiondata->options->layout)) { $question->layout = $questiondata->options->layout; } else { $question->layout = qtype_multichoice_single_question::LAYOUT_VERTICAL; } $question->synchronised = $questiondata->options->synchronize; $this->initialise_combined_feedback($question, $questiondata, true); $this->initialise_question_answers($question, $questiondata); foreach ($questiondata->options->answers as $a) { $question->answers[$a->id]->correctanswerlength = $a->correctanswerlength; $question->answers[$a->id]->correctanswerformat = $a->correctanswerformat; } $question->datasetloader = new qtype_calculated_dataset_loader($questiondata->id); } /** * Public override method, created so that make_answer will be available * for use by classes which extend qtype_multichoice_base. * * @param stdClass $answer Moodle DB question_answers object. * @return question_answer */ public function make_answer($answer) {
< return parent::make_answer($answer);
> return new qtype_calculatedmulti_answer($answer->id, $answer->answer, > $answer->fraction, $answer->feedback, $answer->feedbackformat);
} protected function make_hint($hint) { return question_hint_with_parts::load_from_record($hint); } public function comment_header($question) { $strheader = ''; $delimiter = ''; $answers = $question->options->answers; foreach ($answers as $key => $answer) { $ans = shorten_text($answer->answer, 17, true); $strheader .= $delimiter.$ans; $delimiter = '<br/><br/>'; } return $strheader; } public function comment_on_datasetitems($qtypeobj, $questionid, $questiontext, $answers, $data, $number) { $comment = new stdClass(); $comment->stranswers = array(); $comment->outsidelimit = false; $comment->answers = array(); $answers = fullclone($answers); foreach ($answers as $key => $answer) { // Evaluate the equations i.e {=5+4). $anssubstituted = $this->substitute_variables($answer->answer, $data); $formulas = $this->find_formulas($anssubstituted); $replaces = []; foreach ($formulas as $formula) { if ($formulaerrors = qtype_calculated_find_formula_errors($formula)) { $str = $formulaerrors; } else { eval('$str = ' . $formula . ';'); } $replaces[$formula] = $str; } $anstext = str_replace(array_keys($replaces), array_values($replaces), $anssubstituted); $comment->stranswers[$key] = $anssubstituted.'<br/>'.$anstext; } return fullclone($comment); } public function get_virtual_qtype() { return question_bank::get_qtype('multichoice'); } public function get_possible_responses($questiondata) { if ($questiondata->options->single) { $responses = array(); foreach ($questiondata->options->answers as $aid => $answer) { $responses[$aid] = new question_possible_response($answer->answer, $answer->fraction); } $responses[null] = question_possible_response::no_response(); return array($questiondata->id => $responses); } else { $parts = array(); foreach ($questiondata->options->answers as $aid => $answer) { $parts[$aid] = array($aid => new question_possible_response($answer->answer, $answer->fraction)); } return $parts; } } public function move_files($questionid, $oldcontextid, $newcontextid) { $fs = get_file_storage(); parent::move_files($questionid, $oldcontextid, $newcontextid); $this->move_files_in_answers($questionid, $oldcontextid, $newcontextid, true); $this->move_files_in_hints($questionid, $oldcontextid, $newcontextid); $fs->move_area_files_to_new_context($oldcontextid, $newcontextid, 'qtype_calculatedmulti', 'correctfeedback', $questionid); $fs->move_area_files_to_new_context($oldcontextid, $newcontextid, 'qtype_calculatedmulti', 'partiallycorrectfeedback', $questionid); $fs->move_area_files_to_new_context($oldcontextid, $newcontextid, 'qtype_calculatedmulti', 'incorrectfeedback', $questionid); } protected function delete_files($questionid, $contextid) { $fs = get_file_storage(); parent::delete_files($questionid, $contextid); $this->delete_files_in_answers($questionid, $contextid, true); $this->delete_files_in_hints($questionid, $contextid); $fs->delete_area_files($contextid, 'qtype_calculatedmulti', 'correctfeedback', $questionid); $fs->delete_area_files($contextid, 'qtype_calculatedmulti', 'partiallycorrectfeedback', $questionid); $fs->delete_area_files($contextid, 'qtype_calculatedmulti', 'incorrectfeedback', $questionid); } }