MDL-27410 qtype_calculated works in my unit tests.
[moodle.git] / question / type / calculated / edit_calculated_form.php
CommitLineData
aeb15530 1<?php
fe6ce234
DC
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/>.
16
271ffe3f 17/**
18 * Defines the editing form for the calculated question type.
19 *
b04a4319
TH
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
271ffe3f 24 */
25
b04a4319 26
a17b297d
TH
27defined('MOODLE_INTERNAL') || die();
28
29
271ffe3f 30/**
d3603157 31 * Calculated question type editing form definition.
b04a4319
TH
32 *
33 * @copyright 2007 Jamie Pratt me@jamiep.org
34 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
271ffe3f 35 */
afe24f85 36class qtype_calculated_edit_form extends question_edit_form {
9aa022fe 37 /**
38 * Handle to the question type for this question.
39 *
f184c65c 40 * @var qtype_calculated
9aa022fe 41 */
d90b016b 42 public $qtypeobj;
fe6ce234
DC
43 public $questiondisplay;
44 public $activecategory;
45 public $categorychanged = false;
d90b016b 46 public $initialname = '';
fe6ce234
DC
47 public $reload = false;
48
f184c65c
TH
49 public function __construct($submiturl, $question, $category, $contexts,
50 $formeditable = true) {
51 global $CFG, $DB;
d90b016b 52 $this->question = $question;
f184c65c
TH
53 $this->qtypeobj = $QTYPES[$this->question->qtype];
54 if ('1' == optional_param('reload', '', PARAM_INT)) {
55 $this->reload = true;
56 } else {
57 $this->reload = false;
d90b016b 58 }
2aef1fe5 59
f184c65c
TH
60 if (!$this->reload) { // use database data as this is first pass
61 if (isset($this->question->id)) {
d90b016b 62 // remove prefix #{..}# if exists
f184c65c 63 $this->initialname = $question->name;
d90b016b 64 $regs= array();
f184c65c 65 if (preg_match('~#\{([^[:space:]]*)#~', $question->name , $regs)) {
d90b016b 66 $question->name = str_replace($regs[0], '', $question->name);
fe6ce234 67 };
d90b016b 68 }
fe6ce234 69 }
f184c65c 70 parent::__construct($submiturl, $question, $category, $contexts, $formeditable);
d90b016b 71 }
fe6ce234 72
f184c65c
TH
73 public function get_per_answer_fields($mform, $label, $gradeoptions,
74 &$repeatedoptions, &$answersoption) {
fe6ce234 75 $repeated = array();
f184c65c
TH
76 $repeated[] = $mform->createElement('header', 'answerhdr', $label);
77 $repeated[] = $mform->createElement('text', 'answer',
78 get_string('answer', 'question'), array('size' => 50));
79 $repeated[] = $mform->createElement('select', 'fraction',
80 get_string('grade'), $gradeoptions);
81 $repeated[] = $mform->createElement('editor', 'feedback',
82 get_string('feedback', 'question'), null, $this->editoroptions);
28a27ef1 83 $repeatedoptions['answer']['type'] = PARAM_RAW;
84 $repeatedoptions['fraction']['default'] = 0;
85 $answersoption = 'answers';
86
2aef1fe5 87 $mform->setType('answer', PARAM_NOTAGS);
88
89 $addrepeated = array();
f184c65c
TH
90 $addrepeated[] = $mform->createElement('text', 'tolerance',
91 get_string('tolerance', 'qtype_calculated'));
92 $addrepeated[] = $mform->createElement('select', 'tolerancetype',
93 get_string('tolerancetype', 'qtype_numerical'),
94 $this->qtypeobj->tolerance_types());
2aef1fe5 95 $repeatedoptions['tolerance']['type'] = PARAM_NUMBER;
96 $repeatedoptions['tolerance']['default'] = 0.01;
2aef1fe5 97
f184c65c
TH
98 $addrepeated[] = $mform->createElement('select', 'correctanswerlength',
99 get_string('correctanswershows', 'qtype_calculated'), range(0, 9));
2aef1fe5 100 $repeatedoptions['correctanswerlength']['default'] = 2;
101
f184c65c
TH
102 $answerlengthformats = array(
103 '1' => get_string('decimalformat', 'qtype_numerical'),
104 '2' => get_string('significantfiguresformat', 'quiz')
105 );
106 $addrepeated[] = $mform->createElement('select', 'correctanswerformat',
107 get_string('correctanswershowsformat', 'qtype_calculated'), $answerlengthformats);
2aef1fe5 108 array_splice($repeated, 3, 0, $addrepeated);
f184c65c 109 $repeated[1]->setLabel(get_string('correctanswerformula', 'qtype_calculated') . ' = ');
2aef1fe5 110 return $repeated;
111 }
112
271ffe3f 113 /**
114 * Add question-type specific form fields.
115 *
9aa022fe 116 * @param MoodleQuickForm $mform the form being built.
271ffe3f 117 */
c7df5006 118 protected function definition_inner(&$mform) {
f184c65c 119 $this->qtypeobj = $QTYPES[$this->qtype()];
cf146692 120 $label = get_string('sharedwildcards', 'qtype_calculated');
a6d46515 121 $mform->addElement('hidden', 'initialcategory', 1);
d90b016b 122 $mform->addElement('hidden', 'reload', 1);
d18e0fe6 123 $mform->setType('initialcategory', PARAM_INT);
a6d46515 124 $html2 = $this->qtypeobj->print_dataset_definitions_category($this->question);
f184c65c
TH
125 $mform->insertElementBefore(
126 $mform->createElement('static', 'listcategory', $label, $html2), 'name');
127 if (isset($this->question->id)) {
128 $mform->insertElementBefore($mform->createElement('static', 'initialname',
129 get_string('questionstoredname', 'qtype_calculated'),
130 $this->initialname), 'name');
d90b016b 131 };
f184c65c
TH
132 $addfieldsname = 'updatecategory';
133 $addstring = get_string('updatecategory', 'qtype_calculated');
fe6ce234 134 $mform->registerNoSubmitButton($addfieldsname);
271e6dec 135
f184c65c
TH
136 $mform->insertElementBefore(
137 $mform->createElement('submit', $addfieldsname, $addstring), 'listcategory');
28a27ef1 138 $mform->registerNoSubmitButton('createoptionbutton');
aeb15530 139
ab8b5142 140 //editing as regular
fe6ce234 141 $mform->setType('single', PARAM_INT);
aeb15530 142
f184c65c 143 $mform->addElement('hidden', 'shuffleanswers', '1');
fe6ce234 144 $mform->setType('shuffleanswers', PARAM_INT);
f184c65c 145 $mform->addElement('hidden', 'answernumbering', 'abc');
fe6ce234 146 $mform->setType('answernumbering', PARAM_SAFEDIR);
271ffe3f 147
271ffe3f 148 $creategrades = get_grade_options();
ab8b5142 149
f184c65c
TH
150 $this->add_per_answer_fields($mform, get_string('answerhdr', 'qtype_calculated', '{no}'),
151 $creategrades->gradeoptions, 1, 1);
92186abc 152
271e6dec 153 $repeated = array();
271ffe3f 154
f184c65c
TH
155 $QTYPES['numerical']->add_units_options($mform, $this);
156 $QTYPES['numerical']->add_units_elements($mform, $this);
fe6ce234 157
9aa022fe 158 //hidden elements
e593233a 159 $mform->addElement('hidden', 'synchronize', '');
d18e0fe6 160 $mform->setType('synchronize', PARAM_INT);
60b5ecd3 161 $mform->addElement('hidden', 'wizard', 'datasetdefinitions');
162 $mform->setType('wizard', PARAM_ALPHA);
271ffe3f 163 }
164
f184c65c 165 public function data_preprocessing($question) {
81053284
PP
166 global $QTYPES;
167
cf146692 168 $default_values = array();
f184c65c 169 if (isset($question->options)) {
271ffe3f 170 $answers = $question->options->answers;
171 if (count($answers)) {
172 $key = 0;
f184c65c 173 foreach ($answers as $answer) {
fe6ce234 174 $draftid = file_get_submitted_draft_itemid('feedback['.$key.']');
9af77e9d 175 $default_values['answer['.$key.']'] = $answer->answer;
92186abc 176 $default_values['fraction['.$key.']'] = $answer->fraction;
177 $default_values['tolerance['.$key.']'] = $answer->tolerance;
f7089b63 178 $default_values['tolerancetype['.$key.']'] = $answer->tolerancetype;
92186abc 179 $default_values['correctanswerlength['.$key.']'] = $answer->correctanswerlength;
9aa022fe 180 $default_values['correctanswerformat['.$key.']'] = $answer->correctanswerformat;
fe6ce234
DC
181 $default_values['feedback['.$key.']'] = array();
182 $default_values['feedback['.$key.']']['text'] = file_prepare_draft_area(
183 $draftid, // draftid
184 $this->context->id, // context
185 'question', // component
186 'answerfeedback', // filarea
187 !empty($answer->id)?(int)$answer->id:null, // itemid
188 $this->fileoptions, // options
189 $answer->feedback // text
190 );
191 $default_values['feedback['.$key.']']['format'] = $answer->feedbackformat;
192 $default_values['feedback['.$key.']']['itemid'] = $draftid;
271ffe3f 193 $key++;
194 }
195 }
f184c65c 196 $default_values['synchronize'] = $question->options->synchronize;
fe6ce234
DC
197 // set unit data, prepare files in instruction area
198 $QTYPES['numerical']->set_numerical_unit_data($this, $question, $default_values);
81053284 199 }
f184c65c 200 if (isset($question->options->single)) {
cf146692
PP
201 $default_values['single'] = $question->options->single;
202 $default_values['answernumbering'] = $question->options->answernumbering;
203 $default_values['shuffleanswers'] = $question->options->shuffleanswers;
fe6ce234 204 // prepare feedback editor to display files in draft area
cf146692 205 }
8fc3e643 206 $default_values['submitbutton'] = get_string('nextpage', 'qtype_calculated');
207 $default_values['makecopy'] = get_string('makecopynextpage', 'qtype_calculated');
f184c65c
TH
208 $default_values['returnurl'] = '0';
209
0ff4bd08
TH
210 $qu = new stdClass();
211 $el = new stdClass();
fe6ce234 212 /* no need to call elementExists() here */
f184c65c
TH
213 if ($this->_form->elementExists('category')) {
214 $el = $this->_form->getElement('category');
fe6ce234 215 } else {
f184c65c 216 $el = $this->_form->getElement('categorymoveto');
fe6ce234 217 }
f184c65c
TH
218 if ($value = $el->getSelected()) {
219 $qu->category = $value[0];
220 } else {
221 // on load $question->category is set by question.php
222 $qu->category = $question->category;
271e6dec 223 }
0dd3e11c 224 $html2 = $this->qtypeobj->print_dataset_definitions_category($qu);
f184c65c 225 $this->_form->_elements[$this->_form->_elementIndex['listcategory']]->_text = $html2;
79bb7202 226 $question = (object)((array)$question + $default_values);
92186abc 227
fe6ce234 228 return $question;
271ffe3f 229 }
230
f184c65c 231 public function qtype() {
271ffe3f 232 return 'calculated';
233 }
234
f184c65c 235 public function validation($data, $files) {
28a27ef1 236
fe93ba83 237 $errors = parent::validation($data, $files);
f184c65c 238 // verifying for errors in {=...} in question text;
bfdc0bce 239 $qtext = "";
fe6ce234
DC
240 $qtextremaining = $data['questiontext']['text'];
241 $possibledatasets = $this->qtypeobj->find_dataset_names($data['questiontext']['text']);
242 foreach ($possibledatasets as $name => $value) {
bfdc0bce 243 $qtextremaining = str_replace('{'.$name.'}', '1', $qtextremaining);
244 }
fe6ce234 245 while (preg_match('~\{=([^[:space:]}]*)}~', $qtextremaining, $regs1)) {
bfdc0bce 246 $qtextsplits = explode($regs1[0], $qtextremaining, 2);
f184c65c 247 $qtext = $qtext.$qtextsplits[0];
bfdc0bce 248 $qtextremaining = $qtextsplits[1];
f184c65c
TH
249 if (!empty($regs1[1]) && $formulaerrors =
250 qtype_calculated_find_formula_errors($regs1[1])) {
251 if (!isset($errors['questiontext'])) {
252 $errors['questiontext'] = $formulaerrors.':'.$regs1[1];
253 } else {
bfdc0bce 254 $errors['questiontext'] .= '<br/>'.$formulaerrors.':'.$regs1[1];
271e6dec 255 }
bfdc0bce 256 }
271e6dec 257 }
9af77e9d 258 $answers = $data['answer'];
271ffe3f 259 $answercount = 0;
a6d46515 260 $maxgrade = false;
fe6ce234 261 $possibledatasets = $this->qtypeobj->find_dataset_names($data['questiontext']['text']);
f6232d58 262 $mandatorydatasets = array();
f184c65c 263 foreach ($answers as $key => $answer) {
a6d46515 264 $mandatorydatasets += $this->qtypeobj->find_dataset_names($answer);
271e6dec 265 }
f184c65c
TH
266 if (count($mandatorydatasets)== 0) {
267 foreach ($answers as $key => $answer) {
268 $errors['answer['.$key.']'] =
269 get_string('atleastonewildcard', 'qtype_datasetdependent');
271e6dec 270 }
271 }
fe6ce234 272 // regular calculated
f184c65c
TH
273 foreach ($answers as $key => $answer) {
274 // check no of choices
fe6ce234
DC
275 // the * for everykind of answer not actually implemented
276 $trimmedanswer = trim($answer);
f184c65c 277 if ($trimmedanswer != '' || $answercount == 0) {
fe6ce234 278 $eqerror = qtype_calculated_find_formula_errors($trimmedanswer);
f184c65c 279 if (false !== $eqerror) {
fe6ce234 280 $errors['answer['.$key.']'] = $eqerror;
9aa022fe 281 }
fe6ce234 282 }
f184c65c 283 if ($trimmedanswer != '') {
fe6ce234 284 if ('2' == $data['correctanswerformat'][$key]
f184c65c
TH
285 && '0' == $data['correctanswerlength'][$key]) {
286 $errors['correctanswerlength['.$key.']'] =
287 get_string('zerosignificantfiguresnotallowed', 'qtype_calculated');
288 }
289 if (!is_numeric($data['tolerance'][$key])) {
290 $errors['tolerance['.$key.']'] =
291 get_string('mustbenumeric', 'qtype_calculated');
a6d46515 292 }
fe6ce234
DC
293 if ($data['fraction'][$key] == 1) {
294 $maxgrade = true;
295 }
296
297 $answercount++;
298 }
fe6ce234 299 }
aeb15530 300
f184c65c
TH
301 $QTYPES['numerical']->validate_numerical_options($data, $errors);
302 if ($answercount == 0) {
fe6ce234
DC
303 $errors['answer[0]'] = get_string('atleastoneanswer', 'qtype_calculated');
304 }
305 if ($maxgrade == false) {
306 $errors['fraction[0]'] = get_string('fractionsnomax', 'question');
307 }
308
271ffe3f 309 return $errors;
310 }
311}