MDL-55308 mod_feedback: Convert analysis chart to the new library
[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";
34     public function build_editform($item, $feedback, $cm) {
35         global $DB, $CFG;
36         require_once('multichoicerated_form.php');
38         //get the lastposition number of the feedback_items
39         $position = $item->position;
40         $lastposition = $DB->count_records('feedback_item', array('feedback'=>$feedback->id));
41         if ($position == -1) {
42             $i_formselect_last = $lastposition + 1;
43             $i_formselect_value = $lastposition + 1;
44             $item->position = $lastposition + 1;
45         } else {
46             $i_formselect_last = $lastposition;
47             $i_formselect_value = $item->position;
48         }
49         //the elements for position dropdownlist
50         $positionlist = array_slice(range(0, $i_formselect_last), 1, $i_formselect_last, true);
52         $item->presentation = empty($item->presentation) ? '' : $item->presentation;
53         $info = $this->get_info($item);
55         $item->ignoreempty = $this->ignoreempty($item);
56         $item->hidenoselect = $this->hidenoselect($item);
58         //all items for dependitem
59         $feedbackitems = feedback_get_depend_candidates_for_item($feedback, $item);
60         $commonparams = array('cmid'=>$cm->id,
61                              'id'=>isset($item->id) ? $item->id : null,
62                              'typ'=>$item->typ,
63                              'items'=>$feedbackitems,
64                              'feedback'=>$feedback->id);
66         //build the form
67         $customdata = array('item' => $item,
68                             'common' => $commonparams,
69                             'positionlist' => $positionlist,
70                             'position' => $position,
71                             'info' => $info);
73         $this->item_form = new feedback_multichoicerated_form('edit_item.php', $customdata);
74     }
76     public function save_item() {
77         global $DB;
79         if (!$item = $this->item_form->get_data()) {
80             return false;
81         }
83         if (isset($item->clone_item) AND $item->clone_item) {
84             $item->id = ''; //to clone this item
85             $item->position++;
86         }
88         $this->set_ignoreempty($item, $item->ignoreempty);
89         $this->set_hidenoselect($item, $item->hidenoselect);
91         $item->hasvalue = $this->get_hasvalue();
92         if (!$item->id) {
93             $item->id = $DB->insert_record('feedback_item', $item);
94         } else {
95             $DB->update_record('feedback_item', $item);
96         }
98         return $DB->get_record('feedback_item', array('id'=>$item->id));
99     }
102     /**
103      * Helper function for collected data, both for analysis page and export to excel
104      *
105      * @param stdClass $item the db-object from feedback_item
106      * @param int $groupid
107      * @param int $courseid
108      * @return array
109      */
110     protected function get_analysed($item, $groupid = false, $courseid = false) {
111         $analysed_item = array();
112         $analysed_item[] = $item->typ;
113         $analysed_item[] = $item->name;
115         //die moeglichen Antworten extrahieren
116         $info = $this->get_info($item);
117         $lines = null;
118         $lines = explode (FEEDBACK_MULTICHOICERATED_LINE_SEP, $info->presentation);
119         if (!is_array($lines)) {
120             return null;
121         }
123         //die Werte holen
124         $values = feedback_get_group_values($item, $groupid, $courseid, $this->ignoreempty($item));
125         if (!$values) {
126             return null;
127         }
128         //schleife ueber den Werten und ueber die Antwortmoeglichkeiten
130         $analysed_answer = array();
131         $sizeoflines = count($lines);
132         for ($i = 1; $i <= $sizeoflines; $i++) {
133             $item_values = explode(FEEDBACK_MULTICHOICERATED_VALUE_SEP, $lines[$i-1]);
134             $ans = new stdClass();
135             $ans->answertext = $item_values[1];
136             $avg = 0.0;
137             $anscount = 0;
138             foreach ($values as $value) {
139                 //ist die Antwort gleich dem index der Antworten + 1?
140                 if ($value->value == $i) {
141                     $avg += $item_values[0]; //erst alle Werte aufsummieren
142                     $anscount++;
143                 }
144             }
145             $ans->answercount = $anscount;
146             $ans->avg = doubleval($avg) / doubleval(count($values));
147             $ans->value = $item_values[0];
148             $ans->quotient = $ans->answercount / count($values);
149             $analysed_answer[] = $ans;
150         }
151         $analysed_item[] = $analysed_answer;
152         return $analysed_item;
153     }
155     public function get_printval($item, $value) {
156         $printval = '';
158         if (!isset($value->value)) {
159             return $printval;
160         }
162         $info = $this->get_info($item);
164         $presentation = explode (FEEDBACK_MULTICHOICERATED_LINE_SEP, $info->presentation);
165         $index = 1;
166         foreach ($presentation as $pres) {
167             if ($value->value == $index) {
168                 $item_label = explode(FEEDBACK_MULTICHOICERATED_VALUE_SEP, $pres);
169                 $printval = $item_label[1];
170                 break;
171             }
172             $index++;
173         }
174         return $printval;
175     }
177     public function print_analysed($item, $itemnr = '', $groupid = false, $courseid = false) {
178         global $OUTPUT;
179         $analysed_item = $this->get_analysed($item, $groupid, $courseid);
180         if ($analysed_item) {
181             echo "<table class=\"analysis itemtype_{$item->typ}\">";
182             echo '<tr><th colspan="2" align="left">';
183             echo $itemnr . ' ';
184             if (strval($item->label) !== '') {
185                 echo '('. format_string($item->label).') ';
186             }
187             echo $analysed_item[1];
188             echo '</th></tr>';
189             echo '</table>';
190             $analysed_vals = $analysed_item[2];
191             $avg = 0.0;
192             $count = 0;
193             $data = [];
194             foreach ($analysed_vals as $val) {
195                 $avg += $val->avg;
196                 $quotient = format_float($val->quotient * 100, 2);
197                 $answertext = format_text(trim($val->answertext), FORMAT_HTML,
198                         array('noclean' => true, 'para' => false));
199                 if ($val->quotient > 0) {
200                     $str_quotient = ' ('.$quotient.' %)';
201                 } else {
202                     $str_quotient = '';
203                 }
205                 $data['labels'][$count] = $answertext;
206                 $data['series'][$count] = $val->answercount;
207                 $data['series_labels'][$count] = $str_quotient;
208                 $count++;
209             }
210             $chart = new \core\chart_bar();
211             $chart->set_horizontal(true);
212             $series = new \core\chart_series(format_string(get_string("responses", "feedback")), $data['series']);
213             $series->set_labels($data['series_labels']);
214             $chart->add_series($series);
215             $chart->set_labels($data['labels']);
216             echo $OUTPUT->render($chart);
218             $avg = format_float($avg, 2);
219             echo '<tr><td align="left" colspan="2"><b>';
220             echo get_string('average', 'feedback').': '.$avg.'</b>';
221             echo '</td></tr>';
222         }
223     }
225     public function excelprint_item(&$worksheet, $row_offset,
226                              $xls_formats, $item,
227                              $groupid, $courseid = false) {
229         $analysed_item = $this->get_analysed($item, $groupid, $courseid);
231         $data = $analysed_item[2];
233         //write the item
234         $worksheet->write_string($row_offset, 0, $item->label, $xls_formats->head2);
235         $worksheet->write_string($row_offset, 1, $analysed_item[1], $xls_formats->head2);
236         if (is_array($data)) {
237             $avg = 0.0;
238             $sizeofdata = count($data);
239             for ($i = 0; $i < $sizeofdata; $i++) {
240                 $analysed_data = $data[$i];
242                 $worksheet->write_string($row_offset,
243                                 $i + 2,
244                                 trim($analysed_data->answertext).' ('.$analysed_data->value.')',
245                                 $xls_formats->value_bold);
247                 $worksheet->write_number($row_offset + 1,
248                                 $i + 2,
249                                 $analysed_data->answercount,
250                                 $xls_formats->default);
252                 $avg += $analysed_data->avg;
253             }
254             //mittelwert anzeigen
255             $worksheet->write_string($row_offset,
256                                 count($data) + 2,
257                                 get_string('average', 'feedback'),
258                                 $xls_formats->value_bold);
260             $worksheet->write_number($row_offset + 1,
261                                 count($data) + 2,
262                                 $avg,
263                                 $xls_formats->value_bold);
264         }
265         $row_offset +=2;
266         return $row_offset;
267     }
269     /**
270      * Options for the multichoice element
271      * @param stdClass $item
272      * @return array
273      */
274     protected function get_options($item) {
275         $info = $this->get_info($item);
276         $lines = explode(FEEDBACK_MULTICHOICERATED_LINE_SEP, $info->presentation);
277         $options = array();
278         foreach ($lines as $idx => $line) {
279             list($weight, $optiontext) = explode(FEEDBACK_MULTICHOICERATED_VALUE_SEP, $line);
280             $options[$idx + 1] = format_text("<span class=\"weight\">($weight) </span>".$optiontext,
281                     FORMAT_HTML, array('noclean' => true, 'para' => false));
282         }
283         if ($info->subtype === 'r' && !$this->hidenoselect($item)) {
284             $options = array(0 => get_string('not_selected', 'feedback')) + $options;
285         }
287         return $options;
288     }
290     /**
291      * Adds an input element to the complete form
292      *
293      * @param stdClass $item
294      * @param mod_feedback_complete_form $form
295      */
296     public function complete_form_element($item, $form) {
297         $info = $this->get_info($item);
298         $name = $this->get_display_name($item);
299         $class = 'multichoicerated-' . $info->subtype;
300         $inputname = $item->typ . '_' . $item->id;
301         $options = $this->get_options($item);
302         if ($info->subtype === 'd' || $form->is_frozen()) {
303             $el = $form->add_form_element($item,
304                     ['select', $inputname, $name, array('' => '') + $options, array('class' => $class)]);
305         } else {
306             $objs = array();
307             foreach ($options as $idx => $label) {
308                 $objs[] = ['radio', $inputname, '', $label, $idx];
309             }
310             $separator = $info->horizontal ? ' ' : '<br>';
311             $class .= ' multichoicerated-' . ($info->horizontal ? 'horizontal' : 'vertical');
312             $el = $form->add_form_group_element($item, 'group_'.$inputname, $name, $objs, $separator, $class);
314             // Set previously input values.
315             $form->set_element_default($inputname, $form->get_item_value($item));
317             // Process "required" rule.
318             if ($item->required) {
319                 $form->add_validation_rule(function($values, $files) use ($item) {
320                     $inputname = $item->typ . '_' . $item->id;
321                     return empty($values[$inputname]) ? array('group_' . $inputname => get_string('required')) : true;
322                 });
323             }
324         }
325     }
327     /**
328      * Compares the dbvalue with the dependvalue
329      *
330      * @param stdClass $item
331      * @param string $dbvalue is the value input by user in the format as it is stored in the db
332      * @param string $dependvalue is the value that it needs to be compared against
333      */
334     public function compare_value($item, $dbvalue, $dependvalue) {
336         if (is_array($dbvalue)) {
337             $dbvalues = $dbvalue;
338         } else {
339             $dbvalues = explode(FEEDBACK_MULTICHOICERATED_LINE_SEP, $dbvalue);
340         }
342         $info = $this->get_info($item);
343         $presentation = explode (FEEDBACK_MULTICHOICERATED_LINE_SEP, $info->presentation);
344         $index = 1;
345         foreach ($presentation as $pres) {
346             $presvalues = explode(FEEDBACK_MULTICHOICERATED_VALUE_SEP, $pres);
348             foreach ($dbvalues as $dbval) {
349                 if ($dbval == $index AND trim($presvalues[1]) == $dependvalue) {
350                     return true;
351                 }
352             }
353             $index++;
354         }
355         return false;
356     }
358     public function get_info($item) {
359         $presentation = empty($item->presentation) ? '' : $item->presentation;
361         $info = new stdClass();
362         //check the subtype of the multichoice
363         //it can be check(c), radio(r) or dropdown(d)
364         $info->subtype = '';
365         $info->presentation = '';
366         $info->horizontal = false;
368         $parts = explode(FEEDBACK_MULTICHOICERATED_TYPE_SEP, $item->presentation);
369         @list($info->subtype, $info->presentation) = $parts;
371         if (!isset($info->subtype)) {
372             $info->subtype = 'r';
373         }
375         if ($info->subtype != 'd') {
376             $parts = explode(FEEDBACK_MULTICHOICERATED_ADJUST_SEP, $info->presentation);
377             @list($info->presentation, $info->horizontal) = $parts;
379             if (isset($info->horizontal) AND $info->horizontal == 1) {
380                 $info->horizontal = true;
381             } else {
382                 $info->horizontal = false;
383             }
384         }
386         $info->values = $this->prepare_presentation_values_print($info->presentation,
387                                                     FEEDBACK_MULTICHOICERATED_VALUE_SEP,
388                                                     FEEDBACK_MULTICHOICERATED_VALUE_SEP2);
389         return $info;
390     }
392     public function prepare_presentation_values($linesep1,
393                                          $linesep2,
394                                          $valuestring,
395                                          $valuesep1,
396                                          $valuesep2) {
398         $lines = explode($linesep1, $valuestring);
399         $newlines = array();
400         foreach ($lines as $line) {
401             $value = '';
402             $text = '';
403             if (strpos($line, $valuesep1) === false) {
404                 $value = 0;
405                 $text = $line;
406             } else {
407                 @list($value, $text) = explode($valuesep1, $line, 2);
408             }
410             $value = intval($value);
411             $newlines[] = $value.$valuesep2.$text;
412         }
413         $newlines = implode($linesep2, $newlines);
414         return $newlines;
415     }
417     public function prepare_presentation_values_print($valuestring, $valuesep1, $valuesep2) {
418         $valuestring = str_replace(array("\n","\r"), "", $valuestring);
419         return $this->prepare_presentation_values(FEEDBACK_MULTICHOICERATED_LINE_SEP,
420                                                   "\n",
421                                                   $valuestring,
422                                                   $valuesep1,
423                                                   $valuesep2);
424     }
426     public function prepare_presentation_values_save($valuestring, $valuesep1, $valuesep2) {
427         $valuestring = str_replace("\r", "\n", $valuestring);
428         $valuestring = str_replace("\n\n", "\n", $valuestring);
429         return $this->prepare_presentation_values("\n",
430                         FEEDBACK_MULTICHOICERATED_LINE_SEP,
431                         $valuestring,
432                         $valuesep1,
433                         $valuesep2);
434     }
436     public function set_ignoreempty($item, $ignoreempty=true) {
437         $item->options = str_replace(FEEDBACK_MULTICHOICERATED_IGNOREEMPTY, '', $item->options);
438         if ($ignoreempty) {
439             $item->options .= FEEDBACK_MULTICHOICERATED_IGNOREEMPTY;
440         }
441     }
443     public function ignoreempty($item) {
444         if (strstr($item->options, FEEDBACK_MULTICHOICERATED_IGNOREEMPTY)) {
445             return true;
446         }
447         return false;
448     }
450     public function set_hidenoselect($item, $hidenoselect=true) {
451         $item->options = str_replace(FEEDBACK_MULTICHOICERATED_HIDENOSELECT, '', $item->options);
452         if ($hidenoselect) {
453             $item->options .= FEEDBACK_MULTICHOICERATED_HIDENOSELECT;
454         }
455     }
457     public function hidenoselect($item) {
458         if (strstr($item->options, FEEDBACK_MULTICHOICERATED_HIDENOSELECT)) {
459             return true;
460         }
461         return false;
462     }