MDL-27675 - Feedback module abuses data_submitted
[moodle.git] / mod / feedback / item / multichoicerated / lib.php
1 <?php
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/>.
17 defined('MOODLE_INTERNAL') OR die('not allowed');
18 require_once($CFG->dirroot.'/mod/feedback/item/feedback_item_class.php');
20 define('FEEDBACK_RADIORATED_ADJUST_SEP', '<<<<<');
22 define('FEEDBACK_MULTICHOICERATED_MAXCOUNT', 10); //count of possible items
23 define('FEEDBACK_MULTICHOICERATED_VALUE_SEP', '####');
24 define('FEEDBACK_MULTICHOICERATED_VALUE_SEP2', '/');
25 define('FEEDBACK_MULTICHOICERATED_TYPE_SEP', '>>>>>');
26 define('FEEDBACK_MULTICHOICERATED_LINE_SEP', '|');
27 define('FEEDBACK_MULTICHOICERATED_ADJUST_SEP', '<<<<<');
28 define('FEEDBACK_MULTICHOICERATED_IGNOREEMPTY', 'i');
29 define('FEEDBACK_MULTICHOICERATED_HIDENOSELECT', 'h');
31 class feedback_item_multichoicerated extends feedback_item_base {
32     protected $type = "multichoicerated";
33     private $commonparams;
34     private $item_form;
35     private $item;
37     public function init() {
39     }
41     public function build_editform($item, $feedback, $cm) {
42         global $DB, $CFG;
43         require_once('multichoicerated_form.php');
45         //get the lastposition number of the feedback_items
46         $position = $item->position;
47         $lastposition = $DB->count_records('feedback_item', array('feedback'=>$feedback->id));
48         if ($position == -1) {
49             $i_formselect_last = $lastposition + 1;
50             $i_formselect_value = $lastposition + 1;
51             $item->position = $lastposition + 1;
52         } else {
53             $i_formselect_last = $lastposition;
54             $i_formselect_value = $item->position;
55         }
56         //the elements for position dropdownlist
57         $positionlist = array_slice(range(0, $i_formselect_last), 1, $i_formselect_last, true);
59         $item->presentation = empty($item->presentation) ? '' : $item->presentation;
60         $info = $this->get_info($item);
62         $item->ignoreempty = $this->ignoreempty($item);
63         $item->hidenoselect = $this->hidenoselect($item);
65         //all items for dependitem
66         $feedbackitems = feedback_get_depend_candidates_for_item($feedback, $item);
67         $commonparams = array('cmid'=>$cm->id,
68                              'id'=>isset($item->id) ? $item->id : null,
69                              'typ'=>$item->typ,
70                              'items'=>$feedbackitems,
71                              'feedback'=>$feedback->id);
73         //build the form
74         $customdata = array('item' => $item,
75                             'common' => $commonparams,
76                             'positionlist' => $positionlist,
77                             'position' => $position,
78                             'info' => $info);
80         $this->item_form = new feedback_multichoicerated_form('edit_item.php', $customdata);
81     }
83     //this function only can used after the call of build_editform()
84     public function show_editform() {
85         $this->item_form->display();
86     }
88     public function is_cancelled() {
89         return $this->item_form->is_cancelled();
90     }
92     public function get_data() {
93         if ($this->item = $this->item_form->get_data()) {
94             return true;
95         }
96         return false;
97     }
99     public function save_item() {
100         global $DB;
102         if (!$item = $this->item_form->get_data()) {
103             return false;
104         }
106         if (isset($item->clone_item) AND $item->clone_item) {
107             $item->id = ''; //to clone this item
108             $item->position++;
109         }
111         $this->set_ignoreempty($item, $item->ignoreempty);
112         $this->set_hidenoselect($item, $item->hidenoselect);
114         $item->hasvalue = $this->get_hasvalue();
115         if (!$item->id) {
116             $item->id = $DB->insert_record('feedback_item', $item);
117         } else {
118             $DB->update_record('feedback_item', $item);
119         }
121         return $DB->get_record('feedback_item', array('id'=>$item->id));
122     }
125     //gets an array with three values(typ, name, XXX)
126     //XXX is an object with answertext, answercount and quotient
127     public function get_analysed($item, $groupid = false, $courseid = false) {
128         $analysed_item = array();
129         $analysed_item[] = $item->typ;
130         $analysed_item[] = $item->name;
132         //die moeglichen Antworten extrahieren
133         $info = $this->get_info($item);
134         $lines = null;
135         $lines = explode (FEEDBACK_MULTICHOICERATED_LINE_SEP, $info->presentation);
136         if (!is_array($lines)) {
137             return null;
138         }
140         //die Werte holen
141         $values = feedback_get_group_values($item, $groupid, $courseid, $this->ignoreempty($item));
142         if (!$values) {
143             return null;
144         }
145         //schleife ueber den Werten und ueber die Antwortmoeglichkeiten
147         $analysed_answer = array();
148         $sizeoflines = count($lines);
149         for ($i = 1; $i <= $sizeoflines; $i++) {
150             $item_values = explode(FEEDBACK_MULTICHOICERATED_VALUE_SEP, $lines[$i-1]);
151             $ans = new stdClass();
152             $ans->answertext = $item_values[1];
153             $avg = 0.0;
154             $anscount = 0;
155             foreach ($values as $value) {
156                 //ist die Antwort gleich dem index der Antworten + 1?
157                 if ($value->value == $i) {
158                     $avg += $item_values[0]; //erst alle Werte aufsummieren
159                     $anscount++;
160                 }
161             }
162             $ans->answercount = $anscount;
163             $ans->avg = doubleval($avg) / doubleval(count($values));
164             $ans->value = $item_values[0];
165             $ans->quotient = $ans->answercount / count($values);
166             $analysed_answer[] = $ans;
167         }
168         $analysed_item[] = $analysed_answer;
169         return $analysed_item;
170     }
172     public function get_printval($item, $value) {
173         $printval = '';
175         if (!isset($value->value)) {
176             return $printval;
177         }
179         $info = $this->get_info($item);
181         $presentation = explode (FEEDBACK_MULTICHOICERATED_LINE_SEP, $info->presentation);
182         $index = 1;
183         foreach ($presentation as $pres) {
184             if ($value->value == $index) {
185                 $item_label = explode(FEEDBACK_MULTICHOICERATED_VALUE_SEP, $pres);
186                 $printval = $item_label[1];
187                 break;
188             }
189             $index++;
190         }
191         return $printval;
192     }
194     public function print_analysed($item, $itemnr = '', $groupid = false, $courseid = false) {
195         global $OUTPUT;
196         $sep_dec = get_string('separator_decimal', 'feedback');
197         if (substr($sep_dec, 0, 2) == '[[') {
198             $sep_dec = FEEDBACK_DECIMAL;
199         }
201         $sep_thous = get_string('separator_thousand', 'feedback');
202         if (substr($sep_thous, 0, 2) == '[[') {
203             $sep_thous = FEEDBACK_THOUSAND;
204         }
206         $analysed_item = $this->get_analysed($item, $groupid, $courseid);
207         if ($analysed_item) {
208             echo '<tr><th colspan="2" align="left">';
209             echo $itemnr.'&nbsp;('.$item->label.') '.$analysed_item[1];
210             echo '</th></tr>';
211             $analysed_vals = $analysed_item[2];
212             $pixnr = 0;
213             $avg = 0.0;
214             foreach ($analysed_vals as $val) {
215                 $intvalue = $pixnr % 10;
216                 $pix = $OUTPUT->pix_url('multichoice/' . $intvalue, 'feedback');
217                 $pixnr++;
218                 $pixwidth = intval($val->quotient * FEEDBACK_MAX_PIX_LENGTH);
220                 $avg += $val->avg;
221                 $quotient = number_format(($val->quotient * 100), 2, $sep_dec, $sep_thous);
222                 echo '<tr>';
223                 echo '<td align="left" valign="top">';
224                 echo '-&nbsp;&nbsp;'.trim($val->answertext).' ('.$val->value.'):</td>';
225                 echo '<td align="left" style="width: '.FEEDBACK_MAX_PIX_LENGTH.'">';
226                 echo '<img alt="'.$intvalue.'" src="'.$pix.'" height="5" width="'.$pixwidth.'" />';
227                 echo $val->answercount;
228                 if ($val->quotient > 0) {
229                     echo '&nbsp;('.$quotient.'&nbsp;%)';
230                 } else {
231                     echo '';
232                 }
233                 echo '</td></tr>';
234             }
235             $avg = number_format(($avg), 2, $sep_dec, $sep_thous);
236             echo '<tr><td align="left" colspan="2"><b>';
237             echo get_string('average', 'feedback').': '.$avg.'</b>';
238             echo '</td></tr>';
239         }
240     }
242     public function excelprint_item(&$worksheet, $row_offset,
243                              $xls_formats, $item,
244                              $groupid, $courseid = false) {
246         $analysed_item = $this->get_analysed($item, $groupid, $courseid);
248         $data = $analysed_item[2];
250         //write the item
251         $worksheet->write_string($row_offset, 0, $item->label, $xls_formats->head2);
252         $worksheet->write_string($row_offset, 1, $analysed_item[1], $xls_formats->head2);
253         if (is_array($data)) {
254             $avg = 0.0;
255             $sizeofdata = count($data);
256             for ($i = 0; $i < $sizeofdata; $i++) {
257                 $analysed_data = $data[$i];
259                 $worksheet->write_string($row_offset,
260                                 $i + 2,
261                                 trim($analysed_data->answertext).' ('.$analysed_data->value.')',
262                                 $xls_formats->value_bold);
264                 $worksheet->write_number($row_offset + 1,
265                                 $i + 2,
266                                 $analysed_data->answercount,
267                                 $xls_formats->default);
269                 $avg += $analysed_data->avg;
270             }
271             //mittelwert anzeigen
272             $worksheet->write_string($row_offset,
273                                 count($data) + 2,
274                                 get_string('average', 'feedback'),
275                                 $xls_formats->value_bold);
277             $worksheet->write_number($row_offset + 1,
278                                 count($data) + 2,
279                                 $avg,
280                                 $xls_formats->value_bold);
281         }
282         $row_offset +=2;
283         return $row_offset;
284     }
286     /**
287      * print the item at the edit-page of feedback
288      *
289      * @global object
290      * @param object $item
291      * @return void
292      */
293     public function print_item_preview($item) {
294         global $OUTPUT, $DB;
296         $align = right_to_left() ? 'right' : 'left';
297         $info = $this->get_info($item);
298         $str_required_mark = '<span class="feedback_required_mark">*</span>';
300         $lines = explode (FEEDBACK_MULTICHOICERATED_LINE_SEP, $info->presentation);
301         $requiredmark =  ($item->required == 1) ? $str_required_mark : '';
302         //print the question and label
303         echo '<div class="feedback_item_label_'.$align.'">';
304         echo '('.$item->label.') ';
305         echo format_text($item->name.$requiredmark, true, false, false);
306         if ($item->dependitem) {
307             if ($dependitem = $DB->get_record('feedback_item', array('id'=>$item->dependitem))) {
308                 echo ' <span class="feedback_depend">';
309                 echo '('.$dependitem->label.'-&gt;'.$item->dependvalue.')';
310                 echo '</span>';
311             }
312         }
313         echo '</div>';
315         //print the presentation
316         echo '<div class="feedback_item_presentation_'.$align.'">';
317         switch($info->subtype) {
318             case 'r':
319                 $this->print_item_radio($item, false, $info, $align, true, $lines);
320                 break;
321             case 'd':
322                 $this->print_item_dropdown($item, false, $info, $align, true, $lines);
323                 break;
324         }
325         echo '</div>';
326     }
328     /**
329      * print the item at the complete-page of feedback
330      *
331      * @global object
332      * @param object $item
333      * @param string $value
334      * @param bool $highlightrequire
335      * @return void
336      */
337     public function print_item_complete($item, $value = '', $highlightrequire = false) {
338         global $OUTPUT;
339         $align = right_to_left() ? 'right' : 'left';
340         $info = $this->get_info($item);
341         $str_required_mark = '<span class="feedback_required_mark">*</span>';
343         $lines = explode (FEEDBACK_MULTICHOICERATED_LINE_SEP, $info->presentation);
344         $requiredmark =  ($item->required == 1) ? $str_required_mark : '';
345         if ($highlightrequire AND $item->required AND intval($value) <= 0) {
346             $highlight = ' missingrequire';
347         } else {
348             $highlight = '';
349         }
351         //print the question and label
352         echo '<div class="feedback_item_label_'.$align.$highlight.'">';
353             echo format_text($item->name.$requiredmark, true, false, false);
354         echo '</div>';
356         //print the presentation
357         echo '<div class="feedback_item_presentation_'.$align.$highlight.'">';
358         switch($info->subtype) {
359             case 'r':
360                 $this->print_item_radio($item, $value, $info, $align, false, $lines);
361                 break;
362             case 'd':
363                 $this->print_item_dropdown($item, $value, $info, $align, false, $lines);
364                 break;
365         }
366         echo '</div>';
367     }
369     /**
370      * print the item at the complete-page of feedback
371      *
372      * @global object
373      * @param object $item
374      * @param string $value
375      * @return void
376      */
377     public function print_item_show_value($item, $value = '') {
378         global $OUTPUT;
379         $align = right_to_left() ? 'right' : 'left';
380         $info = $this->get_info($item);
382         $lines = explode (FEEDBACK_MULTICHOICERATED_LINE_SEP, $info->presentation);
383         $requiredmark = ($item->required == 1)?'<span class="feedback_required_mark">*</span>':'';
385         //print the question and label
386         echo '<div class="feedback_item_label_'.$align.'">';
387             echo '('.$item->label.') ';
388             echo format_text($item->name . $requiredmark, true, false, false);
389         echo '</div>';
391         //print the presentation
392         echo '<div class="feedback_item_presentation_'.$align.'">';
393         $index = 1;
394         foreach ($lines as $line) {
395             if ($value == $index) {
396                 $item_value = explode(FEEDBACK_MULTICHOICERATED_VALUE_SEP, $line);
397                 echo $OUTPUT->box_start('generalbox boxalign'.$align);
398                 echo text_to_html($item_value[1], true, false, false);
399                 echo $OUTPUT->box_end();
400                 break;
401             }
402             $index++;
403         }
404         echo '</div>';
405     }
407     public function check_value($value, $item) {
408         if ((!isset($value) OR $value == '' OR $value == 0) AND $item->required != 1) {
409             return true;
410         }
411         if (intval($value) > 0) {
412             return true;
413         }
414         return false;
415     }
417     public function create_value($data) {
418         $data = trim($data);
419         return $data;
420     }
422     //compares the dbvalue with the dependvalue
423     //dbvalue is the number of one selection
424     //dependvalue is the presentation of one selection
425     public function compare_value($item, $dbvalue, $dependvalue) {
427         if (is_array($dbvalue)) {
428             $dbvalues = $dbvalue;
429         } else {
430             $dbvalues = explode(FEEDBACK_MULTICHOICERATED_LINE_SEP, $dbvalue);
431         }
433         $info = $this->get_info($item);
434         $presentation = explode (FEEDBACK_MULTICHOICERATED_LINE_SEP, $info->presentation);
435         $index = 1;
436         foreach ($presentation as $pres) {
437             $presvalues = explode(FEEDBACK_MULTICHOICERATED_VALUE_SEP, $pres);
439             foreach ($dbvalues as $dbval) {
440                 if ($dbval == $index AND trim($presvalues[1]) == $dependvalue) {
441                     return true;
442                 }
443             }
444             $index++;
445         }
446         return false;
447     }
449     public function get_presentation($data) {
450         $present = $this->prepare_presentation_values_save(trim($data->itemvalues),
451                                             FEEDBACK_MULTICHOICERATED_VALUE_SEP2,
452                                             FEEDBACK_MULTICHOICERATED_VALUE_SEP);
453         if (!isset($data->subtype)) {
454             $subtype = 'r';
455         } else {
456             $subtype = substr($data->subtype, 0, 1);
457         }
458         if (isset($data->horizontal) AND $data->horizontal == 1 AND $subtype != 'd') {
459             $present .= FEEDBACK_MULTICHOICERATED_ADJUST_SEP.'1';
460         }
461         return $subtype.FEEDBACK_MULTICHOICERATED_TYPE_SEP.$present;
462     }
464     public function get_hasvalue() {
465         return 1;
466     }
468     private function get_info($item) {
469         $presentation = empty($item->presentation) ? '' : $item->presentation;
471         $info = new stdClass();
472         //check the subtype of the multichoice
473         //it can be check(c), radio(r) or dropdown(d)
474         $info->subtype = '';
475         $info->presentation = '';
476         $info->horizontal = false;
478         $parts = explode(FEEDBACK_MULTICHOICERATED_TYPE_SEP, $item->presentation);
479         @list($info->subtype, $info->presentation) = $parts;
481         if (!isset($info->subtype)) {
482             $info->subtype = 'r';
483         }
485         if ($info->subtype != 'd') {
486             $parts = explode(FEEDBACK_MULTICHOICERATED_ADJUST_SEP, $info->presentation);
487             @list($info->presentation, $info->horizontal) = $parts;
489             if (isset($info->horizontal) AND $info->horizontal == 1) {
490                 $info->horizontal = true;
491             } else {
492                 $info->horizontal = false;
493             }
494         }
496         $info->values = $this->prepare_presentation_values_print($info->presentation,
497                                                     FEEDBACK_MULTICHOICERATED_VALUE_SEP,
498                                                     FEEDBACK_MULTICHOICERATED_VALUE_SEP2);
499         return $info;
500     }
502     private function print_item_radio($item, $value, $info, $align, $showrating, $lines) {
503         $index = 1;
504         $checked = '';
506         if ($info->horizontal) {
507             $hv = 'h';
508         } else {
509             $hv = 'v';
510         }
511         echo '<ul>';
512         if (!$this->hidenoselect($item)) {
513             ?>
514                 <li class="feedback_item_radio_<?php echo $hv.'_'.$align;?>">
515                     <span class="feedback_item_radio_<?php echo $hv.'_'.$align;?>">
516                         <?php
517                         echo '<input type="radio" '.
518                                     'name="'.$item->typ.'_'.$item->id.'" '.
519                                     'id="'.$item->typ.'_'.$item->id.'_xxx" '.
520                                     'value="" checked="checked" />';
521                         ?>
522                     </span>
523                     <span class="feedback_item_radiolabel_<?php echo $hv.'_'.$align;?>">
524                         <label for="<?php echo $item->typ . '_' . $item->id.'_xxx';?>">
525                         <?php print_string('not_selected', 'feedback');?>&nbsp;
526                         </label>
527                     </span>
528                 </li>
529             <?php
530         }
531         foreach ($lines as $line) {
532             if ($value == $index) {
533                 $checked = 'checked="checked"';
534             } else {
535                 $checked = '';
536             }
537             $radio_value = explode(FEEDBACK_MULTICHOICERATED_VALUE_SEP, $line);
538             $inputname = $item->typ . '_' . $item->id;
539             $inputid = $inputname.'_'.$index;
540         ?>
541             <li class="feedback_item_radio_<?php echo $hv.'_'.$align;?>">
542                 <span class="feedback_item_radio_<?php echo $hv.'_'.$align;?>">
543                 <?php
544                 echo '<input type="radio" '.
545                             'name="'.$inputname.'" '.
546                             'id="'.$inputid.'" '.
547                             'value="'.$index.'" '.$checked.' />';
548                 ?>
549                 </span>
550                 <span class="feedback_item_radiolabel_<?php echo $hv.'_'.$align;?>">
551                     <label for="<?php echo $inputid;?>">
552                         <?php
553                             if ($showrating) {
554                                 $str_rating_value = '('.$radio_value[0].') '.$radio_value[1];
555                                 echo text_to_html($str_rating_value, true, false, false);
556                             } else {
557                                 echo text_to_html($radio_value[1], true, false, false);
558                             }
559                         ?>
560                     </label>
561                 </span>
562             </li>
563         <?php
564             $index++;
565         }
566         echo '</ul>';
567     }
569     private function print_item_dropdown($item, $value, $info, $align, $showrating, $lines) {
570         if ($info->horizontal) {
571             $hv = 'h';
572         } else {
573             $hv = 'v';
574         }
575         echo '<ul>';
576         ?>
577         <li class="feedback_item_select_<?php echo $hv.'_'.$align;?>">
578             <select name="<?php echo $item->typ.'_'.$item->id;?>">
579                 <option value="0">&nbsp;</option>
580                 <?php
581                 $index = 1;
582                 $checked = '';
583                 foreach ($lines as $line) {
584                     if ($value == $index) {
585                         $selected = 'selected="selected"';
586                     } else {
587                         $selected = '';
588                     }
589                     $dropdown_value = explode(FEEDBACK_MULTICHOICERATED_VALUE_SEP, $line);
590                     if ($showrating) {
591                         echo '<option value="'.$index.'" '.$selected.'>';
592                         echo clean_text('('.$dropdown_value[0].') '.$dropdown_value[1]);
593                         echo '</option>';
594                     } else {
595                         echo '<option value="'.$index.'" '.$selected.'>';
596                         echo clean_text($dropdown_value[1]);
597                         echo '</option>';
598                     }
599                     $index++;
600                 }
601                 ?>
602             </select>
603         </li>
604         <?php
605         echo '</ul>';
606     }
608     public function prepare_presentation_values($linesep1,
609                                          $linesep2,
610                                          $valuestring,
611                                          $valuesep1,
612                                          $valuesep2) {
614         $lines = explode($linesep1, $valuestring);
615         $newlines = array();
616         foreach ($lines as $line) {
617             $value = '';
618             $text = '';
619             if (strpos($line, $valuesep1) === false) {
620                 $value = 0;
621                 $text = $line;
622             } else {
623                 @list($value, $text) = explode($valuesep1, $line, 2);
624             }
626             $value = intval($value);
627             $newlines[] = $value.$valuesep2.$text;
628         }
629         $newlines = implode($linesep2, $newlines);
630         return $newlines;
631     }
633     public function prepare_presentation_values_print($valuestring, $valuesep1, $valuesep2) {
634         return $this->prepare_presentation_values(FEEDBACK_MULTICHOICERATED_LINE_SEP,
635                                                   "\n",
636                                                   $valuestring,
637                                                   $valuesep1,
638                                                   $valuesep2);
639     }
641     public function prepare_presentation_values_save($valuestring, $valuesep1, $valuesep2) {
642         return $this->prepare_presentation_values("\n",
643                         FEEDBACK_MULTICHOICERATED_LINE_SEP,
644                         $valuestring,
645                         $valuesep1,
646                         $valuesep2);
647     }
649     public function set_ignoreempty($item, $ignoreempty=true) {
650         $item->options = str_replace(FEEDBACK_MULTICHOICERATED_IGNOREEMPTY, '', $item->options);
651         if ($ignoreempty) {
652             $item->options .= FEEDBACK_MULTICHOICERATED_IGNOREEMPTY;
653         }
654     }
656     public function ignoreempty($item) {
657         if (strstr($item->options, FEEDBACK_MULTICHOICERATED_IGNOREEMPTY)) {
658             return true;
659         }
660         return false;
661     }
663     public function set_hidenoselect($item, $hidenoselect=true) {
664         $item->options = str_replace(FEEDBACK_MULTICHOICERATED_HIDENOSELECT, '', $item->options);
665         if ($hidenoselect) {
666             $item->options .= FEEDBACK_MULTICHOICERATED_HIDENOSELECT;
667         }
668     }
670     public function hidenoselect($item) {
671         if (strstr($item->options, FEEDBACK_MULTICHOICERATED_HIDENOSELECT)) {
672             return true;
673         }
674         return false;
675     }
677     public function can_switch_require() {
678         return true;
679     }
681     public function value_type() {
682         return PARAM_INT;
683     }
685     function clean_input_value($value) {
686         return clean_param($value, $this->value_type());
687     }