MDL-16094 File storage conversion Quiz and Questions
[moodle.git] / question / type / calculatedmulti / edit_calculatedmulti_form.php
1 <?php
2 /**
3  * Defines the editing form for the calculated question type.
4  *
5  * @copyright &copy; 2007 Jamie Pratt
6  * @author Jamie Pratt me@jamiep.org
7  * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
8  * @package questionbank
9  * @subpackage questiontypes
10  */
12 /**
13  * calculated editing form definition.
14  */
15 class question_edit_calculatedmulti_form extends question_edit_form {
16     /**
17      * Handle to the question type for this question.
18      *
19      * @var question_calculatedmulti_qtype
20      */
21     public $qtypeobj;
22     public $questiondisplay ;
23     public $initialname = '';
24     public $reload = false ;
25     function question_edit_calculatedmulti_form(&$submiturl, &$question, &$category, &$contexts, $formeditable = true) {
26         global $QTYPES, $SESSION, $CFG, $DB;
27         $this->question = $question;
28         $this->qtypeobj =& $QTYPES[$this->question->qtype];
29         if  (  "1" == optional_param('reload','', PARAM_INT )) {
30             $this->reload = true ;
31         }else {
32             $this->reload = false ;
33         }
34         if(!$this->reload ){ // use database data as this is first pass
35             if(isset($this->question->id )){
36                 // remove prefix #{..}# if exists
37                 $this->initialname = $question->name ;
38                 $regs= array();
39                 if(preg_match('~#\{([^[:space:]]*)#~',$question->name , $regs)){
40                     $question->name = str_replace($regs[0], '', $question->name);
41                 };
42             }
43         }else {
44         }
45         parent::question_edit_form($submiturl, $question, $category, $contexts, $formeditable);
46     }
48     function get_per_answer_fields(&$mform, $label, $gradeoptions, &$repeatedoptions, &$answersoption) {
49         //     $repeated = parent::get_per_answer_fields($mform, $label, $gradeoptions, $repeatedoptions, $answersoption);
50         $repeated = array();
51         $repeated[] =& $mform->createElement('header', 'answerhdr', $label);
52         //   if ($this->editasmultichoice == 1){
53         $repeated[] =& $mform->createElement('text', 'answer', get_string('answer', 'quiz'), array('size' => 50));
54         $repeated[] =& $mform->createElement('select', 'fraction', get_string('grade'), $gradeoptions);
55         $repeated[] =& $mform->createElement('editor', 'feedback', get_string('feedback', 'quiz'), null, $this->editoroptions);
56         $repeatedoptions['answer']['type'] = PARAM_RAW;
57         $repeatedoptions['fraction']['default'] = 0;
58         $answersoption = 'answers';
60         $mform->setType('answer', PARAM_NOTAGS);
62         $addrepeated = array();
63         $addrepeated[] =& $mform->createElement('hidden', 'tolerance');
64         $addrepeated[] =& $mform->createElement('hidden', 'tolerancetype',1);
65         $repeatedoptions['tolerance']['type'] = PARAM_NUMBER;
66         $repeatedoptions['tolerance']['default'] = 0.01;
68         $addrepeated[] =&  $mform->createElement('select', 'correctanswerlength', get_string('correctanswershows', 'qtype_calculated'), range(0, 9));
69         $repeatedoptions['correctanswerlength']['default'] = 2;
71         $answerlengthformats = array('1' => get_string('decimalformat', 'quiz'), '2' => get_string('significantfiguresformat', 'quiz'));
72         $addrepeated[] =&  $mform->createElement('select', 'correctanswerformat', get_string('correctanswershowsformat', 'qtype_calculated'), $answerlengthformats);
73         array_splice($repeated, 3, 0, $addrepeated);
74         $repeated[1]->setLabel('...<strong>{={x}+..}</strong>...');
76         return $repeated;
77     }
79     /**
80      * Add question-type specific form fields.
81      *
82      * @param MoodleQuickForm $mform the form being built.
83      */
84     function definition_inner(&$mform) {
85         global $QTYPES;
86         $this->qtypeobj =& $QTYPES[$this->qtype()];
87         // echo code left for testing period
88         // echo "<p>question ".optional_param('multichoice', '', PARAM_RAW)." optional<pre>";print_r($this->question);echo "</pre></p>";
89         $label = get_string("sharedwildcards", "qtype_calculated");
90         $mform->addElement('hidden', 'initialcategory', 1);
91         $mform->addElement('hidden', 'reload', 1);
92         $mform->setType('initialcategory', PARAM_INT);
94         //     $html2 = $this->qtypeobj->print_dataset_definitions_category($this->question);
95         $html2 ="";
96         $mform->insertElementBefore($mform->createElement('static','listcategory',$label,$html2),'name');
97         if(isset($this->question->id )){
98             $mform->insertElementBefore($mform->createElement('static','initialname',get_string('questionstoredname','qtype_calculated'),$this->initialname),'name');
99         };
100         $addfieldsname='updatecategory';
101         $addstring=get_string("updatecategory", "qtype_calculated");
102         $mform->registerNoSubmitButton($addfieldsname);
103         $this->editasmultichoice =  1 ;
106         $mform->insertElementBefore(    $mform->createElement('submit', $addfieldsname, $addstring),'listcategory');
107         $mform->registerNoSubmitButton('createoptionbutton');
108         $mform->addElement('hidden', 'multichoice',$this->editasmultichoice);
109         $mform->setType('multichoice', PARAM_INT);
112         //            $mform->addElement('header', 'choicehdr',get_string('multichoicecalculatedquestion', 'qtype_calculated'));
113         $menu = array(get_string('answersingleno', 'qtype_multichoice'), get_string('answersingleyes', 'qtype_multichoice'));
114         $mform->addElement('select', 'single', get_string('answerhowmany', 'qtype_multichoice'), $menu);
115         $mform->setDefault('single', 1);
117         $mform->addElement('advcheckbox', 'shuffleanswers', get_string('shuffleanswers', 'qtype_multichoice'), null, null, array(0,1));
118         $mform->setHelpButton('shuffleanswers', array('multichoiceshuffle', get_string('shuffleanswers','qtype_multichoice'), 'qtype_multichoice'));
119         $mform->setDefault('shuffleanswers', 1);
121         $numberingoptions = $QTYPES['multichoice']->get_numbering_styles();
122         $menu = array();
123         foreach ($numberingoptions as $numberingoption) {
124             $menu[$numberingoption] = get_string('answernumbering' . $numberingoption, 'qtype_multichoice');
125         }
126         $mform->addElement('select', 'answernumbering', get_string('answernumbering', 'qtype_multichoice'), $menu);
127         $mform->setDefault('answernumbering', 'abc');
129         $creategrades = get_grade_options();
130         $this->add_per_answer_fields($mform, get_string('choiceno', 'qtype_multichoice', '{no}'),
131             $creategrades->gradeoptionsfull, max(5, QUESTION_NUMANS_START));
134         $repeated = array();
135         //   if ($this->editasmultichoice == 1){
136         $nounits = optional_param('nounits', 1, PARAM_INT);
137         $mform->addElement('hidden', 'nounits', $nounits);
138         $mform->setType('nounits', PARAM_INT);
139         $mform->setConstants(array('nounits'=>$nounits));
140         for ($i=0; $i< $nounits; $i++) {
141             $mform->addElement('hidden','unit'."[$i]", optional_param('unit'."[$i]", '', PARAM_NOTAGS));
142             $mform->setType('unit'."[$i]", PARAM_NOTAGS);
143             $mform->addElement('hidden', 'multiplier'."[$i]", optional_param('multiplier'."[$i]", '', PARAM_NUMBER));
144             $mform->setType('multiplier'."[$i]", PARAM_NUMBER);
145         }
146         $mform->addElement('hidden','unitgradingtype',optional_param('unitgradingtype', '', PARAM_INT)) ;
147         $mform->addElement('hidden','unitpenalty',optional_param('unitpenalty', '', PARAM_NUMBER)) ;
148         $mform->addElement('hidden','showunits',optional_param('showunits', '', PARAM_INT)) ;
149         $mform->addElement('hidden','unitsleft',optional_param('unitsleft', '', PARAM_INT)) ;
150         $mform->addElement('hidden','instructions',optional_param('instructions', '', PARAM_RAW)) ;
152         $mform->setType('addunits','hidden');
153         $mform->addElement('header', 'overallfeedbackhdr', get_string('overallfeedback', 'qtype_multichoice'));
155         foreach (array('correctfeedback', 'partiallycorrectfeedback', 'incorrectfeedback') as $feedbackname) {
156             $mform->addElement('editor', $feedbackname, get_string($feedbackname, 'qtype_multichoice'), null, $this->editoroptions);
157             $mform->setType($feedbackname, PARAM_RAW);
158         }
159         //hidden elements
160         $mform->addElement('hidden', 'synchronize', '');
161         $mform->setType('synchronize', PARAM_INT);
162         if (isset($this->question->options)&& isset($this->question->options->synchronize) ){
163             $mform->setDefault("synchronize", $this->question->options->synchronize);
164         } else {
165             $mform->setDefault("synchronize", 0 );
166         }
167         $mform->addElement('hidden', 'wizard', 'datasetdefinitions');
168         $mform->setType('wizard', PARAM_ALPHA);
169     }
171     function data_preprocessing($question) {
172         $default_values['multichoice']= $this->editasmultichoice ; //$this->editasmultichoice ;
173         if (isset($question->options)){
174             $answers = $question->options->answers;
175             if (count($answers)) {
176                 $key = 0;
177                 foreach ($answers as $answer){
178                     $draftid = file_get_submitted_draft_itemid('feedback['.$key.']');
179                     $default_values['answer['.$key.']'] = $answer->answer;
180                     $default_values['fraction['.$key.']'] = $answer->fraction;
181                     $default_values['tolerance['.$key.']'] = $answer->tolerance;
182                     $default_values['tolerancetype['.$key.']'] = $answer->tolerancetype;
183                     $default_values['correctanswerlength['.$key.']'] = $answer->correctanswerlength;
184                     $default_values['correctanswerformat['.$key.']'] = $answer->correctanswerformat;
185                     $default_values['feedback['.$key.']'] = array();
186                     // prepare draftarea
187                     $default_values['feedback['.$key.']']['text'] = file_prepare_draft_area($draftid, $this->context->id, 'question', 'answerfeedback', empty($answer->id)?null:(int)$answer->id, null, $answer->feedback);
188                     $default_values['feedback['.$key.']']['format'] = $answer->feedbackformat;
189                     $default_values['feedback['.$key.']']['itemid'] = $draftid;
190                     $key++;
191                 }
192             }
193             //  $default_values['unitgradingtype'] = $question->options->unitgradingtype ;
194             //  $default_values['unitpenalty'] = $question->options->unitpenalty ;
195             //  $default_values['showunits'] = $question->options->showunits ;
196             //  $default_values['unitsleft'] = $question->options->unitsleft ;
197             //  $default_values['instructions'] = $question->options->instructions  ;
198             $default_values['synchronize'] = $question->options->synchronize ;
200             if (isset($question->options->units)){
201                 $units  = array_values($question->options->units);
202                 // make sure the default unit is at index 0
203                 usort($units, create_function('$a, $b',
204                     'if (1.0 === (float)$a->multiplier) { return -1; } else '.
205                     'if (1.0 === (float)$b->multiplier) { return 1; } else { return 0; }'));
206                 if (count($units)) {
207                     $key = 0;
208                     foreach ($units as $unit){
209                         $default_values['unit['.$key.']'] = $unit->unit;
210                         $default_values['multiplier['.$key.']'] = $unit->multiplier;
211                         $key++;
212                     }
213                 }
214             }
215         }
216         if (isset($question->options->single)){
217             $default_values['single'] =  $question->options->single;
218             $default_values['answernumbering'] =  $question->options->answernumbering;
219             $default_values['shuffleanswers'] =  $question->options->shuffleanswers;
220         }
221         $default_values['submitbutton'] = get_string('nextpage', 'qtype_calculated');
222         $default_values['makecopy'] = get_string('makecopynextpage', 'qtype_calculated');
224         // prepare draft files
225         foreach (array('correctfeedback', 'partiallycorrectfeedback', 'incorrectfeedback') as $feedbackname) {
226             if (!isset($question->options->$feedbackname)) {
227                 continue;
228             }
229             $text = $question->options->$feedbackname;
230             $draftid = file_get_submitted_draft_itemid($feedbackname);
231             $feedbackformat = $feedbackname . 'format';
232             $format = $question->options->$feedbackformat;
233             $default_values[$feedbackname] = array();
234             $default_values[$feedbackname]['text'] = file_prepare_draft_area(
235                 $draftid,                // draftid
236                 $this->context->id,      // context
237                 'qtype_calculatedmulti', // component
238                 $feedbackname,           // filarea
239                 !empty($question->id)?(int)$question->id:null, // itemid
240                 $this->fileoptions,      // options
241                 $text                    // text
242             );
243             $default_values[$feedbackname]['format'] = $format;
244             $default_values[$feedbackname]['itemid'] = $draftid;
245         }
246         /**
247          * set the wild cards category display given that on loading the category element is
248          * unselected when processing this function but have a valid value when processing the
249          * update category button. The value can be obtain by
250          * $qu->category =$this->_form->_elements[$this->_form->_elementIndex['category']]->_values[0];
251          * but is coded using existing functions
252          */
253         $qu = new stdClass;
254         $el = new stdClass;
255         /* no need to call elementExists() here */
256         if ($this->_form->elementExists('category')){
257             $el=$this->_form->getElement('category');
258         } else {
259             $el=$this->_form->getElement('categorymoveto');
260         }
261         if($value =$el->getSelected()) {
262             $qu->category =$value[0];
263         }else {
264             $qu->category=$question->category;// on load  $question->category is set by question.php
265         }
266         $html2 = $this->qtypeobj->print_dataset_definitions_category($qu);
267         $this->_form->_elements[$this->_form->_elementIndex['listcategory']]->_text = $html2 ;
268         $question = (object)((array)$question + $default_values);
269         return $question;
270     }
272     function qtype() {
273         return 'calculatedmulti';
274     }
276     function validation($data, $files) {
277         // echo code left for testing period
278         // echo "<p>question <pre>";print_r($this->question);echo "</pre></p>";
279         // echo "<p>data <pre>";print_r($data);echo "</pre></p>";
281         $errors = parent::validation($data, $files);
282         //verifying for errors in {=...} in question text;
283         $qtext = "";
284         $qtextremaining = $data['questiontext']['text'];
285         $possibledatasets = $this->qtypeobj->find_dataset_names($data['questiontext']['text']);
286         foreach ($possibledatasets as $name => $value) {
287             $qtextremaining = str_replace('{'.$name.'}', '1', $qtextremaining);
288         }
289         // echo "numericalquestion qtextremaining <pre>";print_r($possibledatasets);
290         while (preg_match('~\{=([^[:space:]}]*)}~', $qtextremaining, $regs1)) {
291             $qtextsplits = explode($regs1[0], $qtextremaining, 2);
292             $qtext =$qtext.$qtextsplits[0];
293             $qtextremaining = $qtextsplits[1];
294             if (!empty($regs1[1]) && $formulaerrors = qtype_calculated_find_formula_errors($regs1[1])) {
295                 if(!isset($errors['questiontext'])){
296                     $errors['questiontext'] = $formulaerrors.':'.$regs1[1] ;
297                 }else {
298                     $errors['questiontext'] .= '<br/>'.$formulaerrors.':'.$regs1[1];
299                 }
300             }
301         }
302         $answers = $data['answer'];
303         $answercount = 0;
304         $maxgrade = false;
305         $possibledatasets = $this->qtypeobj->find_dataset_names($data['questiontext']['text']);
306         $mandatorydatasets = array();
307         foreach ($answers as $key => $answer){
308             $mandatorydatasets += $this->qtypeobj->find_dataset_names($answer);
309         }
310         if ( count($mandatorydatasets )==0){
311             //  $errors['questiontext']=get_string('atleastonewildcard', 'qtype_datasetdependent');
312             foreach ($answers as $key => $answer){
313                 $errors['answer['.$key.']'] = get_string('atleastonewildcard', 'qtype_datasetdependent');
314             }
315         }
316         if ($data['multichoice']== 1 ){
317             foreach ($answers as $key => $answer){
318                 $trimmedanswer = trim($answer);
319                 if (($trimmedanswer!='')||$answercount==0){
320                     //verifying for errors in {=...} in answer text;
321                     $qanswer = "";
322                     $qanswerremaining =  $trimmedanswer ;
323                     $possibledatasets = $this->qtypeobj->find_dataset_names($trimmedanswer);
324                     foreach ($possibledatasets as $name => $value) {
325                         $qanswerremaining = str_replace('{'.$name.'}', '1', $qanswerremaining);
326                     }
327                     //     echo "numericalquestion qanswerremaining <pre>";print_r($possibledatasets);
328                     while  (preg_match('~\{=([^[:space:]}]*)}~', $qanswerremaining, $regs1)) {
329                         $qanswersplits = explode($regs1[0], $qanswerremaining, 2);
330                         $qanswer =$qanswer.$qanswersplits[0];
331                         $qanswerremaining = $qanswersplits[1];
332                         if (!empty($regs1[1]) && $formulaerrors = qtype_calculated_find_formula_errors($regs1[1])) {
333                             if(!isset($errors['answer['.$key.']'])){
334                                 $errors['answer['.$key.']'] = $formulaerrors.':'.$regs1[1] ;
335                             }else {
336                                 $errors['answer['.$key.']'] .= '<br/>'.$formulaerrors.':'.$regs1[1];
337                             }
338                         }
339                     }
340                 }
341                 if ($trimmedanswer!=''){
342                     if ('2' == $data['correctanswerformat'][$key]
343                         && '0' == $data['correctanswerlength'][$key]) {
344                             $errors['correctanswerlength['.$key.']'] = get_string('zerosignificantfiguresnotallowed','quiz');
345                         }
346                     if (!is_numeric($data['tolerance'][$key])){
347                         $errors['tolerance['.$key.']'] = get_string('mustbenumeric', 'qtype_calculated');
348                     }
349                     if ($data['fraction'][$key] == 1) {
350                         $maxgrade = true;
351                     }
353                     $answercount++;
354                 }
355                 //check grades
356                 $totalfraction = 0 ;
357                 $maxfraction = 0 ;
358                 if ($answer != '') {
359                     if ($data['fraction'][$key] > 0) {
360                         $totalfraction += $data['fraction'][$key];
361                     }
362                     if ($data['fraction'][$key] > $maxfraction) {
363                         $maxfraction = $data['fraction'][$key];
364                     }
365                 }
366             }
367             if ($answercount==0){
368                 $errors['answer[0]'] = get_string('notenoughanswers', 'qtype_multichoice', 2);
369                 $errors['answer[1]'] = get_string('notenoughanswers', 'qtype_multichoice', 2);
370             } elseif ($answercount==1){
371                 $errors['answer[1]'] = get_string('notenoughanswers', 'qtype_multichoice', 2);
373             }
375             /// Perform sanity checks on fractional grades
376             if ($data['single']) {
377                 if ($maxfraction > 0.999 ) {
378                     $maxfraction = $maxfraction * 100;
379                     $errors['fraction[0]'] = get_string('errfractionsnomax', 'qtype_multichoice', $maxfraction);
380                 }
381             } else {
382                 $totalfraction = round($totalfraction,2);
383                 if ($totalfraction != 1) {
384                     $totalfraction = $totalfraction * 100;
385                     $errors['fraction[0]'] = get_string('errfractionsaddwrong', 'qtype_multichoice', $totalfraction);
386                 }
387             }
389             if ($answercount==0){
390                 $errors['answer[0]'] = get_string('atleastoneanswer', 'qtype_calculated');
391             }
392             if ($maxgrade == false) {
393                 $errors['fraction[0]'] = get_string('fractionsnomax', 'question');
394             }
396         }
397         return $errors;
398     }