6e59f1ef75d751a1dea7615ffa8135e8c07f50d4
[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 '<tr><th colspan="2" align="left">';
182             echo $itemnr . ' ';
183             if (strval($item->label) !== '') {
184                 echo '('. format_string($item->label).') ';
185             }
186             echo $analysed_item[1];
187             echo '</th></tr>';
188             $analysed_vals = $analysed_item[2];
189             $pixnr = 0;
190             $avg = 0.0;
191             foreach ($analysed_vals as $val) {
192                 $intvalue = $pixnr % 10;
193                 $pix = $OUTPUT->pix_url('multichoice/' . $intvalue, 'feedback');
194                 $pixspacer = $OUTPUT->pix_url('spacer');
195                 $pixnr++;
196                 $pixwidth = intval($val->quotient * FEEDBACK_MAX_PIX_LENGTH);
197                 $pixwidthspacer = FEEDBACK_MAX_PIX_LENGTH + 1 - $pixwidth;
199                 $avg += $val->avg;
200                 $quotient = format_float($val->quotient * 100, 2);
201                 echo '<tr>';
202                 echo '<td class="optionname">';
203                 echo '<span class="weight">('.$val->value.') </span>'.
204                         format_text(trim($val->answertext), FORMAT_HTML, array('noclean' => true, 'para' => false)).':</td>';
205                 echo '<td class="optionvalue" style="width: '.FEEDBACK_MAX_PIX_LENGTH.'">';
206                 echo '<img class="feedback_bar_image" alt="'.$intvalue.'" src="'.$pix.'" width="'.$pixwidth.'" />';
207                 echo '<img class="feedback_bar_image" alt="" src="'.$pixspacer.'" width="'.$pixwidthspacer.'" /> ';
208                 echo $val->answercount;
209                 if ($val->quotient > 0) {
210                     echo ' ('.$quotient.' %)';
211                 } else {
212                     echo '';
213                 }
214                 echo '</td></tr>';
215             }
216             $avg = format_float($avg, 2);
217             echo '<tr><td align="left" colspan="2"><b>';
218             echo get_string('average', 'feedback').': '.$avg.'</b>';
219             echo '</td></tr>';
220         }
221     }
223     public function excelprint_item(&$worksheet, $row_offset,
224                              $xls_formats, $item,
225                              $groupid, $courseid = false) {
227         $analysed_item = $this->get_analysed($item, $groupid, $courseid);
229         $data = $analysed_item[2];
231         //write the item
232         $worksheet->write_string($row_offset, 0, $item->label, $xls_formats->head2);
233         $worksheet->write_string($row_offset, 1, $analysed_item[1], $xls_formats->head2);
234         if (is_array($data)) {
235             $avg = 0.0;
236             $sizeofdata = count($data);
237             for ($i = 0; $i < $sizeofdata; $i++) {
238                 $analysed_data = $data[$i];
240                 $worksheet->write_string($row_offset,
241                                 $i + 2,
242                                 trim($analysed_data->answertext).' ('.$analysed_data->value.')',
243                                 $xls_formats->value_bold);
245                 $worksheet->write_number($row_offset + 1,
246                                 $i + 2,
247                                 $analysed_data->answercount,
248                                 $xls_formats->default);
250                 $avg += $analysed_data->avg;
251             }
252             //mittelwert anzeigen
253             $worksheet->write_string($row_offset,
254                                 count($data) + 2,
255                                 get_string('average', 'feedback'),
256                                 $xls_formats->value_bold);
258             $worksheet->write_number($row_offset + 1,
259                                 count($data) + 2,
260                                 $avg,
261                                 $xls_formats->value_bold);
262         }
263         $row_offset +=2;
264         return $row_offset;
265     }
267     /**
268      * Options for the multichoice element
269      * @param stdClass $item
270      * @return array
271      */
272     protected function get_options($item) {
273         $info = $this->get_info($item);
274         $lines = explode(FEEDBACK_MULTICHOICERATED_LINE_SEP, $info->presentation);
275         $options = array();
276         foreach ($lines as $idx => $line) {
277             list($weight, $optiontext) = explode(FEEDBACK_MULTICHOICERATED_VALUE_SEP, $line);
278             $options[$idx + 1] = format_text("<span class=\"weight\">($weight) </span>".$optiontext,
279                     FORMAT_HTML, array('noclean' => true, 'para' => false));
280         }
281         if ($info->subtype === 'r' && !$this->hidenoselect($item)) {
282             $options = array(0 => get_string('not_selected', 'feedback')) + $options;
283         }
285         return $options;
286     }
288     /**
289      * Adds an input element to the complete form
290      *
291      * @param stdClass $item
292      * @param mod_feedback_complete_form $form
293      */
294     public function complete_form_element($item, $form) {
295         $info = $this->get_info($item);
296         $name = $this->get_display_name($item);
297         $class = 'multichoicerated-' . $info->subtype;
298         $inputname = $item->typ . '_' . $item->id;
299         $options = $this->get_options($item);
300         if ($info->subtype === 'd' || $form->is_frozen()) {
301             $el = $form->add_form_element($item,
302                     ['select', $inputname, $name, array('' => '') + $options, array('class' => $class)]);
303         } else {
304             $objs = array();
305             foreach ($options as $idx => $label) {
306                 $objs[] = ['radio', $inputname, '', $label, $idx];
307             }
308             $separator = $info->horizontal ? ' ' : '<br>';
309             $class .= ' multichoicerated-' . ($info->horizontal ? 'horizontal' : 'vertical');
310             $el = $form->add_form_group_element($item, 'group_'.$inputname, $name, $objs, $separator, $class);
312             // Set previously input values.
313             $form->set_element_default($inputname, $form->get_item_value($item));
315             // Process "required" rule.
316             if ($item->required) {
317                 $form->add_validation_rule(function($values, $files) use ($item) {
318                     $inputname = $item->typ . '_' . $item->id;
319                     return empty($values[$inputname]) ? array('group_' . $inputname => get_string('required')) : true;
320                 });
321             }
322         }
323     }
325     /**
326      * Compares the dbvalue with the dependvalue
327      *
328      * @param stdClass $item
329      * @param string $dbvalue is the value input by user in the format as it is stored in the db
330      * @param string $dependvalue is the value that it needs to be compared against
331      */
332     public function compare_value($item, $dbvalue, $dependvalue) {
334         if (is_array($dbvalue)) {
335             $dbvalues = $dbvalue;
336         } else {
337             $dbvalues = explode(FEEDBACK_MULTICHOICERATED_LINE_SEP, $dbvalue);
338         }
340         $info = $this->get_info($item);
341         $presentation = explode (FEEDBACK_MULTICHOICERATED_LINE_SEP, $info->presentation);
342         $index = 1;
343         foreach ($presentation as $pres) {
344             $presvalues = explode(FEEDBACK_MULTICHOICERATED_VALUE_SEP, $pres);
346             foreach ($dbvalues as $dbval) {
347                 if ($dbval == $index AND trim($presvalues[1]) == $dependvalue) {
348                     return true;
349                 }
350             }
351             $index++;
352         }
353         return false;
354     }
356     public function get_info($item) {
357         $presentation = empty($item->presentation) ? '' : $item->presentation;
359         $info = new stdClass();
360         //check the subtype of the multichoice
361         //it can be check(c), radio(r) or dropdown(d)
362         $info->subtype = '';
363         $info->presentation = '';
364         $info->horizontal = false;
366         $parts = explode(FEEDBACK_MULTICHOICERATED_TYPE_SEP, $item->presentation);
367         @list($info->subtype, $info->presentation) = $parts;
369         if (!isset($info->subtype)) {
370             $info->subtype = 'r';
371         }
373         if ($info->subtype != 'd') {
374             $parts = explode(FEEDBACK_MULTICHOICERATED_ADJUST_SEP, $info->presentation);
375             @list($info->presentation, $info->horizontal) = $parts;
377             if (isset($info->horizontal) AND $info->horizontal == 1) {
378                 $info->horizontal = true;
379             } else {
380                 $info->horizontal = false;
381             }
382         }
384         $info->values = $this->prepare_presentation_values_print($info->presentation,
385                                                     FEEDBACK_MULTICHOICERATED_VALUE_SEP,
386                                                     FEEDBACK_MULTICHOICERATED_VALUE_SEP2);
387         return $info;
388     }
390     public function prepare_presentation_values($linesep1,
391                                          $linesep2,
392                                          $valuestring,
393                                          $valuesep1,
394                                          $valuesep2) {
396         $lines = explode($linesep1, $valuestring);
397         $newlines = array();
398         foreach ($lines as $line) {
399             $value = '';
400             $text = '';
401             if (strpos($line, $valuesep1) === false) {
402                 $value = 0;
403                 $text = $line;
404             } else {
405                 @list($value, $text) = explode($valuesep1, $line, 2);
406             }
408             $value = intval($value);
409             $newlines[] = $value.$valuesep2.$text;
410         }
411         $newlines = implode($linesep2, $newlines);
412         return $newlines;
413     }
415     public function prepare_presentation_values_print($valuestring, $valuesep1, $valuesep2) {
416         $valuestring = str_replace(array("\n","\r"), "", $valuestring);
417         return $this->prepare_presentation_values(FEEDBACK_MULTICHOICERATED_LINE_SEP,
418                                                   "\n",
419                                                   $valuestring,
420                                                   $valuesep1,
421                                                   $valuesep2);
422     }
424     public function prepare_presentation_values_save($valuestring, $valuesep1, $valuesep2) {
425         $valuestring = str_replace("\r", "\n", $valuestring);
426         $valuestring = str_replace("\n\n", "\n", $valuestring);
427         return $this->prepare_presentation_values("\n",
428                         FEEDBACK_MULTICHOICERATED_LINE_SEP,
429                         $valuestring,
430                         $valuesep1,
431                         $valuesep2);
432     }
434     public function set_ignoreempty($item, $ignoreempty=true) {
435         $item->options = str_replace(FEEDBACK_MULTICHOICERATED_IGNOREEMPTY, '', $item->options);
436         if ($ignoreempty) {
437             $item->options .= FEEDBACK_MULTICHOICERATED_IGNOREEMPTY;
438         }
439     }
441     public function ignoreempty($item) {
442         if (strstr($item->options, FEEDBACK_MULTICHOICERATED_IGNOREEMPTY)) {
443             return true;
444         }
445         return false;
446     }
448     public function set_hidenoselect($item, $hidenoselect=true) {
449         $item->options = str_replace(FEEDBACK_MULTICHOICERATED_HIDENOSELECT, '', $item->options);
450         if ($hidenoselect) {
451             $item->options .= FEEDBACK_MULTICHOICERATED_HIDENOSELECT;
452         }
453     }
455     public function hidenoselect($item) {
456         if (strstr($item->options, FEEDBACK_MULTICHOICERATED_HIDENOSELECT)) {
457             return true;
458         }
459         return false;
460     }