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