MDL-16094 File storage conversion Quiz and Questions
[moodle.git] / question / type / calculated / edit_calculated_form.php
1 <?php
3 // This file is part of Moodle - http://moodle.org/
4 //
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
18 /**
19  * Defines the editing form for the calculated question type.
20  *
21  * @copyright &copy; 2007 Jamie Pratt
22  * @author Jamie Pratt me@jamiep.org
23  * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
24  * @package questionbank
25  * @subpackage questiontypes
26  */
28 /**
29  * calculated editing form definition.
30  */
31 class question_edit_calculated_form extends question_edit_form {
32     /**
33      * Handle to the question type for this question.
34      *
35      * @var question_calculated_qtype
36      */
37     public $qtypeobj;
38     public $questiondisplay;
39     public $activecategory;
40     public $categorychanged = false;
41     public $initialname = '';
42     public $reload = false;
44     function question_edit_calculated_form(&$submiturl, &$question, &$category, &$contexts, $formeditable = true){
45         global $QTYPES, $SESSION, $CFG, $DB;
46         $this->question = $question;
47         $this->qtypeobj =& $QTYPES[$this->question->qtype];
48         if  (  "1" == optional_param('reload','', PARAM_INT )) {
49             $this->reload = true ;
50         }else {
51             $this->reload = false ;
52         }
54         if(!$this->reload ){ // use database data as this is first pass
55             if(isset($this->question->id )){
56                 // remove prefix #{..}# if exists
57                 $this->initialname = $question->name ;
58                 $regs= array();
59                 if(preg_match('~#\{([^[:space:]]*)#~',$question->name , $regs)){
60                     $question->name = str_replace($regs[0], '', $question->name);
61                 };
62             }
63         }else {
64         }
65         parent::question_edit_form($submiturl, $question, $category, $contexts, $formeditable);
66     }
68     function get_per_answer_fields(&$mform, $label, $gradeoptions, &$repeatedoptions, &$answersoption) {
69         // $repeated = parent::get_per_answer_fields($mform, $label, $gradeoptions, $repeatedoptions, $answersoption);
70         $repeated = array();
71         $repeated[] =& $mform->createElement('header', 'answerhdr', $label);
72         $repeated[] =& $mform->createElement('text', 'answer', get_string('answer', 'quiz'), array('size' => 50));
73         $repeated[] =& $mform->createElement('select', 'fraction', get_string('grade'), $gradeoptions);
74         $repeated[] =& $mform->createElement('editor', 'feedback', get_string('feedback', 'quiz'), null, $this->editoroptions);
75         $repeatedoptions['answer']['type'] = PARAM_RAW;
76         $repeatedoptions['fraction']['default'] = 0;
77         $answersoption = 'answers';
79         $mform->setType('answer', PARAM_NOTAGS);
81         $addrepeated = array();
82         $addrepeated[] =& $mform->createElement('text', 'tolerance', get_string('tolerance', 'qtype_calculated'));
83         $addrepeated[] =& $mform->createElement('select', 'tolerancetype', get_string('tolerancetype', 'quiz'), $this->qtypeobj->tolerance_types());
84         $repeatedoptions['tolerance']['type'] = PARAM_NUMBER;
85         $repeatedoptions['tolerance']['default'] = 0.01;
87         $addrepeated[] =&  $mform->createElement('select', 'correctanswerlength', get_string('correctanswershows', 'qtype_calculated'), range(0, 9));
88         $repeatedoptions['correctanswerlength']['default'] = 2;
90         $answerlengthformats = array('1' => get_string('decimalformat', 'quiz'), '2' => get_string('significantfiguresformat', 'quiz'));
91         $addrepeated[] =&  $mform->createElement('select', 'correctanswerformat', get_string('correctanswershowsformat', 'qtype_calculated'), $answerlengthformats);
92         array_splice($repeated, 3, 0, $addrepeated);
93         $repeated[1]->setLabel(get_string('correctanswerformula', 'quiz').'=');
95         return $repeated;
96     }
98     /**
99      * Add question-type specific form fields.
100      *
101      * @param MoodleQuickForm $mform the form being built.
102      */
103     function definition_inner(&$mform) {
104         global $QTYPES;
105         $this->qtypeobj =& $QTYPES[$this->qtype()];
106         // echo code left for testing period
107         // echo "<p>question ".optional_param('multichoice', '', PARAM_RAW)." optional<pre>";print_r($this->question);echo "</pre></p>";
108         $label = get_string('sharedwildcards', 'qtype_calculated');
109         $mform->addElement('hidden', 'initialcategory', 1);
110         $mform->addElement('hidden', 'reload', 1);
111         $mform->setType('initialcategory', PARAM_INT);
112         $html2 = $this->qtypeobj->print_dataset_definitions_category($this->question);
113         $mform->insertElementBefore($mform->createElement('static','listcategory',$label,$html2),'name');
114         if(isset($this->question->id )){
115             $mform->insertElementBefore($mform->createElement('static','initialname',get_string('questionstoredname','qtype_calculated'),$this->initialname),'name');
116         };
117         $addfieldsname='updatecategory';
118         $addstring=get_string("updatecategory", "qtype_calculated");
119         $mform->registerNoSubmitButton($addfieldsname);
121         $mform->insertElementBefore(    $mform->createElement('submit', $addfieldsname, $addstring),'listcategory');
122         $mform->registerNoSubmitButton('createoptionbutton');
124         //editing as regular
125         $mform->setType('single', PARAM_INT);
127         $mform->addElement('hidden','shuffleanswers', '1');
128         $mform->setType('shuffleanswers', PARAM_INT);
129         $mform->addElement('hidden','answernumbering', 'abc');
130         $mform->setType('answernumbering', PARAM_SAFEDIR);
132         $creategrades = get_grade_options();
134         $this->add_per_answer_fields($mform, get_string('answerhdr', 'qtype_calculated', '{no}'), $creategrades->gradeoptions, 1, 1);
136         $repeated = array();
138         $QTYPES['numerical']->add_units_options($mform,$this);
139         $QTYPES['numerical']->add_units_elements($mform,$this);
141         $mform->addElement('header', 'overallfeedbackhdr', get_string('overallfeedback', 'qtype_multichoice'));
142         foreach (array('correctfeedback', 'partiallycorrectfeedback', 'incorrectfeedback') as $feedbackname) {
143             $mform->addElement('editor', $feedbackname, get_string($feedbackname, 'qtype_calculated'), null, $this->editoroptions);
144             $mform->setType($feedbackname, PARAM_RAW);
145         }
147         //hidden elements
148         $mform->addElement('hidden', 'synchronize', '');
149         $mform->setType('synchronize', PARAM_INT);
150         $mform->addElement('hidden', 'wizard', 'datasetdefinitions');
151         $mform->setType('wizard', PARAM_ALPHA);
152     }
154     function data_preprocessing($question) {
155         global $QTYPES;
157         $default_values = array();
158         if (isset($question->options)){
159             $answers = $question->options->answers;
160             if (count($answers)) {
161                 $key = 0;
162                 foreach ($answers as $answer){
163                     $draftid = file_get_submitted_draft_itemid('feedback['.$key.']');
164                     $default_values['answer['.$key.']'] = $answer->answer;
165                     $default_values['fraction['.$key.']'] = $answer->fraction;
166                     $default_values['tolerance['.$key.']'] = $answer->tolerance;
167                     $default_values['tolerancetype['.$key.']'] = $answer->tolerancetype;
168                     $default_values['correctanswerlength['.$key.']'] = $answer->correctanswerlength;
169                     $default_values['correctanswerformat['.$key.']'] = $answer->correctanswerformat;
170                     $default_values['feedback['.$key.']'] = array();
171                     $default_values['feedback['.$key.']']['text'] = file_prepare_draft_area(
172                         $draftid,           // draftid
173                         $this->context->id, // context
174                         'question', // component
175                         'answerfeedback',         // filarea
176                         !empty($answer->id)?(int)$answer->id:null, // itemid
177                         $this->fileoptions, // options
178                         $answer->feedback   // text
179                     );
180                     $default_values['feedback['.$key.']']['format'] = $answer->feedbackformat;
181                     $default_values['feedback['.$key.']']['itemid'] = $draftid;
182                     $key++;
183                 }
184             }
185             $default_values['synchronize'] = $question->options->synchronize ;
186             // set unit data, prepare files in instruction area
187             $QTYPES['numerical']->set_numerical_unit_data($this, $question, $default_values);
188         }
189         if (isset($question->options->single)){
190             $default_values['single'] =  $question->options->single;
191             $default_values['answernumbering'] =  $question->options->answernumbering;
192             $default_values['shuffleanswers'] =  $question->options->shuffleanswers;
193             //$default_values['correctfeedback'] =  $question->options->correctfeedback;
194             //$default_values['partiallycorrectfeedback'] =  $question->options->partiallycorrectfeedback;
195             //$default_values['incorrectfeedback'] =  $question->options->incorrectfeedback;
196             // prepare feedback editor to display files in draft area
197             foreach (array('correctfeedback', 'partiallycorrectfeedback', 'incorrectfeedback') as $feedbackname) {
198                 $draftid = file_get_submitted_draft_itemid($feedbackname);
199                 $text = $question->options->$feedbackname;
200                 $feedbackformat = $feedbackname . 'format';
201                 $format = $question->options->$feedbackformat;
202                 $default_values[$feedbackname] = array();
203                 $default_values[$feedbackname]['text'] = file_prepare_draft_area(
204                     $draftid,       // draftid
205                     $this->context->id,    // context
206                     'qtype_calculated',   // component
207                     $feedbackname,         // filarea
208                     !empty($question->id)?(int)$question->id:null, // itemid
209                     $this->fileoptions,    // options
210                     $text      // text
211                 );
212                 $default_values[$feedbackname]['format'] = $format;
213                 $default_values[$feedbackname]['itemid'] = $draftid;
214             }
215         }
216         $default_values['submitbutton'] = get_string('nextpage', 'qtype_calculated');
217         $default_values['makecopy'] = get_string('makecopynextpage', 'qtype_calculated');
218         /* set the wild cards category display given that on loading the category element is
219         unselected when processing this function but have a valid value when processing the
220         update category button. The value can be obtain by
221          $qu->category =$this->_form->_elements[$this->_form->_elementIndex['category']]->_values[0];
222          but is coded using existing functions
223          */
224         $qu = new stdClass;
225         $el = new stdClass;
226         /* no need to call elementExists() here */
227         if ($this->_form->elementExists('category')){
228             $el=$this->_form->getElement('category');
229         } else {
230             $el=$this->_form->getElement('categorymoveto');
231         }
232         if($value =$el->getSelected()) {
233             $qu->category =$value[0];
234         }else {
235             $qu->category=$question->category;// on load  $question->category is set by question.php
236         }
237         $html2 = $this->qtypeobj->print_dataset_definitions_category($qu);
238         $this->_form->_elements[$this->_form->_elementIndex['listcategory']]->_text = $html2 ;
239         $question = (object)((array)$question + $default_values);
241         return $question;
242     }
244     function qtype() {
245         return 'calculated';
246     }
248     function validation($data, $files) {
249         global $QTYPES;
250         // echo code left for testing period
252         // echo "<p>question <pre>";print_r($this->question);echo "</pre></p>";
253         // echo "<p>data <pre>";print_r($data);echo "</pre></p>";
255         $errors = parent::validation($data, $files);
256         //verifying for errors in {=...} in question text;
257         $qtext = "";
258         $qtextremaining = $data['questiontext']['text'];
259         $possibledatasets = $this->qtypeobj->find_dataset_names($data['questiontext']['text']);
260         foreach ($possibledatasets as $name => $value) {
261             $qtextremaining = str_replace('{'.$name.'}', '1', $qtextremaining);
262         }
263         // echo "numericalquestion qtextremaining <pre>";print_r($possibledatasets);
264         while (preg_match('~\{=([^[:space:]}]*)}~', $qtextremaining, $regs1)) {
265             $qtextsplits = explode($regs1[0], $qtextremaining, 2);
266             $qtext =$qtext.$qtextsplits[0];
267             $qtextremaining = $qtextsplits[1];
268             if (!empty($regs1[1]) && $formulaerrors = qtype_calculated_find_formula_errors($regs1[1])) {
269                 if(!isset($errors['questiontext'])){
270                     $errors['questiontext'] = $formulaerrors.':'.$regs1[1] ;
271                 }else {
272                     $errors['questiontext'] .= '<br/>'.$formulaerrors.':'.$regs1[1];
273                 }
274             }
275         }
276         $answers = $data['answer'];
277         $answercount = 0;
278         $maxgrade = false;
279         $possibledatasets = $this->qtypeobj->find_dataset_names($data['questiontext']['text']);
280         $mandatorydatasets = array();
281         foreach ($answers as $key => $answer){
282             $mandatorydatasets += $this->qtypeobj->find_dataset_names($answer);
283         }
284         if ( count($mandatorydatasets )==0){
285             //  $errors['questiontext']=get_string('atleastonewildcard', 'qtype_datasetdependent');
286             foreach ($answers as $key => $answer){
287                 $errors['answer['.$key.']'] = get_string('atleastonewildcard', 'qtype_datasetdependent');
288             }
289         }
290         // regular calculated
291         foreach ($answers as $key => $answer){
292             //check no of choices
293             // the * for everykind of answer not actually implemented
294             $trimmedanswer = trim($answer);
295             if (($trimmedanswer!='')||$answercount==0){
296                 $eqerror = qtype_calculated_find_formula_errors($trimmedanswer);
297                 if (FALSE !== $eqerror){
298                     $errors['answer['.$key.']'] = $eqerror;
299                 }
300             }
301             if ($trimmedanswer!=''){
302                 if ('2' == $data['correctanswerformat'][$key]
303                     && '0' == $data['correctanswerlength'][$key]) {
304                         $errors['correctanswerlength['.$key.']'] = get_string('zerosignificantfiguresnotallowed','quiz');
305                     }
306                 if (!is_numeric($data['tolerance'][$key])){
307                     $errors['tolerance['.$key.']'] = get_string('mustbenumeric', 'qtype_calculated');
308                 }
309                 if ($data['fraction'][$key] == 1) {
310                     $maxgrade = true;
311                 }
313                 $answercount++;
314             }
315             //check grades
317             //TODO how should grade checking work here??
318                 /*if ($answer != '') {
319                     if ($data['fraction'][$key] > 0) {
320                         $totalfraction += $data['fraction'][$key];
321                     }
322                     if ($data['fraction'][$key] > $maxfraction) {
323                         $maxfraction = $data['fraction'][$key];
324                     }
325                 }*/
326         }
328         //grade checking :
329         /// Perform sanity checks on fractional grades
330             /*if ( ) {
331                 if ($maxfraction != 1) {
332                     $maxfraction = $maxfraction * 100;
333                     $errors['fraction[0]'] = get_string('errfractionsnomax', 'qtype_multichoice', $maxfraction);
334                 }
335             } else {
336                 $totalfraction = round($totalfraction,2);
337                 if ($totalfraction != 1) {
338                     $totalfraction = $totalfraction * 100;
339                     $errors['fraction[0]'] = get_string('errfractionsaddwrong', 'qtype_multichoice', $totalfraction);
340                 }
341             }
342             $units = $data['unit'];
343             if (count($units)) {
344                 foreach ($units as $key => $unit){
345                     if (is_numeric($unit)){
346                         $errors['unit['.$key.']'] = get_string('mustnotbenumeric', 'qtype_calculated');
347                     }
348                     $trimmedunit = trim($unit);
349                     $trimmedmultiplier = trim($data['multiplier'][$key]);
350                     if (!empty($trimmedunit)){
351                         if (empty($trimmedmultiplier)){
352                             $errors['multiplier['.$key.']'] = get_string('youmustenteramultiplierhere', 'qtype_calculated');
353                         }
354                         if (!is_numeric($trimmedmultiplier)){
355                             $errors['multiplier['.$key.']'] = get_string('mustbenumeric', 'qtype_calculated');
356                         }
358                     }
359                 }
360             }*/
361         $QTYPES['numerical']->validate_numerical_options($data, $errors) ;
362         if ($answercount==0){
363             $errors['answer[0]'] = get_string('atleastoneanswer', 'qtype_calculated');
364         }
365         if ($maxgrade == false) {
366             $errors['fraction[0]'] = get_string('fractionsnomax', 'question');
367         }
370         return $errors;
371     }