853030d383c7d147fe6d5d7fcafe0482eaec288b
[moodle.git] / question / type / calculated / edit_calculated_form.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * Defines the editing form for the calculated question type.
19  *
20  * @package    qtype
21  * @subpackage calculated
22  * @copyright  2007 Jamie Pratt me@jamiep.org
23  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
27 defined('MOODLE_INTERNAL') || die();
29 require_once($CFG->dirroot . '/question/type/numerical/edit_numerical_form.php');
32 /**
33  * Calculated question type editing form definition.
34  *
35  * @copyright  2007 Jamie Pratt me@jamiep.org
36  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
37  */
38 class qtype_calculated_edit_form extends qtype_numerical_edit_form {
39     /**
40      * Handle to the question type for this question.
41      *
42      * @var qtype_calculated
43      */
44     public $qtypeobj;
45     public $questiondisplay;
46     public $activecategory;
47     public $categorychanged = false;
48     public $initialname = '';
49     public $reload = false;
51     public function __construct($submiturl, $question, $category, $contexts,
52             $formeditable = true) {
53         global $CFG, $DB;
54         $this->question = $question;
55         $this->reload = optional_param('reload', false, PARAM_BOOL);
57         if (!$this->reload) { // Use database data as this is first pass.
58             if (isset($this->question->id)) {
59                 // Remove prefix #{..}# if exists.
60                 $this->initialname = $question->name;
61                 $regs= array();
62                 if (preg_match('~#\{([^[:space:]]*)#~', $question->name , $regs)) {
63                     $question->name = str_replace($regs[0], '', $question->name);
64                 };
65             }
66         }
67         parent::__construct($submiturl, $question, $category, $contexts, $formeditable);
68     }
70     public function get_per_answer_fields($mform, $label, $gradeoptions,
71             &$repeatedoptions, &$answersoption) {
72         $repeated = parent::get_per_answer_fields($mform, $label, $gradeoptions,
73                 $repeatedoptions, $answersoption);
75         // Reorganise answer options group. 0 is the answer. 1 is tolerance. 2 is Grade.
76         $answeroptions = $repeated[0]->getElements();
77         // Tolerance field will be part of its own group.
78         $tolerance = $answeroptions[1];
80         // Update Answer options group to contain only answer and grade fields.
81         $answeroptions[0]->setSize(55);
82         $answeroptions = array($answeroptions[0], $answeroptions[2]);
83         $repeated[0]->setElements($answeroptions);
85         // Update answer field and group label.
86         $repeated[0]->setLabel(get_string('answerformula', 'qtype_calculated', '{no}') . ' =');
87         $answeroptions[0]->setLabel(get_string('answerformula', 'qtype_calculated', '{no}') . ' =');
89         // Get feedback field to re append later.
90         $feedback = array_pop($repeated);
92         // Create tolerance group.
93         $answertolerance = array();
94         $tolerance->setLabel(get_string('tolerance', 'qtype_calculated') . '=');
95         $answertolerance[] = $tolerance;
96         $answertolerance[] = $mform->createElement('select', 'tolerancetype',
97                 get_string('tolerancetype', 'qtype_calculated'), $this->qtypeobj->tolerance_types());
98         $repeated[] = $mform->createElement('group', 'answertolerance',
99                  get_string('tolerance', 'qtype_calculated'), $answertolerance, null, false);
100         $repeatedoptions['tolerance']['default'] = 0.01;
102         // Create display group.
103         $answerdisplay = array();
104         $answerdisplay[] = $mform->createElement('select', 'correctanswerlength',
105                 get_string('answerdisplay', 'qtype_calculated'), range(0, 9));
106         $repeatedoptions['correctanswerlength']['default'] = 2;
108         $answerlengthformats = array(
109             '1' => get_string('decimalformat', 'qtype_numerical'),
110             '2' => get_string('significantfiguresformat', 'qtype_calculated')
111         );
112         $answerdisplay[] = $mform->createElement('select', 'correctanswerformat',
113                 get_string('correctanswershowsformat', 'qtype_calculated'), $answerlengthformats);
114         $repeated[] = $mform->createElement('group', 'answerdisplay',
115                  get_string('answerdisplay', 'qtype_calculated'), $answerdisplay, null, false);
117         // Add feedback.
118         $repeated[] = $feedback;
120         return $repeated;
121     }
123     /**
124      * Add question-type specific form fields.
125      *
126      * @param MoodleQuickForm $mform the form being built.
127      */
128     protected function definition_inner($mform) {
129         $this->qtypeobj = question_bank::get_qtype($this->qtype());
130         $label = get_string('sharedwildcards', 'qtype_calculated');
131         $mform->addElement('hidden', 'initialcategory', 1);
132         $mform->addElement('hidden', 'reload', 1);
133         $mform->setType('initialcategory', PARAM_INT);
134         $mform->setType('reload', PARAM_BOOL);
135         $html2 = $this->qtypeobj->print_dataset_definitions_category($this->question);
136         $mform->insertElementBefore(
137                 $mform->createElement('static', 'listcategory', $label, $html2), 'name');
138         if (isset($this->question->id)) {
139             $mform->insertElementBefore($mform->createElement('static', 'initialname',
140                     get_string('questionstoredname', 'qtype_calculated'),
141                     $this->initialname), 'name');
142         };
143         $addfieldsname = 'updatecategory';
144         $addstring = get_string('updatecategory', 'qtype_calculated');
145         $mform->registerNoSubmitButton($addfieldsname);
147         $mform->insertElementBefore(
148                 $mform->createElement('submit', $addfieldsname, $addstring), 'listcategory');
149         $mform->registerNoSubmitButton('createoptionbutton');
151         // Editing as regular question.
152         $mform->setType('single', PARAM_INT);
154         $mform->addElement('hidden', 'shuffleanswers', '1');
155         $mform->setType('shuffleanswers', PARAM_INT);
156         $mform->addElement('hidden', 'answernumbering', 'abc');
157         $mform->setType('answernumbering', PARAM_SAFEDIR);
159         $this->add_per_answer_fields($mform, get_string('answerhdr', 'qtype_calculated', '{no}'),
160                 question_bank::fraction_options(), 1, 1);
162         $repeated = array();
164         $this->add_unit_options($mform, $this);
165         $this->add_unit_fields($mform, $this);
166         $this->add_interactive_settings();
168         // Hidden elements.
169         $mform->addElement('hidden', 'synchronize', '');
170         $mform->setType('synchronize', PARAM_INT);
171         $mform->addElement('hidden', 'wizard', 'datasetdefinitions');
172         $mform->setType('wizard', PARAM_ALPHA);
173     }
175     protected function can_preview() {
176         return false; // Generally not possible for calculated questions on this page.
177     }
179     public function data_preprocessing($question) {
180         $question = parent::data_preprocessing($question);
181         $question = $this->data_preprocessing_answers($question);
182         $question = $this->data_preprocessing_hints($question);
183         $question = $this->data_preprocessing_units($question);
184         $question = $this->data_preprocessing_unit_options($question);
186         if (isset($question->options->synchronize)) {
187             $question->synchronize = $question->options->synchronize;
188         }
190         return $question;
191     }
193     protected function data_preprocessing_answers($question, $withanswerfiles = false) {
194         $question = parent::data_preprocessing_answers($question, $withanswerfiles);
195         if (empty($question->options->answers)) {
196             return $question;
197         }
199         $key = 0;
200         foreach ($question->options->answers as $answer) {
201             // See comment in the parent method about this hack.
202             unset($this->_form->_defaultValues["tolerancetype[$key]"]);
203             unset($this->_form->_defaultValues["correctanswerlength[$key]"]);
204             unset($this->_form->_defaultValues["correctanswerformat[$key]"]);
206             $question->tolerancetype[$key]       = $answer->tolerancetype;
207             $question->correctanswerlength[$key] = $answer->correctanswerlength;
208             $question->correctanswerformat[$key] = $answer->correctanswerformat;
209             $key++;
210         }
212         return $question;
213     }
215     public function qtype() {
216         return 'calculated';
217     }
219     public function validation($data, $files) {
221         // Verifying for errors in {=...} in question text.
222         $qtext = "";
223         $qtextremaining = $data['questiontext']['text'];
224         $possibledatasets = $this->qtypeobj->find_dataset_names($data['questiontext']['text']);
225         foreach ($possibledatasets as $name => $value) {
226             $qtextremaining = str_replace('{'.$name.'}', '1', $qtextremaining);
227         }
228         while (preg_match('~\{=([^[:space:]}]*)}~', $qtextremaining, $regs1)) {
229             $qtextsplits = explode($regs1[0], $qtextremaining, 2);
230             $qtext = $qtext.$qtextsplits[0];
231             $qtextremaining = $qtextsplits[1];
232             if (!empty($regs1[1]) && $formulaerrors =
233                     qtype_calculated_find_formula_errors($regs1[1])) {
234                 if (!isset($errors['questiontext'])) {
235                     $errors['questiontext'] = $formulaerrors.':'.$regs1[1];
236                 } else {
237                     $errors['questiontext'] .= '<br/>'.$formulaerrors.':'.$regs1[1];
238                 }
239             }
240         }
242         $errors = parent::validation($data, $files);
244         // Check that the answers use datasets.
245         $answers = $data['answer'];
246         $mandatorydatasets = array();
247         foreach ($answers as $key => $answer) {
248             $mandatorydatasets += $this->qtypeobj->find_dataset_names($answer);
249         }
250         if (empty($mandatorydatasets)) {
251             foreach ($answers as $key => $answer) {
252                 $errors['answeroptions['.$key.']'] =
253                         get_string('atleastonewildcard', 'qtype_calculated');
254             }
255         }
257         // Validate the answer format.
258         foreach ($answers as $key => $answer) {
259             $trimmedanswer = trim($answer);
260             if (trim($answer)) {
261                 if ($data['correctanswerformat'][$key] == 2 &&
262                         $data['correctanswerlength'][$key] == '0') {
263                     $errors['answerdisplay['.$key.']'] =
264                             get_string('zerosignificantfiguresnotallowed', 'qtype_calculated');
265                 }
266             }
267         }
269         return $errors;
270     }
272     protected function is_valid_answer($answer, $data) {
273         return !qtype_calculated_find_formula_errors($answer);
274     }
276     protected function valid_answer_message($answer) {
277         if (!$answer) {
278             return get_string('mustenteraformulaorstar', 'qtype_numerical');
279         } else {
280             return qtype_calculated_find_formula_errors($answer);
281         }
282     }