MDL-26069 detect missing file postprocessing
[moodle.git] / question / type / multianswer / edit_multianswer_form.php
1 <?php
2 /**
3  * Defines the editing form for the multianswer 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  * multianswer editing form definition.
14  */
15 class question_edit_multianswer_form extends question_edit_form {
17     //  $questiondisplay will contain the qtype_multianswer_extract_question from the questiontext
18     public $questiondisplay ;
19     //  $savedquestiondisplay will contain the qtype_multianswer_extract_question from the questiontext in database
20     public $savedquestion ;
21     public $savedquestiondisplay ;
22     public $used_in_quiz = false ;
23     public $qtype_change = false ;
24     public $negative_diff = 0 ;
25     public $nb_of_quiz = 0;
26     public $nb_of_attempts = 0;
27     public $confirm = 0 ;
28     public $reload = false ;
30     function question_edit_multianswer_form(&$submiturl, &$question, &$category, &$contexts, $formeditable = true){
31         global $QTYPES, $SESSION, $CFG, $DB;
32         $this->regenerate = true;
33         if  (  "1" == optional_param('reload','', PARAM_INT )) {
34             $this->reload = true ;
35         }else {
36             $this->reload = false ;
37         }
38         // $this->question = $question;
39         $this->used_in_quiz =false;
40         //  echo "<p> question <pre>";print_r($question);echo "</pre></p>";
41         if(isset($question->id) && $question->id != 0 ){
42             $this->savedquestiondisplay =fullclone($question ) ;
43             if ($list = $DB->get_records('quiz_question_instances', array( 'question'=> $question->id))){
44                 foreach($list as $key => $li){
45                     $this->nb_of_quiz ++;
46                     if($att = $DB->get_records('quiz_attempts',array( 'quiz'=> $li->quiz, 'preview'=> '0'))){
47                         $this->nb_of_attempts+= count($att);
48                         $this->used_in_quiz = true;
49                     }
50                 }
51             }
52         }
54         parent::question_edit_form($submiturl, $question, $category, $contexts, $formeditable);
55     }
57     function definition_inner(&$mform) {
58         $mform->addElement('hidden', 'reload', 1);
59    //     $mform->addElement('hidden', 'generalfeedback','');
60         $mform->setType('reload', PARAM_INT);
61         $question_type_names = question_type_menu();
63         // Remove meaningless defaultgrade field.
64         $mform->removeElement('defaultgrade');
65         $this->confirm = optional_param('confirm','0', PARAM_RAW);
67         // display the questions from questiontext;
68         if  (  "" != optional_param('questiontext','', PARAM_RAW)) {
69                 //   echo "<p> optional_param('questiontext' <pre>";print_r(optional_param('questiontext','', PARAM_RAW));echo "</pre></p>";
71             $this->questiondisplay = fullclone(qtype_multianswer_extract_question(optional_param('questiontext','', PARAM_RAW))) ;
73         }else {
74             if(!$this->reload && !empty($this->savedquestiondisplay->id)){
75                 // use database data as this is first pass
76                 // question->id == 0 so no stored datasets
77                 $this->questiondisplay = fullclone($this->savedquestiondisplay);
78                 foreach($this->questiondisplay->options->questions as $subquestion){
79                     if (!empty($subquestion)){
80                         $subquestion->answer = array('');
81                         foreach($subquestion->options->answers as $ans){
82                             $subquestion->answer[]=$ans->answer ;
83                         }
84                         //  $subquestion->answer = fullclone($subquestion->options->answers);
85                     }
86                 }
87             }else {
88                 $this->questiondisplay = "";
89             }
90         }
92         if ( isset($this->savedquestiondisplay->options->questions) && is_array($this->savedquestiondisplay->options->questions) ) {
93             $countsavedsubquestions =0;
94             foreach($this->savedquestiondisplay->options->questions as $subquestion){
95                 if (!empty($subquestion)){
96                     $countsavedsubquestions++;
97                 }
98             }
99         } else {
100             $countsavedsubquestions =0;
101         }
102         if ($this->reload){
103             if ( isset($this->questiondisplay->options->questions) && is_array($this->questiondisplay->options->questions) ) {
104                 $countsubquestions =0;
105                 foreach($this->questiondisplay->options->questions as $subquestion){
106                     if (!empty($subquestion)){
107                         $countsubquestions++;
108                     }
109                 }
110             } else {
111                 $countsubquestions =0;
112             }
113         }else{
114             $countsubquestions =$countsavedsubquestions ;
115         }
116                  //  echo "<p> count subquestion $countsubquestions <pre>";print_r($this->savedquestiondisplay);echo "</pre></p>";
117         //       //     echo "<p> saved question $countsubquestions <pre>";print_r($this->questiondisplay);echo "</pre></p>";
120         $mform->addElement('submit', 'analyzequestion', get_string('decodeverifyquestiontext','qtype_multianswer'));
121         $mform->registerNoSubmitButton('analyzequestion');
122         echo '<div class="ablock clearfix">';
123         echo '<div class=" clearfix">';
124         if ( $this->reload ){
125             for ($sub =1;$sub <=$countsubquestions ;$sub++) {
127                 $this->editas[$sub] =  'unknown type';
128                 if (isset( $this->questiondisplay->options->questions[$sub]->qtype) ) {
129                     $this->editas[$sub] =  $this->questiondisplay->options->questions[$sub]->qtype ;
130                 } else if (optional_param('sub_'.$sub."_".'qtype', '', PARAM_RAW) != '') {
131                     $this->editas[$sub] = optional_param('sub_'.$sub."_".'qtype', '', PARAM_RAW);
132                 }
133                 $storemess = '';
134                 if(isset($this->savedquestiondisplay->options->questions[$sub]->qtype) &&
135                     $this->savedquestiondisplay->options->questions[$sub]->qtype != $this->questiondisplay->options->questions[$sub]->qtype ){
136                         $this->qtype_change = true ;
137                         $storemess = "<font class=\"error\"> STORED QTYPE ".$question_type_names[$this->savedquestiondisplay->options->questions[$sub]->qtype]."</font >";
138                     }
140                 $mform->addElement('header', 'subhdr'.$sub, get_string('questionno', 'quiz',
141                     '{#'.$sub.'}').'&nbsp;'.$question_type_names[$this->questiondisplay->options->questions[$sub]->qtype].$storemess);
143                 $mform->addElement('static', 'sub_'.$sub."_".'questiontext', get_string('questiondefinition','qtype_multianswer'),array('cols'=>60, 'rows'=>3));
145                 if (isset ( $this->questiondisplay->options->questions[$sub]->questiontext)) {
146                     $mform->setDefault('sub_'.$sub."_".'questiontext', $this->questiondisplay->options->questions[$sub]->questiontext['text']);
147                 }
149                 $mform->addElement('static', 'sub_'.$sub."_".'defaultgrade', get_string('defaultgrade', 'quiz'));
150                 $mform->setDefault('sub_'.$sub."_".'defaultgrade',$this->questiondisplay->options->questions[$sub]->defaultgrade);
152                 if ($this->questiondisplay->options->questions[$sub]->qtype =='shortanswer'   ) {
153                     $mform->addElement('static', 'sub_'.$sub."_".'usecase', get_string('casesensitive', 'quiz'));
154                 }
156                 if ($this->questiondisplay->options->questions[$sub]->qtype =='multichoice'   ) {
157                     $mform->addElement('static', 'sub_'.$sub."_".'layout', get_string('layout', 'qtype_multianswer'),array('cols'=>60, 'rows'=>1)) ;//, $gradeoptions);
158                 }
159                 foreach ($this->questiondisplay->options->questions[$sub]->answer as $key =>$ans) {
161                     $mform->addElement('static', 'sub_'.$sub."_".'answer['.$key.']', get_string('answer', 'quiz'), array('cols'=>60, 'rows'=>1));
163                     if ($this->questiondisplay->options->questions[$sub]->qtype =='numerical' && $key == 0 ) {
164                         $mform->addElement('static', 'sub_'.$sub."_".'tolerance['.$key.']', get_string('acceptederror', 'quiz')) ;//, $gradeoptions);
165                     }
167                     $mform->addElement('static', 'sub_'.$sub."_".'fraction['.$key.']', get_string('grade')) ;//, $gradeoptions);
169                     $mform->addElement('static', 'sub_'.$sub."_".'feedback['.$key.']', get_string('feedback', 'quiz'));
170                 }
172             }
173             echo '</div>';
174             $this->negative_diff =$countsavedsubquestions - $countsubquestions ;
175             if ( ($this->negative_diff > 0 ) ||$this->qtype_change || ($this->used_in_quiz && $this->negative_diff != 0)){
176                 $mform->addElement('header', 'additemhdr', get_string('warningquestionmodified','qtype_multianswer'));
177             }
178             if($this->negative_diff > 0) {
179                 //$this->used_in_quiz
180                 $mform->addElement('static', 'alert1', "<strong>".get_string('questiondeleted','qtype_multianswer')."</strong>",get_string('questionsless','qtype_multianswer',$this->negative_diff));
181             }
182             if($this->qtype_change ) {
183                 $mform->addElement('static', 'alert1', "<strong>".get_string('questiontypechanged','qtype_multianswer')."</strong>",get_string('questiontypechangedcomment','qtype_multianswer'));
184             }
185             echo '</div>';
186         }
187         if( $this->used_in_quiz){
188             if($this->negative_diff < 0) {
189                 $diff = $countsubquestions - $countsavedsubquestions;
190                 $mform->addElement('static', 'alert1', "<strong>".get_string('questionsadded','qtype_multianswer')."</strong>","<strong>".get_string('questionsmore','qtype_multianswer',$diff)."</strong>");
191             }
192             $a = new stdClass ;
193             $a->nb_of_quiz = $this->nb_of_quiz;
194             $a->nb_of_attempts = $this->nb_of_attempts;
195             $mform->addElement('header', 'additemhdr2', get_string('questionusedinquiz','qtype_multianswer',$a));
196             $mform->addElement('static', 'alertas', get_string('youshouldnot','qtype_multianswer'));
197         }
198         if ( ($this->negative_diff > 0 || $this->used_in_quiz && ($this->negative_diff > 0 ||$this->negative_diff < 0 || $this->qtype_change ) ) &&  $this->reload ){
199             $mform->addElement('header', 'additemhdr', get_string('questionsaveasedited', 'qtype_multianswer'));
200             $mform->addElement('checkbox', 'confirm','' ,get_string('confirmquestionsaveasedited', 'qtype_multianswer'));
201             $mform->setDefault('confirm', 0);
202         }else {
203             $mform->addElement('hidden', 'confirm',0);
204         }
206     }
209     function set_data($question) {
210         global $DB;
211         $default_values =array();
212         if (isset($question->id) and $question->id and $question->qtype and $question->questiontext) {
214             foreach ($question->options->questions as $key => $wrapped) {
215                 if(!empty($wrapped)){
216                     // The old way of restoring the definitions is kept to gradually
217                     // update all multianswer questions
218                     if (empty($wrapped->questiontext)) {
219                         $parsableanswerdef = '{' . $wrapped->defaultgrade . ':';
220                         switch ($wrapped->qtype) {
221                         case 'multichoice':
222                             $parsableanswerdef .= 'MULTICHOICE:';
223                             break;
224                         case 'shortanswer':
225                             $parsableanswerdef .= 'SHORTANSWER:';
226                             break;
227                         case 'numerical':
228                             $parsableanswerdef .= 'NUMERICAL:';
229                             break;
230                         default:
231                             print_error('unknownquestiontype', 'question', '', $wrapped->qtype);
232                         }
233                         $separator= '';
234                         foreach ($wrapped->options->answers as $subanswer) {
235                             $parsableanswerdef .= $separator
236                                 . '%' . round(100*$subanswer->fraction) . '%';
237                             $parsableanswerdef .= $subanswer->answer;
238                             if (!empty($wrapped->options->tolerance)) {
239                                 // Special for numerical answers:
240                                 $parsableanswerdef .= ":{$wrapped->options->tolerance}";
241                                 // We only want tolerance for the first alternative, it will
242                                 // be applied to all of the alternatives.
243                                 unset($wrapped->options->tolerance);
244                             }
245                             if ($subanswer->feedback) {
246                                 $parsableanswerdef .= "#$subanswer->feedback";
247                             }
248                             $separator = '~';
249                         }
250                         $parsableanswerdef .= '}';
251                         // Fix the questiontext fields of old questions
252                         $DB->set_field('question', 'questiontext', $parsableanswerdef, array('id' => $wrapped->id));
253                     } else {
254                         $parsableanswerdef = str_replace('&#', '&\#', $wrapped->questiontext);
255                     }
256                     $question->questiontext = str_replace("{#$key}", $parsableanswerdef, $question->questiontext);
257                 }
258             }
259         }
261         // set default to $questiondisplay questions elements
262         if ( $this->reload ){
263             if (isset($this->questiondisplay->options->questions)) {
264                 $subquestions = fullclone($this->questiondisplay->options->questions) ;
265                 if (count($subquestions)) {
266                     $sub =1;
267                     foreach ($subquestions as $subquestion) {
268                         $prefix = 'sub_'.$sub.'_' ;
270                         // validate parameters
271                         $answercount = 0;
272                         $maxgrade = false;
273                         $maxfraction = -1;
274                         if ($subquestion->qtype =='shortanswer'   ) {
275                             switch ($subquestion->usecase) {
276                             case '1':
277                                 $default_values[$prefix.'usecase']= get_string('caseyes', 'quiz');
278                                 break;
279                             case '0':
280                             default :
281                                 $default_values[$prefix.'usecase']= get_string('caseno', 'quiz');
282                             }
283                         }
285                         if ($subquestion->qtype == 'multichoice' ) {
286                             $default_values[$prefix.'layout']  = $subquestion->layout ;
287                             switch ($subquestion->layout) {
288                             case '0':
289                                 $default_values[$prefix.'layout']= get_string('layoutselectinline', 'qtype_multianswer');
290                                 break;
291                             case '1':
292                                 $default_values[$prefix.'layout']= get_string('layoutvertical', 'qtype_multianswer');
293                                 break;
294                             case '2':
295                                 $default_values[$prefix.'layout']= get_string('layouthorizontal', 'qtype_multianswer');
296                                 break;
297                             default:
298                                 $default_values[$prefix.'layout']= get_string('layoutundefined', 'qtype_multianswer');
299                             }
300                         }
301                         foreach ($subquestion->answer as $key=>$answer) {
302                             if ( $subquestion->qtype == 'numerical' && $key == 0 ) {
303                                 $default_values[$prefix.'tolerance['.$key.']']  = $subquestion->tolerance[0] ;
304                             }
305                             $trimmedanswer = trim($answer);
306                             if ($trimmedanswer !== '') {
307                                 $answercount++;
308                                 if ($subquestion->qtype == 'numerical' && !(is_numeric($trimmedanswer) || $trimmedanswer == '*')) {
309                                     $this->_form->setElementError($prefix.'answer['.$key.']' , get_string('answermustbenumberorstar', 'qtype_numerical'));
310                                 }
311                                 if ($subquestion->fraction[$key] == 1) {
312                                     $maxgrade = true;
313                                 }
314                                 if ($subquestion->fraction[$key] > $maxfraction) {
315                                     $maxfraction = $subquestion->fraction[$key] ;
316                                 }
317                             }
319                             $default_values[$prefix.'answer['.$key.']']  = htmlspecialchars ($answer);
320                         }
321                         if ($answercount == 0) {
322                             if ($subquestion->qtype == 'multichoice' ) {
323                                 $this->_form->setElementError($prefix.'answer[0]' ,  get_string('notenoughanswers', 'qtype_multichoice', 2));
324                             } else {
325                                 $this->_form->setElementError($prefix.'answer[0]' , get_string('notenoughanswers', 'quiz', 1));
326                             }
327                         }
328                         if ($maxgrade == false) {
329                             $this->_form->setElementError($prefix.'fraction[0]' ,get_string('fractionsnomax', 'question'));
330                         }
331                         foreach ($subquestion->feedback as $key=>$answer) {
333                             $default_values[$prefix.'feedback['.$key.']']  = htmlspecialchars ($answer['text']);
334                         }
335                         foreach ( $subquestion->fraction as $key=>$answer) {
336                             $default_values[$prefix.'fraction['.$key.']']  = $answer;
337                         }
340                         $sub++;
341                     }
342                 }
343             }
344         }
345         $default_values['alertas']= "<strong>".get_string('questioninquiz','qtype_multianswer')."</strong>";
347         if( $default_values != "")   {
348             $question = (object)((array)$question + $default_values);
349         }
350         parent::set_data($question);
351     }
353     function validation($data, $files) {
354         $errors = parent::validation($data, $files);
356         $questiondisplay = qtype_multianswer_extract_question($data['questiontext']);
357 //                   echo "<p> questiondisplay ".$data['questiontext']['text']." <pre>";print_r($questiondisplay);echo "</pre></p>";
359         if (isset($questiondisplay->options->questions)) {
360             $subquestions = fullclone($questiondisplay->options->questions) ;
361             if (count($subquestions)) {
362                 $sub =1;
363                 foreach ($subquestions as $subquestion) {
364                     $prefix = 'sub_'.$sub.'_' ;
365                     $answercount = 0;
366                     $maxgrade = false;
367                     $maxfraction = -1;
368                     if(isset($this->savedquestiondisplay->options->questions[$sub]->qtype) &&
369                         $this->savedquestiondisplay->options->questions[$sub]->qtype != $questiondisplay->options->questions[$sub]->qtype ){
370                             $storemess = " STORED QTYPE ".$question_type_names[$this->savedquestiondisplay->options->questions[$sub]->qtype];
371                         }
372                     foreach ( $subquestion->answer as $key=>$answer) {
373                         $trimmedanswer = trim($answer);
374                         if ($trimmedanswer !== '') {
375                             $answercount++;
376                             if ($subquestion->qtype =='numerical' && !(is_numeric($trimmedanswer) || $trimmedanswer == '*')) {
377                                 $errors[$prefix.'answer['.$key.']']=  get_string('answermustbenumberorstar', 'qtype_numerical');
378                             }
379                             if ($subquestion->fraction[$key] == 1) {
380                                 $maxgrade = true;
381                             }
382                             if ($subquestion->fraction[$key] > $maxfraction) {
383                                 $maxfraction = $subquestion->fraction[$key] ;
384                             }
385                         }
386                     }
387                     if ($answercount==0) {
388                         if ( $subquestion->qtype =='multichoice' ) {
389                             $errors[$prefix.'answer[0]']= get_string('notenoughanswers', 'qtype_multichoice', 2);
390                         }else {
391                             $errors[$prefix.'answer[0]'] = get_string('notenoughanswers', 'quiz', 1);
392                         }
393                     }
394                     if ($maxgrade == false) {
395                         $errors[$prefix.'fraction[0]']=get_string('fractionsnomax', 'question');
396                     }
397                     $sub++;
398                 }
399             } else {
400                 $errors['questiontext']=get_string('questionsmissing', 'qtype_multianswer');
401             }
402         }
403         // $question = qtype_multianswer_extract_question($data['questiontext']);
404         //  if (isset $question->options->questions
405         if (( $this->negative_diff > 0 || $this->used_in_quiz && ($this->negative_diff > 0 ||$this->negative_diff < 0 || $this->qtype_change ))&& $this->confirm == 0 ){
406             $errors['confirm']=get_string('confirmsave', 'qtype_multianswer',$this->negative_diff);
407         }
410         return $errors;
411     }
413     function qtype() {
414         return 'multianswer';
415     }