2 // This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
18 * Contains class mod_feedback_responses_table
20 * @package mod_feedback
21 * @copyright 2016 Marina Glancy
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25 defined('MOODLE_INTERNAL') || die();
28 require_once($CFG->libdir . '/tablelib.php');
31 * Class mod_feedback_responses_table
33 * @package mod_feedback
34 * @copyright 2016 Marina Glancy
35 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
37 class mod_feedback_responses_table extends table_sql {
39 /** @var mod_feedback_structure */
40 protected $feedbackstructure;
43 protected $grandtotal = null;
46 protected $showall = false;
49 protected $showallparamname = 'showall';
52 protected $downloadparamname = 'download';
57 * @param mod_feedback_structure $feedbackstructure
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);
71 $this->baseurl->param($this->showallparamname, $this->showall);
74 $this->is_downloadable(true);
75 $this->is_downloading(optional_param($this->downloadparamname, 0, PARAM_ALPHA),
78 $this->useridfield = 'userid';
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';
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);
115 if ($this->feedbackstructure->get_feedback()->course == SITEID && !$this->feedbackstructure->get_courseid()) {
116 $tablecolumns[] = 'courseid';
117 $tableheaders[] = get_string('course');
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');
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);
138 $where .= ' AND c.userid IN (SELECT g.userid FROM {groups_members} g WHERE g.groupid = :group)';
139 $params['group'] = $group;
142 $this->set_sql($fields, $from, $where, $params);
143 $this->set_count_sql("SELECT COUNT(c.id) FROM $from WHERE $where", $params);
148 * @return context_module
150 protected function get_context() {
151 return context_module::instance($this->feedbackstructure->get_cm()->id);
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
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] ));
165 return $row->$column;
169 * Prepares column userpic for display
170 * @param stdClass $row
173 public function col_userpic($row) {
175 $user = user_picture::unalias($row, [], $this->useridfield);
176 return $OUTPUT->user_picture($user, array('courseid' => $this->feedbackstructure->get_cm()->course));
180 * Prepares column deleteentry for display
181 * @param stdClass $row
184 public function col_deleteentry($row) {
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);
193 * Returns a link for viewing a single response
194 * @param stdClass $row
195 * @return \moodle_url
197 protected function get_link_single_entry($row) {
198 return new moodle_url($this->baseurl, ['userid' => $row->{$this->useridfield}, 'showcompleted' => $row->id]);
202 * Prepares column completed_timemodified for display
203 * @param stdClass $student
206 public function col_completed_timemodified($student) {
207 if ($this->is_downloading()) {
208 return userdate($student->completed_timemodified);
210 return html_writer::link($this->get_link_single_entry($student),
211 userdate($student->completed_timemodified));
216 * Prepares column courseid for display
220 public function col_courseid($row) {
221 $courses = $this->feedbackstructure->get_completed_courses();
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);
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.
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);
252 // Add 'Delete entry' column.
253 if (!$this->is_downloading() && has_capability('mod/feedback:deletesubmissions', $this->get_context())) {
254 $tablecolumns[] = 'deleteentry';
255 $tableheaders[] = '';
258 $this->define_columns($tablecolumns);
259 $this->define_headers($tableheaders);
263 * Query the db. Store results in the table object for use by build_table.
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.
269 public function query_db($pagesize, $useinitialsbar=true) {
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();
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);
286 if ($this->totalrows > $pagesize) {
287 $this->pagesize($pagesize, $this->totalrows);
291 if ($sort = $this->get_sql_sort()) {
292 $sort = "ORDER BY $sort";
296 FROM {$this->sql->from}
297 WHERE {$this->sql->where}
300 if (!$this->is_downloading()) {
301 $this->rawdata = $DB->get_recordset_sql($sql, $this->sql->params, $this->get_page_start(), $this->get_page_size());
303 $this->rawdata = $DB->get_recordset_sql($sql, $this->sql->params);
308 * Returns total number of reponses (without any filters applied)
311 public function get_total_responses_count() {
313 if ($this->grandtotal === null) {
314 $this->grandtotal = $DB->count_records_sql($this->countsql, $this->countparams);
316 return $this->grandtotal;
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.
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;
333 * Convenience method to call a number of methods for you to display the
335 * @param int $pagesize
336 * @param bool $useinitialsbar
337 * @param string $downloadhelpbutton
339 public function out($pagesize, $useinitialsbar, $downloadhelpbutton='') {
340 $this->add_all_values_to_output();
341 parent::out($pagesize, $useinitialsbar, $downloadhelpbutton);
347 public function display() {
349 groups_print_activity_menu($this->feedbackstructure->get_cm(), $this->baseurl->out());
350 $grandtotal = $this->get_total_responses_count();
352 echo $OUTPUT->box(get_string('nothingtodisplay'), 'generalbox nothingtodisplay');
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');
364 echo html_writer::div(html_writer::link(new moodle_url($this->baseurl, [$this->showallparamname => 1]),
365 get_string('showall', '', $this->totalrows)), 'showall');
371 * Returns links to previous/next responses in the list
372 * @param stdClass $record
373 * @return array array of three elements [$prevresponseurl, $returnurl, $nextresponseurl]
375 public function get_reponse_navigation_links($record) {
377 $grandtotal = $this->get_total_responses_count();
378 $this->query_db($grandtotal);
379 $lastrow = $thisrow = $nextrow = null;
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);
387 $this->rawdata->next();
388 $nextrow = $this->rawdata->valid() ? $this->rawdata->current() : null;
392 $this->rawdata->next();
395 $this->rawdata->close();
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,
409 public function download() {
410 \core\session\manager::write_close();
411 $this->out($this->get_total_responses_count(), false);
416 * Returns html code for displaying "Download" button if applicable.
418 public function download_buttons() {
419 if ($this->is_downloadable() && !$this->is_downloading()) {
421 $elementid = $this->uniqueid . '_download';
422 $html = '<div class="mdl-align">';
423 $html .= '<form action="'. $this->baseurl .'" method="post" class="form-inline">';
424 if ($courseid = $this->feedbackstructure->get_courseid()) {
425 $html .= '<input type="hidden" name="courseid" value="' . s($courseid) . '">';
427 $html .= html_writer::tag('label', get_string('downloadresponseas', 'feedback'),
428 ['for' => $elementid]);
429 $html .= html_writer::select($this->get_download_menu(),
430 $this->downloadparamname, $this->defaultdownloadformat, false, ['id' => $elementid]);
431 $html .= html_writer::empty_tag('input', ['type' => 'submit', 'value' => get_string('download')]);
432 $html .= '</form></div>';