diff --git a/mod/feedback/analysis.php b/mod/feedback/analysis.php index bed8b63eb02..ed57ac5809a 100644 --- a/mod/feedback/analysis.php +++ b/mod/feedback/analysis.php @@ -59,14 +59,6 @@ require('tabs.php'); $mygroupid = groups_get_activity_group($cm, true); groups_print_activity_menu($cm, $url); -if ( has_capability('mod/feedback:viewreports', $context) ) { - //button "export to excel" - echo $OUTPUT->container_start('form-buttons'); - $aurl = new moodle_url('analysis_to_excel.php', array('sesskey'=>sesskey(), 'id'=>$id)); - echo $OUTPUT->single_button($aurl, get_string('export_to_excel', 'feedback')); - echo $OUTPUT->container_end(); -} - // Show the summary. $summary = new mod_feedback\output\summary($feedbackstructure, $mygroupid); echo $OUTPUT->render_from_template('mod_feedback/summary', $summary->export_for_template($OUTPUT)); diff --git a/mod/feedback/analysis_course.php b/mod/feedback/analysis_course.php index 2a195b165bb..2765baa7beb 100644 --- a/mod/feedback/analysis_course.php +++ b/mod/feedback/analysis_course.php @@ -30,7 +30,6 @@ $current_tab = 'analysis'; $id = required_param('id', PARAM_INT); //the POST dominated the GET $courseitemfilter = optional_param('courseitemfilter', '0', PARAM_INT); $courseitemfiltertyp = optional_param('courseitemfiltertyp', '0', PARAM_ALPHANUM); -$searchcourse = optional_param('searchcourse', '', PARAM_RAW); $courseid = optional_param('courseid', false, PARAM_INT); $url = new moodle_url('/mod/feedback/analysis_course.php', array('id'=>$id)); @@ -44,9 +43,6 @@ if ($courseitemfilter !== '0') { if ($courseitemfiltertyp !== '0') { $url->param('courseitemfiltertyp', $courseitemfiltertyp); } -if ($searchcourse !== '') { - $url->param('searchcourse', $searchcourse); -} $PAGE->set_url($url); list($course, $cm) = get_course_and_cm_from_cmid($id, 'feedback'); @@ -62,6 +58,12 @@ if (!($feedback->publish_stats OR has_capability('mod/feedback:viewreports', $co $feedbackstructure = new mod_feedback_structure($feedback, $PAGE->cm, $courseid); +// Process course select form. +$courseselectform = new mod_feedback_course_select_form($url, $feedbackstructure); +if ($data = $courseselectform->get_data()) { + redirect(new moodle_url($url, ['courseid' => $data->courseid])); +} + /// Print the page header $strfeedbacks = get_string("modulenameplural", "feedback"); $strfeedback = get_string("modulename", "feedback"); @@ -74,21 +76,12 @@ echo $OUTPUT->heading(format_string($feedback->name)); /// print the tabs require('tabs.php'); -//print the analysed items - -if (has_capability('mod/feedback:viewreports', $context)) { - //button "export to excel" - echo $OUTPUT->container_start('form-buttons'); - $aurl = new moodle_url('analysis_to_excel.php', array('sesskey' => sesskey(), 'id' => $id, - 'coursefilter' => $courseid)); - echo $OUTPUT->single_button($aurl, get_string('export_to_excel', 'feedback')); - echo $OUTPUT->container_end(); -} - //get the groupid //lstgroupid is the choosen id $mygroupid = false; +$courseselectform->display(); + // Show the summary. $summary = new mod_feedback\output\summary($feedbackstructure); echo $OUTPUT->render_from_template('mod_feedback/summary', $summary->export_for_template($OUTPUT)); @@ -131,43 +124,6 @@ if ($courseitemfilter > 0) { echo '

'; } else { - echo '
'; - echo '
'; - echo html_writer::label(get_string('search_course', 'feedback') . ': ', 'searchcourse'); - echo ' '; - echo ''; - echo ''; - $sql = 'select DISTINCT c.id, c.shortname from {course} c, '. - '{feedback_value} fv, {feedback_item} fi '. - 'where c.id = fv.course_id and fv.item = fi.id '. - 'and fi.feedback = ? '. - 'and - ('.$DB->sql_like('c.shortname', '?', false).' - OR '.$DB->sql_like('c.fullname', '?', false).')'; - $params = array($feedback->id, "%$searchcourse%", "%$searchcourse%"); - - if ($courses = $DB->get_records_sql_menu($sql, $params)) { - if (!$courseid) { - $courses = array('' => get_string('choosedots')) + $courses; - } - echo ' '. html_writer::label(get_string('filter_by_course', 'feedback'). ': ', 'coursefilterid'); - echo html_writer::select($courses, 'courseid', $courseid, - null, array('id'=>'coursefilterid', 'class' => 'autosubmit')); - - $PAGE->requires->yui_module('moodle-core-formautosubmit', - 'M.core.init_formautosubmit', - array(array('selectid' => 'coursefilterid', 'nothing' => false)) - ); - } - if ($courseid) { - echo ' '; - echo get_string('show_all', 'feedback'); - echo ''; - } - - echo '
'; - echo '
'; - // Print the items in an analysed form. foreach ($items as $item) { echo ''; diff --git a/mod/feedback/analysis_to_excel.php b/mod/feedback/analysis_to_excel.php deleted file mode 100644 index 83029a18b2e..00000000000 --- a/mod/feedback/analysis_to_excel.php +++ /dev/null @@ -1,313 +0,0 @@ -. - -/** - * prints an analysed excel-spreadsheet of the feedback - * - * @author Andreas Grabs - * @license http://www.gnu.org/copyleft/gpl.html GNU Public License - * @package mod_feedback - */ - -require_once("../../config.php"); -require_once("lib.php"); -require_once("$CFG->libdir/excellib.class.php"); - -feedback_load_feedback_items(); - -$id = required_param('id', PARAM_INT); //the POST dominated the GET -$coursefilter = optional_param('coursefilter', '0', PARAM_INT); - -$url = new moodle_url('/mod/feedback/analysis_to_excel.php', array('id'=>$id)); -if ($coursefilter !== '0') { - $url->param('coursefilter', $coursefilter); -} -$PAGE->set_url($url); - -$formdata = data_submitted(); - -if (! $cm = get_coursemodule_from_id('feedback', $id)) { - print_error('invalidcoursemodule'); -} - -if (! $course = $DB->get_record("course", array("id"=>$cm->course))) { - print_error('coursemisconf'); -} - -if (! $feedback = $DB->get_record("feedback", array("id"=>$cm->instance))) { - print_error('invalidcoursemodule'); -} - -$context = context_module::instance($cm->id); - -require_login($course, true, $cm); -$feedbackstructure = new mod_feedback_structure($feedback, $PAGE->cm, $coursefilter); - -require_capability('mod/feedback:viewreports', $context); - -//buffering any output -//this prevents some output before the excel-header will be send -ob_start(); -$fstring = new stdClass(); -$fstring->bold = get_string('bold', 'feedback'); -$fstring->page = get_string('page', 'feedback'); -$fstring->of = get_string('of', 'feedback'); -$fstring->modulenameplural = get_string('modulenameplural', 'feedback'); -$fstring->questions = get_string('questions', 'feedback'); -$fstring->itemlabel = get_string('item_label', 'feedback'); -$fstring->question = get_string('question', 'feedback'); -$fstring->responses = get_string('responses', 'feedback'); -$fstring->idnumber = get_string('idnumber'); -$fstring->username = get_string('username'); -$fstring->fullname = get_string('fullnameuser'); -$fstring->courseid = get_string('courseid', 'feedback'); -$fstring->course = get_string('course'); -$fstring->anonymous_user = get_string('anonymous_user', 'feedback'); -ob_end_clean(); - -//get the questions (item-names) -$items = $feedbackstructure->get_items(true); -if (!$items) { - print_error('no_items_available_yet', - 'feedback', - $CFG->wwwroot.'/mod/feedback/view.php?id='.$id); - exit; -} - -$filename = "feedback.xls"; - -$mygroupid = groups_get_activity_group($cm); - -// Creating a workbook -$workbook = new MoodleExcelWorkbook('-'); -$workbook->send($filename); - -//creating the needed formats -$xls_formats = new stdClass(); -$xls_formats->head1 = $workbook->add_format(array( - 'bold'=>1, - 'size'=>12)); - -$xls_formats->head2 = $workbook->add_format(array( - 'align'=>'left', - 'bold'=>1, - 'bottum'=>2)); - -$xls_formats->default = $workbook->add_format(array( - 'align'=>'left', - 'v_align'=>'top')); - -$xls_formats->value_bold = $workbook->add_format(array( - 'align'=>'left', - 'bold'=>1, - 'v_align'=>'top')); - -$xls_formats->procent = $workbook->add_format(array( - 'align'=>'left', - 'bold'=>1, - 'v_align'=>'top', - 'num_format'=>'#,##0.00%')); - -// Creating the worksheets -$sheetname = clean_param($feedback->name, PARAM_ALPHANUM); -error_reporting(0); -$worksheet1 = $workbook->add_worksheet(substr($sheetname, 0, 31)); -$worksheet2 = $workbook->add_worksheet('detailed'); -error_reporting($CFG->debug); -$worksheet1->hide_gridlines(); -$worksheet1->set_column(0, 0, 10); -$worksheet1->set_column(1, 1, 30); -$worksheet1->set_column(2, 20, 15); - -//writing the table header -$row_offset1 = 0; -$worksheet1->write_string($row_offset1, 0, userdate(time()), $xls_formats->head1); - -//////////////////////////////////////////////////////////////////////// -//print the analysed sheet -//////////////////////////////////////////////////////////////////////// -//get the completeds -$completedscount = $feedbackstructure->count_completed_responses($mygroupid); -if ($completedscount > 0) { - //write the count of completeds - $row_offset1++; - $worksheet1->write_string($row_offset1, - 0, - $fstring->modulenameplural.': '.strval($completedscount), - $xls_formats->head1); -} - -if (is_array($items)) { - $row_offset1++; - $worksheet1->write_string($row_offset1, - 0, - $fstring->questions.': '. strval(count($items)), - $xls_formats->head1); -} - -$row_offset1 += 2; -$worksheet1->write_string($row_offset1, 0, $fstring->itemlabel, $xls_formats->head1); -$worksheet1->write_string($row_offset1, 1, $fstring->question, $xls_formats->head1); -$worksheet1->write_string($row_offset1, 2, $fstring->responses, $xls_formats->head1); -$row_offset1++; - -if (empty($items)) { - $items=array(); -} -foreach ($items as $item) { - //get the class of item-typ - $itemobj = feedback_get_item_class($item->typ); - $row_offset1 = $itemobj->excelprint_item($worksheet1, - $row_offset1, - $xls_formats, - $item, - $mygroupid, - $coursefilter); -} - -//////////////////////////////////////////////////////////////////////// -//print the detailed sheet -//////////////////////////////////////////////////////////////////////// -//get the completeds - -$completeds = feedback_get_completeds_group($feedback, $mygroupid, $coursefilter); -//important: for each completed you have to print each item, even if it is not filled out!!! -//therefor for each completed we have to iterate over all items of the feedback -//this is done by feedback_excelprint_detailed_items - -$row_offset2 = 0; -//first we print the table-header -$row_offset2 = feedback_excelprint_detailed_head($worksheet2, $xls_formats, $items, $row_offset2); - - -if (is_array($completeds)) { - foreach ($completeds as $completed) { - $row_offset2 = feedback_excelprint_detailed_items($worksheet2, - $xls_formats, - $completed, - $items, - $row_offset2); - } -} - - -$workbook->close(); -exit; -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//functions -//////////////////////////////////////////////////////////////////////////////// - - -function feedback_excelprint_detailed_head(&$worksheet, $xls_formats, $items, $row_offset) { - global $fstring, $feedback; - - if (!$items) { - return; - } - $col_offset = 0; - - $worksheet->write_string($row_offset + 1, $col_offset, $fstring->idnumber, $xls_formats->head2); - $col_offset++; - - $worksheet->write_string($row_offset + 1, $col_offset, $fstring->username, $xls_formats->head2); - $col_offset++; - - $worksheet->write_string($row_offset + 1, $col_offset, $fstring->fullname, $xls_formats->head2); - $col_offset++; - - foreach ($items as $item) { - $worksheet->write_string($row_offset, $col_offset, $item->name, $xls_formats->head2); - $worksheet->write_string($row_offset + 1, $col_offset, $item->label, $xls_formats->head2); - $col_offset++; - } - - $worksheet->write_string($row_offset + 1, $col_offset, $fstring->courseid, $xls_formats->head2); - $col_offset++; - - $worksheet->write_string($row_offset + 1, $col_offset, $fstring->course, $xls_formats->head2); - $col_offset++; - - return $row_offset + 2; -} - -function feedback_excelprint_detailed_items(&$worksheet, $xls_formats, - $completed, $items, $row_offset) { - global $DB, $fstring; - - if (!$items) { - return; - } - $col_offset = 0; - $courseid = 0; - - $feedback = $DB->get_record('feedback', array('id'=>$completed->feedback)); - //get the username - //anonymous users are separated automatically because the userid in the completed is "0" - if ($user = $DB->get_record('user', array('id'=>$completed->userid))) { - if ($completed->anonymous_response == FEEDBACK_ANONYMOUS_NO) { - $worksheet->write_string($row_offset, $col_offset, $user->idnumber, $xls_formats->head2); - $col_offset++; - $userfullname = fullname($user); - $worksheet->write_string($row_offset, $col_offset, $user->username, $xls_formats->head2); - $col_offset++; - } else { - $userfullname = $fstring->anonymous_user; - $worksheet->write_string($row_offset, $col_offset, '-', $xls_formats->head2); - $col_offset++; - $worksheet->write_string($row_offset, $col_offset, '-', $xls_formats->head2); - $col_offset++; - } - } else { - $userfullname = $fstring->anonymous_user; - $worksheet->write_string($row_offset, $col_offset, '-', $xls_formats->head2); - $col_offset++; - $worksheet->write_string($row_offset, $col_offset, '-', $xls_formats->head2); - $col_offset++; - } - - $worksheet->write_string($row_offset, $col_offset, $userfullname, $xls_formats->head2); - - $col_offset++; - foreach ($items as $item) { - $params = array('item' => $item->id, 'completed' => $completed->id); - $value = $DB->get_record('feedback_value', $params); - - $itemobj = feedback_get_item_class($item->typ); - $printval = $itemobj->get_printval($item, $value); - $printval = trim($printval); - - if (is_numeric($printval)) { - $worksheet->write_number($row_offset, $col_offset, $printval, $xls_formats->default); - } else if ($printval != '') { - $worksheet->write_string($row_offset, $col_offset, $printval, $xls_formats->default); - } - $printval = ''; - $col_offset++; - $courseid = isset($value->course_id) ? $value->course_id : 0; - if ($courseid == 0) { - $courseid = $feedback->course; - } - } - $worksheet->write_number($row_offset, $col_offset, $courseid, $xls_formats->default); - $col_offset++; - if (isset($courseid) AND $course = $DB->get_record('course', array('id' => $courseid))) { - $coursecontext = context_course::instance($courseid); - $shortname = format_string($course->shortname, true, array('context' => $coursecontext)); - $worksheet->write_string($row_offset, $col_offset, $shortname, $xls_formats->default); - } - return $row_offset + 1; -} diff --git a/mod/feedback/classes/course_select_form.php b/mod/feedback/classes/course_select_form.php new file mode 100644 index 00000000000..9c644efa325 --- /dev/null +++ b/mod/feedback/classes/course_select_form.php @@ -0,0 +1,83 @@ +. + +/** + * Contains class mod_feedback_course_map_form + * + * @package mod_feedback + * @copyright 2016 Marina Glancy + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +defined('MOODLE_INTERNAL') || die(); + +/** + * Form for mapping courses to the feedback + * + * @package mod_feedback + * @copyright 2016 Marina Glancy + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class mod_feedback_course_select_form extends moodleform { + /** @var moodle_url */ + protected $action; + /** @var mod_feedback_structure $feedbackstructure */ + protected $feedbackstructure; + + /** + * Constructor + * + * @param string|moodle_url $action the action attribute for the form + * @param mod_feedback_structure $feedbackstructure + * @param bool $editable + */ + public function __construct($action, mod_feedback_structure $feedbackstructure, $editable = true) { + $this->action = new moodle_url($action, ['courseid' => null]); + $this->feedbackstructure = $feedbackstructure; + parent::__construct($action, null, 'post', '', ['id' => 'feedback_course_filter'], $editable); + } + + /** + * Definition of the form + */ + public function definition() { + $mform = $this->_form; + $feedbackstructure = $this->feedbackstructure; + + $mform->addElement('hidden', 'id'); + $mform->setType('id', PARAM_INT); + + if (!$this->_form->_freezeAll && ($courses = $feedbackstructure->get_completed_courses()) && count($courses) > 1) { + $elements = []; + $elements[] = $mform->createElement('autocomplete', 'courseid', get_string('filter_by_course', 'feedback'), + ['' => get_string('fulllistofcourses')] + $courses); + $elements[] = $mform->createElement('submit', 'submitbutton', get_string('filter')); + if ($feedbackstructure->get_courseid()) { + $elements[] = $mform->createElement('static', 'showall', '', + html_writer::link($this->action, get_string('show_all', 'feedback'))); + } + if (defined('BEHAT_SITE_RUNNING')) { + // TODO MDL-53734 remove this - behat does not recognise autocomplete element inside a group. + foreach ($elements as $element) { + $mform->addElement($element); + } + } else { + $mform->addGroup($elements, 'coursefilter', get_string('filter_by_course', 'feedback'), array(' '), false); + } + } + + $this->set_data(['courseid' => $feedbackstructure->get_courseid(), 'id' => $feedbackstructure->get_cm()->id]); + } +} diff --git a/mod/feedback/classes/responses_anon_table.php b/mod/feedback/classes/responses_anon_table.php index 20964b26bb5..204bddb94d8 100644 --- a/mod/feedback/classes/responses_anon_table.php +++ b/mod/feedback/classes/responses_anon_table.php @@ -36,48 +36,56 @@ class mod_feedback_responses_anon_table extends mod_feedback_responses_table { /** @var string */ protected $showallparamname = 'ashowall'; + /** @var string */ + protected $downloadparamname = 'adownload'; + /** * Initialises table */ public function init() { - $this->uniqueid = 'feedback-showentry-anon-list-' . $this->cm->instance; + $cm = $this->feedbackstructure->get_cm(); + $this->uniqueid = 'feedback-showentry-anon-list-' . $cm->instance; // There potentially can be both tables with anonymouns and non-anonymous responses on // the same page (for example when feedback anonymity was changed after some people // already responded). In this case we need to distinguish tables' pagination parameters. $this->request[TABLE_VAR_PAGE] = 'apage'; - $tablecolumns = array('random_response', 'showresponse'); - $tableheaders = array('', ''); + $tablecolumns = ['random_response']; + $tableheaders = [get_string('response_nr', 'feedback')]; - $context = context_module::instance($this->cm->id); - if (has_capability('mod/feedback:deletesubmissions', $context)) { - $tablecolumns[] = 'deleteentry'; - $tableheaders[] = ''; + if ($this->feedbackstructure->get_feedback()->course == SITEID && !$this->feedbackstructure->get_courseid()) { + $tablecolumns[] = 'courseid'; + $tableheaders[] = get_string('course'); } $this->define_columns($tablecolumns); $this->define_headers($tableheaders); - $this->sortable(false, 'random_response'); - $this->collapsible(false); - $this->set_attribute('id', 'showentryanonymtable'); + $this->sortable(true, 'random_response'); + $this->collapsible(true); + $this->set_attribute('id', 'showentryanontable'); - $params = ['instance' => $this->cm->instance, 'anon' => FEEDBACK_ANONYMOUS_YES]; + $params = ['instance' => $cm->instance, + 'anon' => FEEDBACK_ANONYMOUS_YES, + 'courseid' => $this->feedbackstructure->get_courseid()]; - $fields = 'DISTINCT c.id, c.random_response'; + $fields = 'c.id, c.random_response, c.courseid'; $from = '{feedback_completed} c'; $where = 'c.anonymous_response = :anon AND c.feedback = :instance'; + if ($this->feedbackstructure->get_courseid()) { + $where .= ' AND c.courseid = :courseid'; + } - $group = groups_get_activity_group($this->cm, true); + $group = groups_get_activity_group($this->feedbackstructure->get_cm(), true); if ($group) { - $from .= ' JOIN {groups_members} g ON g.groupid = :group AND g.userid = c.userid'; + $where .= ' AND c.userid IN (SELECT g.userid FROM {groups_members} g WHERE g.groupid = :group)'; $params['group'] = $group; } $this->set_sql($fields, $from, $where, $params); - $this->set_count_sql("SELECT COUNT(DISTINCT c.id) FROM $from WHERE $where", $params); + $this->set_count_sql("SELECT COUNT(c.id) FROM $from WHERE $where", $params); } /** @@ -95,22 +103,11 @@ class mod_feedback_responses_anon_table extends mod_feedback_responses_table { * @return string */ public function col_random_response($row) { - return get_string('response_nr', 'feedback').': '. $row->random_response; - } - - /** - * Prepares column showresponse for display - * @param stdClass $row - * @return string - */ - public function col_showresponse($row) { - return html_writer::link($this->get_link_single_entry($row), get_string('show_entry', 'feedback')); - } - - /** - * Generate the HTML for the table preferences reset button. - */ - protected function render_reset_button() { - return ''; + if ($this->is_downloading()) { + return $row->random_response; + } else { + return html_writer::link($this->get_link_single_entry($row), + get_string('response_nr', 'feedback').': '. $row->random_response); + } } } diff --git a/mod/feedback/classes/responses_table.php b/mod/feedback/classes/responses_table.php index 5bd0948776e..6130a1bdced 100644 --- a/mod/feedback/classes/responses_table.php +++ b/mod/feedback/classes/responses_table.php @@ -36,8 +36,8 @@ require_once($CFG->libdir . '/tablelib.php'); */ class mod_feedback_responses_table extends table_sql { - /** @var cm_info */ - protected $cm; + /** @var mod_feedback_structure */ + protected $feedbackstructure; /** @var int */ protected $grandtotal = null; @@ -48,23 +48,34 @@ class mod_feedback_responses_table extends table_sql { /** @var string */ protected $showallparamname = 'showall'; + /** @var string */ + protected $downloadparamname = 'download'; + /** * Constructor * - * @param cm_info $cm + * @param mod_feedback_structure $feedbackstructure */ - public function __construct(cm_info $cm) { - $this->cm = $cm; + public function __construct(mod_feedback_structure $feedbackstructure) { + $this->feedbackstructure = $feedbackstructure; - parent::__construct('feedback-showentry-list-' . $cm->instance); + parent::__construct('feedback-showentry-list-' . $feedbackstructure->get_cm()->instance); $this->showall = optional_param($this->showallparamname, 0, PARAM_BOOL); $this->define_baseurl(new moodle_url('/mod/feedback/show_entries.php', - ['id' => $this->cm->id])); + ['id' => $this->feedbackstructure->get_cm()->id])); + if ($courseid = $this->feedbackstructure->get_courseid()) { + $this->baseurl->param('courseid', $courseid); + } if ($this->showall) { $this->baseurl->param($this->showallparamname, $this->showall); } + $this->is_downloadable(true); + $this->is_downloading(optional_param($this->downloadparamname, 0, PARAM_ALPHA), + 'feedback_test'); + + $this->useridfield = 'userid'; $this->init(); } @@ -73,44 +84,85 @@ class mod_feedback_responses_table extends table_sql { */ protected function init() { - $tablecolumns = array('userpic', 'fullname', 'completed_timemodified'); - $tableheaders = array(get_string('userpic'), get_string('fullnameuser'), get_string('date')); + $tablecolumns = array('userpic', 'fullname'); + $tableheaders = array(get_string('userpic'), get_string('fullnameuser')); - $context = context_module::instance($this->cm->id); - if (has_capability('mod/feedback:deletesubmissions', $context)) { - $tablecolumns[] = 'deleteentry'; - $tableheaders[] = ''; + $extrafields = get_extra_user_fields($this->get_context()); + $ufields = user_picture::fields('u', $extrafields, $this->useridfield); + $fields = 'c.id, c.timemodified as completed_timemodified, c.courseid, '.$ufields; + $from = '{feedback_completed} c ' + . 'JOIN {user} u ON u.id = c.userid AND u.deleted = :notdeleted'; + $where = 'c.anonymous_response = :anon + AND c.feedback = :instance'; + if ($this->feedbackstructure->get_courseid()) { + $where .= ' AND c.courseid = :courseid'; } + if ($this->is_downloading()) { + // When downloading data: + // Remove 'userpic' from downloaded data. + array_shift($tablecolumns); + array_shift($tableheaders); + + // Add all identity fields as separate columns. + foreach ($extrafields as $field) { + $fields .= ", u.{$field}"; + $tablecolumns[] = $field; + $tableheaders[] = get_user_field_name($field); + } + } + + if ($this->feedbackstructure->get_feedback()->course == SITEID && !$this->feedbackstructure->get_courseid()) { + $tablecolumns[] = 'courseid'; + $tableheaders[] = get_string('course'); + } + + $tablecolumns[] = 'completed_timemodified'; + $tableheaders[] = get_string('date'); + $this->define_columns($tablecolumns); $this->define_headers($tableheaders); $this->sortable(true, 'lastname', SORT_ASC); - $this->collapsible(false); + $this->collapsible(true); $this->set_attribute('id', 'showentrytable'); $params = array(); $params['anon'] = FEEDBACK_ANONYMOUS_NO; - $params['instance'] = $this->cm->instance; + $params['instance'] = $this->feedbackstructure->get_feedback()->id; $params['notdeleted'] = 0; + $params['courseid'] = $this->feedbackstructure->get_courseid(); - $ufields = user_picture::fields('u', null, 'userid'); - $fields = 'DISTINCT c.id, c.timemodified as completed_timemodified, '.$ufields; - $from = '{user} u, {feedback_completed} c'; - $where = 'anonymous_response = :anon - AND u.id = c.userid - AND c.feedback = :instance - AND u.deleted = :notdeleted'; - - $group = groups_get_activity_group($this->cm, true); + $group = groups_get_activity_group($this->feedbackstructure->get_cm(), true); if ($group) { - $from .= ', {groups_members} g'; - $where .= ' AND g.groupid = :group AND g.userid = c.userid'; + $where .= ' AND c.userid IN (SELECT g.userid FROM {groups_members} g WHERE g.groupid = :group)'; $params['group'] = $group; } $this->set_sql($fields, $from, $where, $params); - $this->set_count_sql("SELECT COUNT(DISTINCT c.id) FROM $from WHERE $where", $params); + $this->set_count_sql("SELECT COUNT(c.id) FROM $from WHERE $where", $params); + } + + /** + * Current context + * @return context_module + */ + protected function get_context() { + return context_module::instance($this->feedbackstructure->get_cm()->id); + } + + /** + * Allows to set the display column value for all columns without "col_xxxxx" method. + * @param string $column column name + * @param stdClass $row current record result of SQL query + */ + public function other_cols($column, $row) { + if (preg_match('/^val(\d+)$/', $column, $matches)) { + $items = $this->feedbackstructure->get_items(); + $itemobj = feedback_get_item_class($items[$matches[1]]->typ); + return trim($itemobj->get_printval($items[$matches[1]], (object) ['value' => $row->$column] )); + } + return $row->$column; } /** @@ -120,7 +172,8 @@ class mod_feedback_responses_table extends table_sql { */ public function col_userpic($row) { global $OUTPUT; - return $OUTPUT->user_picture($row, array('courseid' => $this->cm->course)); + $user = user_picture::unalias($row, [], $this->useridfield); + return $OUTPUT->user_picture($user, array('courseid' => $this->feedbackstructure->get_cm()->course)); } /** @@ -129,11 +182,10 @@ class mod_feedback_responses_table extends table_sql { * @return string */ public function col_deleteentry($row) { - $context = context_module::instance($this->cm->id); - if (has_capability('mod/feedback:deletesubmissions', $context)) { - $deleteentryurl = new moodle_url($this->baseurl, ['delete' => $row->id]); - return html_writer::link($deleteentryurl, get_string('delete_entry', 'feedback')); - } + global $OUTPUT; + $icon = $OUTPUT->render(new \pix_icon('t/delete', get_string('delete_entry', 'feedback'))); + $deleteentryurl = new moodle_url($this->baseurl, ['delete' => $row->id]); + return html_writer::link($deleteentryurl, $icon); } /** @@ -142,7 +194,7 @@ class mod_feedback_responses_table extends table_sql { * @return \moodle_url */ protected function get_link_single_entry($row) { - return new moodle_url($this->baseurl, ['userid' => $row->userid, 'showcompleted' => $row->id]); + return new moodle_url($this->baseurl, ['userid' => $row->{$this->useridfield}, 'showcompleted' => $row->id]); } /** @@ -151,8 +203,59 @@ class mod_feedback_responses_table extends table_sql { * @return string */ public function col_completed_timemodified($student) { - return html_writer::link($this->get_link_single_entry($student), - userdate($student->completed_timemodified)); + if ($this->is_downloading()) { + return userdate($student->completed_timemodified); + } else { + return html_writer::link($this->get_link_single_entry($student), + userdate($student->completed_timemodified)); + } + } + + /** + * Prepares column courseid for display + * @param array $row + * @return string + */ + public function col_courseid($row) { + $courses = $this->feedbackstructure->get_completed_courses(); + $name = ''; + if (isset($courses[$row->courseid])) { + $name = $courses[$row->courseid]; + if (!$this->is_downloading()) { + $name = html_writer::link(course_get_url($row->courseid), $name); + } + } + return $name; + } + + /** + * Adds common values to the table that do not change the number or order of entries and + * are only needed when outputting or downloading data. + */ + protected function add_all_values_to_output() { + $tablecolumns = array_keys($this->columns); + $tableheaders = $this->headers; + + // Add all feedback response values. + $items = $this->feedbackstructure->get_items(true); + foreach ($items as $nr => $item) { + $this->sql->fields .= ", v{$nr}.value AS val{$nr}"; + $this->sql->from .= " LEFT OUTER JOIN {feedback_value} v{$nr} " . + "ON v{$nr}.completed = c.id AND v{$nr}.item = :itemid{$nr}"; + $this->sql->params["itemid{$nr}"] = $item->id; + $tablecolumns[] = "val{$nr}"; + $itemobj = feedback_get_item_class($item->typ); + $tableheaders[] = $itemobj->get_display_name($item); + } + + // Add 'Delete entry' column. + if (!$this->is_downloading() && has_capability('mod/feedback:deletesubmissions', $this->get_context())) { + $tablecolumns[] = 'deleteentry'; + $tableheaders[] = ''; + } + + $this->define_columns($tablecolumns); + $this->define_headers($tableheaders); } /** @@ -165,21 +268,23 @@ class mod_feedback_responses_table extends table_sql { public function query_db($pagesize, $useinitialsbar=true) { global $DB; $this->totalrows = $grandtotal = $this->get_total_responses_count(); - $this->initialbars($useinitialsbar); + if (!$this->is_downloading()) { + $this->initialbars($useinitialsbar); - list($wsql, $wparams) = $this->get_sql_where(); - if ($wsql) { - $this->countsql .= ' AND '.$wsql; - $this->countparams = array_merge($this->countparams, $wparams); + list($wsql, $wparams) = $this->get_sql_where(); + if ($wsql) { + $this->countsql .= ' AND '.$wsql; + $this->countparams = array_merge($this->countparams, $wparams); - $this->sql->where .= ' AND '.$wsql; - $this->sql->params = array_merge($this->sql->params, $wparams); + $this->sql->where .= ' AND '.$wsql; + $this->sql->params = array_merge($this->sql->params, $wparams); - $this->totalrows = $DB->count_records_sql($this->countsql, $this->countparams); - } + $this->totalrows = $DB->count_records_sql($this->countsql, $this->countparams); + } - if ($this->totalrows > $pagesize) { - $this->pagesize($pagesize, $this->totalrows); + if ($this->totalrows > $pagesize) { + $this->pagesize($pagesize, $this->totalrows); + } } if ($sort = $this->get_sql_sort()) { @@ -191,7 +296,11 @@ class mod_feedback_responses_table extends table_sql { WHERE {$this->sql->where} {$sort}"; - $this->rawdata = $DB->get_recordset_sql($sql, $this->sql->params, $this->get_page_start(), $this->get_page_size()); + if (!$this->is_downloading()) { + $this->rawdata = $DB->get_recordset_sql($sql, $this->sql->params, $this->get_page_start(), $this->get_page_size()); + } else { + $this->rawdata = $DB->get_recordset_sql($sql, $this->sql->params); + } } /** @@ -211,7 +320,7 @@ class mod_feedback_responses_table extends table_sql { * @param array $columns an array of identifying names for columns. If * columns are sorted then column names must correspond to a field in sql. */ - function define_columns($columns) { + public function define_columns($columns) { parent::define_columns($columns); foreach ($this->columns as $column => $column) { // Automatically assign classes to columns. @@ -219,12 +328,24 @@ class mod_feedback_responses_table extends table_sql { } } + /** + * Convenience method to call a number of methods for you to display the + * table. + * @param int $pagesize + * @param bool $useinitialsbar + * @param string $downloadhelpbutton + */ + public function out($pagesize, $useinitialsbar, $downloadhelpbutton='') { + $this->add_all_values_to_output(); + parent::out($pagesize, $useinitialsbar, $downloadhelpbutton); + } + /** * Displays the table */ public function display() { global $OUTPUT; - groups_print_activity_menu($this->cm, $this->baseurl); + groups_print_activity_menu($this->feedbackstructure->get_cm(), $this->baseurl->out()); $grandtotal = $this->get_total_responses_count(); if (!$grandtotal) { echo $OUTPUT->box(get_string('nothingtodisplay'), 'generalbox nothingtodisplay'); @@ -280,4 +401,38 @@ class mod_feedback_responses_table extends table_sql { $nextrow ? $this->get_link_single_entry($nextrow) : null, ]; } + + /** + * Download the data. + */ + public function download() { + \core\session\manager::write_close(); + $this->out($this->get_total_responses_count(), false); + exit; + } + + /** + * Returns html code for displaying "Download" button if applicable. + */ + public function download_buttons() { + if ($this->is_downloadable() && !$this->is_downloading()) { + + $elementid = $this->uniqueid . '_download'; + $html = '
'; + $html .= '
'; + if ($courseid = $this->feedbackstructure->get_courseid()) { + $html .= ''; + } + $html .= html_writer::tag('label', get_string('downloadresponseas', 'feedback'), + ['for' => $elementid]); + $html .= html_writer::select($this->get_download_menu(), + $this->downloadparamname, $this->defaultdownloadformat, false, ['id' => $elementid]); + $html .= html_writer::empty_tag('input', ['type' => 'submit', 'value' => get_string('download')]); + $html .= '
'; + + return $html; + } else { + return ''; + } + } } diff --git a/mod/feedback/lang/en/feedback.php b/mod/feedback/lang/en/feedback.php index 27ce17583fb..87773dd84f1 100644 --- a/mod/feedback/lang/en/feedback.php +++ b/mod/feedback/lang/en/feedback.php @@ -129,6 +129,7 @@ $string['checkbox'] = 'Multiple choice - multiple answers allowed (check boxes)' $string['check_values'] = 'Possible responses'; $string['choosefile'] = 'Choose a file'; $string['chosen_feedback_response'] = 'chosen feedback response'; +$string['downloadresponseas'] = 'Download all responses as:'; $string['importfromthisfile'] = 'Import from this file'; $string['import_questions'] = 'Import questions'; $string['import_successfully'] = 'Import successfully'; diff --git a/mod/feedback/show_entries.php b/mod/feedback/show_entries.php index 794a19949e5..7b0a99081e0 100644 --- a/mod/feedback/show_entries.php +++ b/mod/feedback/show_entries.php @@ -32,6 +32,7 @@ $id = required_param('id', PARAM_INT); $userid = optional_param('userid', false, PARAM_INT); $showcompleted = optional_param('showcompleted', false, PARAM_INT); $deleteid = optional_param('delete', null, PARAM_INT); +$courseid = optional_param('courseid', null, PARAM_INT); //////////////////////////////////////////////////////// //get the objects @@ -64,9 +65,24 @@ if ($deleteid) { $feedbackstructure = new mod_feedback_completion($feedback, $cm, 0, true, $showcompleted, $userid); } else { // Viewing list of reponses. - $feedbackstructure = new mod_feedback_structure($feedback, $cm); + $feedbackstructure = new mod_feedback_structure($feedback, $cm, $courseid); } +$responsestable = new mod_feedback_responses_table($feedbackstructure); +$anonresponsestable = new mod_feedback_responses_anon_table($feedbackstructure); + +if ($responsestable->is_downloading()) { + $responsestable->download(); +} +if ($anonresponsestable->is_downloading()) { + $anonresponsestable->download(); +} + +// Process course select form. +$courseselectform = new mod_feedback_course_select_form($baseurl, $feedbackstructure, $feedback->course == SITEID); +if ($data = $courseselectform->get_data()) { + redirect(new moodle_url($baseurl, ['courseid' => $data->courseid])); +} // Print the page header. navigation_node::override_active_url($baseurl); $PAGE->set_heading($course->fullname); @@ -104,12 +120,9 @@ if ($deleteid) { $feedbackstructure, 'feedback_viewresponse_form'); $form->display(); - if ($userid) { - $responsestable = new mod_feedback_responses_table($cm); - } else { - $responsestable = new mod_feedback_responses_anon_table($cm); - } - list($prevresponseurl, $returnurl, $nextresponseurl) = $responsestable->get_reponse_navigation_links($completedrecord); + list($prevresponseurl, $returnurl, $nextresponseurl) = $userid ? + $responsestable->get_reponse_navigation_links($completedrecord) : + $anonresponsestable->get_reponse_navigation_links($completedrecord); echo html_writer::start_div('response_navigation'); echo $prevresponseurl ? html_writer::link($prevresponseurl, get_string('prev'), ['class' => 'prev_response']) : ''; @@ -118,9 +131,9 @@ if ($deleteid) { echo html_writer::end_div(); } else { // Print the list of responses. + $courseselectform->display(); // Show non-anonymous responses (always retrieve them even if current feedback is anonymous). - $responsestable = new mod_feedback_responses_table($cm); $totalrows = $responsestable->get_total_responses_count(); if (!$feedbackstructure->is_anonymous() || $totalrows) { echo $OUTPUT->heading(get_string('non_anonymous_entries', 'feedback', $totalrows), 4); @@ -129,7 +142,6 @@ if ($deleteid) { // Show anonymous responses (always retrieve them even if current feedback is not anonymous). $feedbackstructure->shuffle_anonym_responses(); - $anonresponsestable = new mod_feedback_responses_anon_table($cm); $totalrows = $anonresponsestable->get_total_responses_count(); if ($feedbackstructure->is_anonymous() || $totalrows) { echo $OUTPUT->heading(get_string('anonymous_entries', 'feedback', $totalrows), 4); diff --git a/mod/feedback/tabs.php b/mod/feedback/tabs.php index 849077bef29..6c51bf8fccf 100644 --- a/mod/feedback/tabs.php +++ b/mod/feedback/tabs.php @@ -45,44 +45,39 @@ if (!isset($current_tab)) { $viewurl = new moodle_url('/mod/feedback/view.php', array('id' => $usedid)); $row[] = new tabobject('view', $viewurl->out(), get_string('overview', 'feedback')); +$urlparams = ['id' => $usedid]; +if ($feedback->course == SITEID && $courseid) { + $urlparams['courseid'] = $courseid; +} if (has_capability('mod/feedback:edititems', $context)) { - $editurl = new moodle_url('/mod/feedback/edit.php', array('id'=>$usedid, 'do_show'=>'edit')); + $editurl = new moodle_url('/mod/feedback/edit.php', $urlparams + ['do_show' => 'edit']); $row[] = new tabobject('edit', $editurl->out(), get_string('edit_items', 'feedback')); - $templateurl = new moodle_url('/mod/feedback/edit.php', array('id'=>$usedid, 'do_show'=>'templates')); + $templateurl = new moodle_url('/mod/feedback/edit.php', $urlparams + ['do_show' => 'templates']); $row[] = new tabobject('templates', $templateurl->out(), get_string('templates', 'feedback')); } if ($feedback->course == SITEID && has_capability('mod/feedback:mapcourse', $context)) { - $mapurl = new moodle_url('/mod/feedback/mapcourse.php', array('id' => $usedid)); + $mapurl = new moodle_url('/mod/feedback/mapcourse.php', $urlparams); $row[] = new tabobject('mapcourse', $mapurl->out(), get_string('mappedcourses', 'feedback')); } if (has_capability('mod/feedback:viewreports', $context)) { if ($feedback->course == SITEID) { - $url_params = array('id' => $usedid, 'courseid' => $courseid); - $analysisurl = new moodle_url('/mod/feedback/analysis_course.php', $url_params); - $row[] = new tabobject('analysis', - $analysisurl->out(), - get_string('analysis', 'feedback')); - + $analysisurl = new moodle_url('/mod/feedback/analysis_course.php', $urlparams); } else { - $url_params = array('id' => $usedid); - $analysisurl = new moodle_url('/mod/feedback/analysis.php', $url_params); - $row[] = new tabobject('analysis', - $analysisurl->out(), - get_string('analysis', 'feedback')); + $analysisurl = new moodle_url('/mod/feedback/analysis.php', $urlparams); } + $row[] = new tabobject('analysis', $analysisurl->out(), get_string('analysis', 'feedback')); - $url_params = array('id' => $usedid); - $reporturl = new moodle_url('/mod/feedback/show_entries.php', $url_params); + $reporturl = new moodle_url('/mod/feedback/show_entries.php', $urlparams); $row[] = new tabobject('showentries', $reporturl->out(), get_string('show_entries', 'feedback')); if ($feedback->anonymous == FEEDBACK_ANONYMOUS_NO AND $feedback->course != SITEID) { - $nonrespondenturl = new moodle_url('/mod/feedback/show_nonrespondents.php', array('id'=>$usedid)); + $nonrespondenturl = new moodle_url('/mod/feedback/show_nonrespondents.php', $urlparams); $row[] = new tabobject('nonrespondents', $nonrespondenturl->out(), get_string('show_nonrespondents', 'feedback')); diff --git a/mod/feedback/tests/behat/anonymous.feature b/mod/feedback/tests/behat/anonymous.feature index 04d9ec5d48b..b6673980634 100644 --- a/mod/feedback/tests/behat/anonymous.feature +++ b/mod/feedback/tests/behat/anonymous.feature @@ -102,7 +102,7 @@ Feature: Anonymous feedback And I follow "Show responses" And I should not see "Username" And I should see "Anonymous entries (2)" - And I click on "Show response" "link" in the "Response number: 1" "table_row" + And I follow "Response number: 1" And I should not see "Username" And I should see "Response number: 1 (Anonymous)" And I log out @@ -164,7 +164,7 @@ Feature: Anonymous feedback And I follow "Site feedback" And I follow "Show responses" And I should see "Anonymous entries (2)" - And I click on "Show response" "link" in the "Response number: 1" "table_row" + And I follow "Response number: 1" And I should see "Response number: 1 (Anonymous)" And I log out @@ -222,7 +222,7 @@ Feature: Anonymous feedback And I follow "Show responses" And I should not see "Username" And I should see "Anonymous entries (2)" - And I click on "Show response" "link" in the "Response number: 1" "table_row" + And I follow "Response number: 1" And I should not see "Username" And I should see "Response number: 1 (Anonymous)" And I should not see "Prev" diff --git a/mod/feedback/tests/behat/coursemapping.feature b/mod/feedback/tests/behat/coursemapping.feature index 4c1c8543ae5..8a814c16357 100644 --- a/mod/feedback/tests/behat/coursemapping.feature +++ b/mod/feedback/tests/behat/coursemapping.feature @@ -115,7 +115,7 @@ Feature: Mapping courses in a feedback And I follow "Course feedback" And I follow "Analysis" - And the field "Filter by course" matches value "Choose..." + And I should see "All courses" in the "#feedback_course_filter .fautocomplete .label" "css_element" And I should see "1 (33.33 %)" in the "option a" "table_row" And I should see "1 (33.33 %)" in the "option b" "table_row" And I should see "1 (33.33 %)" in the "option c" "table_row" @@ -124,7 +124,9 @@ Feature: Mapping courses in a feedback And I should see "2.50" in the "C1" "table_row" And I should see "1.00" in the "Acceptance test site" "table_row" And I follow "Back" - And I set the field "Filter by course" to "C1" + And I set the field "Filter by course" to "Course 1" + And I press "Filter" + And I should see "Course 1" in the "#feedback_course_filter .fautocomplete .label" "css_element" And I should see "0" in the "option a" "table_row" And I should see "1 (50.00 %)" in the "option b" "table_row" And I should see "1 (50.00 %)" in the "option c" "table_row" @@ -194,7 +196,7 @@ Feature: Mapping courses in a feedback And I am on site homepage And I follow "Course feedback" And I follow "Analysis" - And the field "Filter by course" matches value "Choose..." + And I should see "All courses" in the "#feedback_course_filter .fautocomplete .label" "css_element" And I should see "0" in the "option a" "table_row" And I should see "1 (33.33 %)" in the "option b" "table_row" And I should see "2 (66.67 %)" in the "option c" "table_row" @@ -203,12 +205,14 @@ Feature: Mapping courses in a feedback And I should see "3.00" in the "C3" "table_row" And I should see "2.50" in the "C2" "table_row" And I follow "Back" - And I set the field "Filter by course" to "C2" + And I set the field "Filter by course" to "Course 2" + And I press "Filter" And I should see "0" in the "option a" "table_row" And I should see "1 (50.00 %)" in the "option b" "table_row" And I should see "1 (50.00 %)" in the "option c" "table_row" And I should see "2 (100.00 %)" in the "option e" "table_row" - And I set the field "Filter by course" to "C3" + And I set the field "Filter by course" to "Course 3" + And I press "Filter" And I should see "0" in the "option a" "table_row" And I should see "0" in the "option b" "table_row" And I should see "1 (100.00 %)" in the "option c" "table_row"