MDL-53638 mod_feedback: corrections during rebase:
[moodle.git] / mod / feedback / classes / responses_table.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 /**
18  * Contains class mod_feedback_responses_table
19  *
20  * @package   mod_feedback
21  * @copyright 2016 Marina Glancy
22  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
25 defined('MOODLE_INTERNAL') || die();
27 global $CFG;
28 require_once($CFG->libdir . '/tablelib.php');
30 /**
31  * Class mod_feedback_responses_table
32  *
33  * @package   mod_feedback
34  * @copyright 2016 Marina Glancy
35  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
36  */
37 class mod_feedback_responses_table extends table_sql {
39     /** @var mod_feedback_structure */
40     protected $feedbackstructure;
42     /** @var int */
43     protected $grandtotal = null;
45     /** @var bool */
46     protected $showall = false;
48     /** @var string */
49     protected $showallparamname = 'showall';
51     /** @var string */
52     protected $downloadparamname = 'download';
54     /**
55      * Constructor
56      *
57      * @param mod_feedback_structure $feedbackstructure
58      */
59     public function __construct(mod_feedback_structure $feedbackstructure) {
60         $this->feedbackstructure = $feedbackstructure;
62         parent::__construct('feedback-showentry-list-' . $feedbackstructure->get_cm()->instance);
64         $this->showall = optional_param($this->showallparamname, 0, PARAM_BOOL);
65         $this->define_baseurl(new moodle_url('/mod/feedback/show_entries.php',
66             ['id' => $this->feedbackstructure->get_cm()->id]));
67         if ($courseid = $this->feedbackstructure->get_courseid()) {
68             $this->baseurl->param('courseid', $courseid);
69         }
70         if ($this->showall) {
71             $this->baseurl->param($this->showallparamname, $this->showall);
72         }
74         $this->is_downloadable(true);
75         $this->is_downloading(optional_param($this->downloadparamname, 0, PARAM_ALPHA),
76                 'feedback_test');
78         $this->useridfield = 'userid';
79         $this->init();
80     }
82     /**
83      * Initialises table
84      */
85     protected function init() {
87         $tablecolumns = array('userpic', 'fullname');
88         $tableheaders = array(get_string('userpic'), get_string('fullnameuser'));
90         $extrafields = get_extra_user_fields($this->get_context());
91         $ufields = user_picture::fields('u', $extrafields, $this->useridfield);
92         $fields = 'c.id, c.timemodified as completed_timemodified, c.courseid, '.$ufields;
93         $from = '{feedback_completed} c '
94                 . 'JOIN {user} u ON u.id = c.userid AND u.deleted = :notdeleted';
95         $where = 'c.anonymous_response = :anon
96                 AND c.feedback = :instance';
97         if ($this->feedbackstructure->get_courseid()) {
98             $where .= ' AND c.courseid = :courseid';
99         }
101         if ($this->is_downloading()) {
102             // When downloading data:
103             // Remove 'userpic' from downloaded data.
104             array_shift($tablecolumns);
105             array_shift($tableheaders);
107             // Add all identity fields as separate columns.
108             foreach ($extrafields as $field) {
109                 $fields .= ", u.{$field}";
110                 $tablecolumns[] = $field;
111                 $tableheaders[] = get_user_field_name($field);
112             }
113         }
115         if ($this->feedbackstructure->get_feedback()->course == SITEID && !$this->feedbackstructure->get_courseid()) {
116             $tablecolumns[] = 'courseid';
117             $tableheaders[] = get_string('course');
118         }
120         $tablecolumns[] = 'completed_timemodified';
121         $tableheaders[] = get_string('date');
123         $this->define_columns($tablecolumns);
124         $this->define_headers($tableheaders);
126         $this->sortable(true, 'lastname', SORT_ASC);
127         $this->collapsible(true);
128         $this->set_attribute('id', 'showentrytable');
130         $params = array();
131         $params['anon'] = FEEDBACK_ANONYMOUS_NO;
132         $params['instance'] = $this->feedbackstructure->get_feedback()->id;
133         $params['notdeleted'] = 0;
134         $params['courseid'] = $this->feedbackstructure->get_courseid();
136         $group = groups_get_activity_group($this->feedbackstructure->get_cm(), true);
137         if ($group) {
138             $where .= ' AND c.userid IN (SELECT g.userid FROM {groups_members} g WHERE g.groupid = :group)';
139             $params['group'] = $group;
140         }
142         $this->set_sql($fields, $from, $where, $params);
143         $this->set_count_sql("SELECT COUNT(c.id) FROM $from WHERE $where", $params);
144     }
146     /**
147      * Current context
148      * @return context_module
149      */
150     protected function get_context() {
151         return context_module::instance($this->feedbackstructure->get_cm()->id);
152     }
154     /**
155      * Allows to set the display column value for all columns without "col_xxxxx" method.
156      * @param string $column column name
157      * @param stdClass $row current record result of SQL query
158      */
159     public function other_cols($column, $row) {
160         if (preg_match('/^val(\d+)$/', $column, $matches)) {
161             $items = $this->feedbackstructure->get_items();
162             $itemobj = feedback_get_item_class($items[$matches[1]]->typ);
163             return trim($itemobj->get_printval($items[$matches[1]], (object) ['value' => $row->$column] ));
164         }
165         return $row->$column;
166     }
168     /**
169      * Prepares column userpic for display
170      * @param stdClass $row
171      * @return string
172      */
173     public function col_userpic($row) {
174         global $OUTPUT;
175         $user = user_picture::unalias($row, [], $this->useridfield);
176         return $OUTPUT->user_picture($user, array('courseid' => $this->feedbackstructure->get_cm()->course));
177     }
179     /**
180      * Prepares column deleteentry for display
181      * @param stdClass $row
182      * @return string
183      */
184     public function col_deleteentry($row) {
185         global $OUTPUT;
186         $deleteentryurl = new moodle_url($this->baseurl, ['delete' => $row->id, 'sesskey' => sesskey()]);
187         $deleteaction = new confirm_action(get_string('confirmdeleteentry', 'feedback'));
188         return $OUTPUT->action_icon($deleteentryurl,
189             new pix_icon('t/delete', get_string('delete_entry', 'feedback')), $deleteaction);
190     }
192     /**
193      * Returns a link for viewing a single response
194      * @param stdClass $row
195      * @return \moodle_url
196      */
197     protected function get_link_single_entry($row) {
198         return new moodle_url($this->baseurl, ['userid' => $row->{$this->useridfield}, 'showcompleted' => $row->id]);
199     }
201     /**
202      * Prepares column completed_timemodified for display
203      * @param stdClass $student
204      * @return string
205      */
206     public function col_completed_timemodified($student) {
207         if ($this->is_downloading()) {
208             return userdate($student->completed_timemodified);
209         } else {
210             return html_writer::link($this->get_link_single_entry($student),
211                     userdate($student->completed_timemodified));
212         }
213     }
215     /**
216      * Prepares column courseid for display
217      * @param array $row
218      * @return string
219      */
220     public function col_courseid($row) {
221         $courses = $this->feedbackstructure->get_completed_courses();
222         $name = '';
223         if (isset($courses[$row->courseid])) {
224             $name = $courses[$row->courseid];
225             if (!$this->is_downloading()) {
226                 $name = html_writer::link(course_get_url($row->courseid), $name);
227             }
228         }
229         return $name;
230     }
232     /**
233      * Adds common values to the table that do not change the number or order of entries and
234      * are only needed when outputting or downloading data.
235      */
236     protected function add_all_values_to_output() {
237         $tablecolumns = array_keys($this->columns);
238         $tableheaders = $this->headers;
240         // Add all feedback response values.
241         $items = $this->feedbackstructure->get_items(true);
242         foreach ($items as $nr => $item) {
243             $this->sql->fields .= ", v{$nr}.value AS val{$nr}";
244             $this->sql->from .= " LEFT OUTER JOIN {feedback_value} v{$nr} " .
245                 "ON v{$nr}.completed = c.id AND v{$nr}.item = :itemid{$nr}";
246             $this->sql->params["itemid{$nr}"] = $item->id;
247             $tablecolumns[] = "val{$nr}";
248             $itemobj = feedback_get_item_class($item->typ);
249             $tableheaders[] = $itemobj->get_display_name($item);
250         }
252         // Add 'Delete entry' column.
253         if (!$this->is_downloading() && has_capability('mod/feedback:deletesubmissions', $this->get_context())) {
254             $tablecolumns[] = 'deleteentry';
255             $tableheaders[] = '';
256         }
258         $this->define_columns($tablecolumns);
259         $this->define_headers($tableheaders);
260     }
262     /**
263      * Query the db. Store results in the table object for use by build_table.
264      *
265      * @param int $pagesize size of page for paginated displayed table.
266      * @param bool $useinitialsbar do you want to use the initials bar. Bar
267      * will only be used if there is a fullname column defined for the table.
268      */
269     public function query_db($pagesize, $useinitialsbar=true) {
270         global $DB;
271         $this->totalrows = $grandtotal = $this->get_total_responses_count();
272         if (!$this->is_downloading()) {
273             $this->initialbars($useinitialsbar);
275             list($wsql, $wparams) = $this->get_sql_where();
276             if ($wsql) {
277                 $this->countsql .= ' AND '.$wsql;
278                 $this->countparams = array_merge($this->countparams, $wparams);
280                 $this->sql->where .= ' AND '.$wsql;
281                 $this->sql->params = array_merge($this->sql->params, $wparams);
283                 $this->totalrows  = $DB->count_records_sql($this->countsql, $this->countparams);
284             }
286             if ($this->totalrows > $pagesize) {
287                 $this->pagesize($pagesize, $this->totalrows);
288             }
289         }
291         if ($sort = $this->get_sql_sort()) {
292             $sort = "ORDER BY $sort";
293         }
294         $sql = "SELECT
295                 {$this->sql->fields}
296                 FROM {$this->sql->from}
297                 WHERE {$this->sql->where}
298                 {$sort}";
300         if (!$this->is_downloading()) {
301             $this->rawdata = $DB->get_recordset_sql($sql, $this->sql->params, $this->get_page_start(), $this->get_page_size());
302         } else {
303             $this->rawdata = $DB->get_recordset_sql($sql, $this->sql->params);
304         }
305     }
307     /**
308      * Returns total number of reponses (without any filters applied)
309      * @return int
310      */
311     public function get_total_responses_count() {
312         global $DB;
313         if ($this->grandtotal === null) {
314             $this->grandtotal = $DB->count_records_sql($this->countsql, $this->countparams);
315         }
316         return $this->grandtotal;
317     }
319     /**
320      * Defines columns
321      * @param array $columns an array of identifying names for columns. If
322      * columns are sorted then column names must correspond to a field in sql.
323      */
324     public function define_columns($columns) {
325         parent::define_columns($columns);
326         foreach ($this->columns as $column => $column) {
327             // Automatically assign classes to columns.
328             $this->column_class[$column] = ' ' . $column;
329         }
330     }
332     /**
333      * Convenience method to call a number of methods for you to display the
334      * table.
335      * @param int $pagesize
336      * @param bool $useinitialsbar
337      * @param string $downloadhelpbutton
338      */
339     public function out($pagesize, $useinitialsbar, $downloadhelpbutton='') {
340         $this->add_all_values_to_output();
341         parent::out($pagesize, $useinitialsbar, $downloadhelpbutton);
342     }
344     /**
345      * Displays the table
346      */
347     public function display() {
348         global $OUTPUT;
349         groups_print_activity_menu($this->feedbackstructure->get_cm(), $this->baseurl->out());
350         $grandtotal = $this->get_total_responses_count();
351         if (!$grandtotal) {
352             echo $OUTPUT->box(get_string('nothingtodisplay'), 'generalbox nothingtodisplay');
353             return;
354         }
355         $this->out($this->showall ? $grandtotal : FEEDBACK_DEFAULT_PAGE_COUNT,
356                 $grandtotal > FEEDBACK_DEFAULT_PAGE_COUNT);
358         // Toggle 'Show all' link.
359         if ($this->totalrows > FEEDBACK_DEFAULT_PAGE_COUNT) {
360             if (!$this->use_pages) {
361                 echo html_writer::div(html_writer::link(new moodle_url($this->baseurl, [$this->showallparamname => 0]),
362                         get_string('showperpage', '', FEEDBACK_DEFAULT_PAGE_COUNT)), 'showall');
363             } else {
364                 echo html_writer::div(html_writer::link(new moodle_url($this->baseurl, [$this->showallparamname => 1]),
365                         get_string('showall', '', $this->totalrows)), 'showall');
366             }
367         }
368     }
370     /**
371      * Returns links to previous/next responses in the list
372      * @param stdClass $record
373      * @return array array of three elements [$prevresponseurl, $returnurl, $nextresponseurl]
374      */
375     public function get_reponse_navigation_links($record) {
376         $this->setup();
377         $grandtotal = $this->get_total_responses_count();
378         $this->query_db($grandtotal);
379         $lastrow = $thisrow = $nextrow = null;
380         $counter = 0;
381         $page = 0;
382         while ($this->rawdata->valid()) {
383             $row = $this->rawdata->current();
384             if ($row->id == $record->id) {
385                 $page = $this->showall ? 0 : floor($counter / FEEDBACK_DEFAULT_PAGE_COUNT);
386                 $thisrow = $row;
387                 $this->rawdata->next();
388                 $nextrow = $this->rawdata->valid() ? $this->rawdata->current() : null;
389                 break;
390             }
391             $lastrow = $row;
392             $this->rawdata->next();
393             $counter++;
394         }
395         $this->rawdata->close();
396         if (!$thisrow) {
397             $lastrow = null;
398         }
399         return [
400             $lastrow ? $this->get_link_single_entry($lastrow) : null,
401             new moodle_url($this->baseurl, [$this->request[TABLE_VAR_PAGE] => $page]),
402             $nextrow ? $this->get_link_single_entry($nextrow) : null,
403         ];
404     }
406     /**
407      * Download the data.
408      */
409     public function download() {
410         \core\session\manager::write_close();
411         $this->out($this->get_total_responses_count(), false);
412         exit;
413     }
415     /**
416      * Returns html code for displaying "Download" button if applicable.
417      */
418     public function download_buttons() {
419         global $OUTPUT;
421         if ($this->is_downloadable() && !$this->is_downloading()) {
422             return $OUTPUT->download_dataformat_selector(get_string('downloadas', 'table'),
423                     $this->baseurl->out_omit_querystring(), $this->downloadparamname, $this->baseurl->params());
424         } else {
425             return '';
426         }
427     }