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/>.
16
17defined('MOODLE_INTERNAL') OR die('not allowed');
18require_once($CFG->dirroot.'/mod/feedback/item/feedback_item_class.php');
19
20class feedback_item_numeric extends feedback_item_base {
21    protected $type = "numeric";
22
23    public function build_editform($item, $feedback, $cm) {
24        global $DB, $CFG;
25        require_once('numeric_form.php');
26
27        //get the lastposition number of the feedback_items
28        $position = $item->position;
29        $lastposition = $DB->count_records('feedback_item', array('feedback'=>$feedback->id));
30        if ($position == -1) {
31            $i_formselect_last = $lastposition + 1;
32            $i_formselect_value = $lastposition + 1;
33            $item->position = $lastposition + 1;
34        } else {
35            $i_formselect_last = $lastposition;
36            $i_formselect_value = $item->position;
37        }
38        //the elements for position dropdownlist
39        $positionlist = array_slice(range(0, $i_formselect_last), 1, $i_formselect_last, true);
40
41        $item->presentation = empty($item->presentation) ? '' : $item->presentation;
42
43        $range_from_to = explode('|', $item->presentation);
44        if (isset($range_from_to[0]) AND is_numeric($range_from_to[0])) {
45            $range_from = $this->format_float($range_from_to[0]);
46        } else {
47            $range_from = '-';
48        }
49
50        if (isset($range_from_to[1]) AND is_numeric($range_from_to[1])) {
51            $range_to = $this->format_float($range_from_to[1]);
52        } else {
53            $range_to = '-';
54        }
55
56        $item->rangefrom = $range_from;
57        $item->rangeto = $range_to;
58
59        //all items for dependitem
60        $feedbackitems = feedback_get_depend_candidates_for_item($feedback, $item);
61        $commonparams = array('cmid'=>$cm->id,
62                             'id'=>isset($item->id) ? $item->id : null,
63                             'typ'=>$item->typ,
64                             'items'=>$feedbackitems,
65                             'feedback'=>$feedback->id);
66
67        //build the form
68        $customdata = array('item' => $item,
69                            'common' => $commonparams,
70                            'positionlist' => $positionlist,
71                            'position' => $position);
72
73        $this->item_form = new feedback_numeric_form('edit_item.php', $customdata);
74    }
75
76    public function save_item() {
77        global $DB;
78
79        if (!$this->get_data()) {
80            return false;
81        }
82        $item = $this->item;
83
84        if (isset($item->clone_item) AND $item->clone_item) {
85            $item->id = ''; //to clone this item
86            $item->position++;
87        }
88
89        $item->hasvalue = $this->get_hasvalue();
90        if (!$item->id) {
91            $item->id = $DB->insert_record('feedback_item', $item);
92        } else {
93            $DB->update_record('feedback_item', $item);
94        }
95
96        return $DB->get_record('feedback_item', array('id'=>$item->id));
97    }
98
99    /**
100     * Helper function for collected data, both for analysis page and export to excel
101     *
102     * @param stdClass $item the db-object from feedback_item
103     * @param int $groupid
104     * @param int $courseid
105     * @return stdClass
106     */
107    protected function get_analysed($item, $groupid = false, $courseid = false) {
108        global $DB;
109
110        $analysed = new stdClass();
111        $analysed->data = array();
112        $analysed->name = $item->name;
113        $values = feedback_get_group_values($item, $groupid, $courseid);
114
115        $avg = 0.0;
116        $counter = 0;
117        if ($values) {
118            $data = array();
119            foreach ($values as $value) {
120                if (is_numeric($value->value)) {
121                    $data[] = $value->value;
122                    $avg += $value->value;
123                    $counter++;
124                }
125            }
126            $avg = $counter > 0 ? $avg / $counter : null;
127            $analysed->data = $data;
128            $analysed->avg = $avg;
129        }
130        return $analysed;
131    }
132
133    public function get_printval($item, $value) {
134        if (!isset($value->value)) {
135            return '';
136        }
137
138        return $value->value;
139    }
140
141    public function print_analysed($item, $itemnr = '', $groupid = false, $courseid = false) {
142
143        $values = $this->get_analysed($item, $groupid, $courseid);
144
145        if (isset($values->data) AND is_array($values->data)) {
146            echo "<table class=\"analysis itemtype_{$item->typ}\">";
147            echo '<tr><th colspan="2" align="left">';
148            echo $itemnr . ' ';
149            if (strval($item->label) !== '') {
150                echo '('. format_string($item->label).') ';
151            }
152            echo format_text($item->name, FORMAT_HTML, array('noclean' => true, 'para' => false));
153            echo '</th></tr>';
154
155            foreach ($values->data as $value) {
156                echo '<tr><td colspan="2" class="singlevalue">';
157                echo $this->format_float($value);
158                echo '</td></tr>';
159            }
160
161            if (isset($values->avg)) {
162                $avg = format_float($values->avg, 2);
163            } else {
164                $avg = '-';
165            }
166            echo '<tr><td colspan="2"><b>';
167            echo get_string('average', 'feedback').': '.$avg;
168            echo '</b></td></tr>';
169            echo '</table>';
170        }
171    }
172
173    public function excelprint_item(&$worksheet, $row_offset,
174                             $xls_formats, $item,
175                             $groupid, $courseid = false) {
176
177        $analysed_item = $this->get_analysed($item, $groupid, $courseid);
178
179        $worksheet->write_string($row_offset, 0, $item->label, $xls_formats->head2);
180        $worksheet->write_string($row_offset, 1, $item->name, $xls_formats->head2);
181        $data = $analysed_item->data;
182        if (is_array($data)) {
183
184            // Export average.
185            $worksheet->write_string($row_offset,
186                                     2,
187                                     get_string('average', 'feedback'),
188                                     $xls_formats->value_bold);
189
190            if (isset($analysed_item->avg)) {
191                $worksheet->write_number($row_offset + 1,
192                                         2,
193                                         $analysed_item->avg,
194                                         $xls_formats->value_bold);
195            } else {
196                $worksheet->write_string($row_offset + 1,
197                                         2,
198                                         '',
199                                         $xls_formats->value_bold);
200            }
201            $row_offset++;
202        }
203        $row_offset++;
204        return $row_offset;
205    }
206
207    /**
208     * Prints the float nicely in the localized format
209     *
210     * Similar to format_float() but automatically calculates the number of decimal places
211     *
212     * @param float $value The float to print
213     * @return string
214     */
215    protected function format_float($value) {
216        if (!is_numeric($value)) {
217            return null;
218        }
219        $decimal = is_int($value) ? 0 : strlen(substr(strrchr($value, '.'), 1));
220        return format_float($value, $decimal);
221    }
222
223    /**
224     * Returns human-readable boundaries (min - max)
225     * @param stdClass $item
226     * @return string
227     */
228    protected function get_boundaries_for_display($item) {
229        list($rangefrom, $rangeto) = explode('|', $item->presentation);
230        if (!isset($rangefrom) || !is_numeric($rangefrom)) {
231            $rangefrom = null;
232        }
233        if (!isset($rangeto) || !is_numeric($rangeto)) {
234            $rangeto = null;
235        }
236
237        if (is_null($rangefrom) && is_numeric($rangeto)) {
238            return ' (' . get_string('maximal', 'feedback') .
239                        ': ' . $this->format_float($rangeto) . ')';
240        }
241        if (is_numeric($rangefrom) && is_null($rangeto)) {
242            return ' (' . get_string('minimal', 'feedback') .
243                        ': ' . $this->format_float($rangefrom) . ')';
244        }
245        if (is_null($rangefrom) && is_null($rangeto)) {
246            return '';
247        }
248        return ' (' . $this->format_float($rangefrom) .
249                ' - ' . $this->format_float($rangeto) . ')';
250    }
251
252    /**
253     * Returns the postfix to be appended to the display name that is based on other settings
254     *
255     * @param stdClass $item
256     * @return string
257     */
258    public function get_display_name_postfix($item) {
259        return html_writer::span($this->get_boundaries_for_display($item), 'boundaries');
260    }
261
262    /**
263     * Adds an input element to the complete form
264     *
265     * @param stdClass $item
266     * @param mod_feedback_complete_form $form
267     */
268    public function complete_form_element($item, $form) {
269        $name = $this->get_display_name($item);
270        $inputname = $item->typ . '_' . $item->id;
271        $form->add_form_element($item,
272                ['text', $inputname, $name],
273                true,
274                false
275                );
276        $form->set_element_type($inputname, PARAM_NOTAGS);
277        $tmpvalue = $this->format_float($form->get_item_value($item));
278        $form->set_element_default($inputname, $tmpvalue);
279
280        // Add form validation rule to check for boundaries.
281        $form->add_validation_rule(function($values, $files) use ($item) {
282            $inputname = $item->typ . '_' . $item->id;
283            list($rangefrom, $rangeto) = explode('|', $item->presentation);
284            if (!isset($values[$inputname]) || trim($values[$inputname]) === '') {
285                return $item->required ? array($inputname => get_string('required')) : true;
286            }
287            $value = unformat_float($values[$inputname], true);
288            if ($value === false) {
289                return array($inputname => get_string('invalidnum', 'error'));
290            }
291            if ((is_numeric($rangefrom) && $value < floatval($rangefrom)) ||
292                    (is_numeric($rangeto) && $value > floatval($rangeto))) {
293                return array($inputname => get_string('numberoutofrange', 'feedback'));
294            }
295            return true;
296        });
297    }
298
299    public function create_value($data) {
300        $data = unformat_float($data, true);
301
302        if (is_numeric($data)) {
303            $data = floatval($data);
304        } else {
305            $data = '';
306        }
307        return $data;
308    }
309
310    /**
311     * Return the analysis data ready for external functions.
312     *
313     * @param stdClass $item     the item (question) information
314     * @param int      $groupid  the group id to filter data (optional)
315     * @param int      $courseid the course id (optional)
316     * @return array an array of data with non scalar types json encoded
317     * @since  Moodle 3.3
318     */
319    public function get_analysed_for_external($item, $groupid = false, $courseid = false) {
320
321        $externaldata = array();
322        $data = $this->get_analysed($item, $groupid, $courseid);
323
324        if (is_array($data->data)) {
325            return $data->data; // No need to json, scalar type.
326        }
327        return $externaldata;
328    }
329}
330